package bluej.pkgmgr;
import bluej.BlueJEvent;
import bluej.Config;
import bluej.extensions.SourceType;
import bluej.utility.Debug;
import bluej.utility.DialogManager;
import bluej.utility.FileUtility;
import bluej.utility.Utility;
import javafx.application.Platform;
import threadchecker.OnThread;
import threadchecker.Tag;
import javax.swing.*;
import javax.tools.DocumentationTool;
import javax.tools.ToolProvider;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
| This class handles documentation generation from inside BlueJ.
| Documentation can be generated for a whole project or for a single class.
| For each Project instance there should be one instance of DocuGenerator
| that takes care of project documentation. Project documentation is written
| into a directory in the project directory.
| As the documentation for a single class serves merely as a preview option,
| it is generated in a temporary directory.
|
| @author Axel Schmolitzky
|
public class DocuGenerator
{
| The name of the directory where project documentation is written to.
|
private static String docDirName =
Config.getPropString("doctool.outputdir");
| Header for log file when generating documentation for projects.
|
private static final String projectLogHeader =
Config.getPropString("Project documentation");
| Header for log file when generating documentation for classes.
|
private static String classLogHeader =
Config.getPropString("Class documentation");
| The name (including path) of the documentation tool used.
|
private static String docCommand =
Config.getJDKExecutablePath("doctool.command","javadoc");
| javadoc parameters for all runs: include author and version
| information, do not generate information about deprecated features,
| consider only package, protected, and public classes and members,
| include bottom line.
|
private static String fixedJavadocParams = Config.getPropString("doctool.options");
| javadoc parameters for preview runs: do not generate an index,
| a tree, a help.
|
private static String tmpJavadocParams = " -noindex -notree -nohelp -nonavbar";
| The project this generator belongs to.
|
private Project project;
| The project directory.
|
private File projectDir;
| the path of the project directory, the root for all sources.
|
private String projectDirPath;
| the directory where documentation is written to.
|
private File docDir;
| the path of the directory where documentation is written to.
|
private String docDirPath;
| Creates a separate thread that starts the external call for faster
| return to the GUI. If the call was successful the URL given in 'url'
| will be shown in a web browser.
| @param call the call to the documentation generating tool.
| @param url the URL to be shown after successful completion.
|
private static void generateDoc(String[] call, File result, File log,
String header, boolean openBrowser)
{
Thread starterThread = new DocuRunStarter(call, result, log, header, openBrowser);
starterThread.setPriority(Thread.MIN_PRIORITY);
starterThread.start();
BlueJEvent.raiseEvent(BlueJEvent.GENERATING_DOCU, null);
}
| This class enables to run the external call for a documentation
| generation in a different thread. An instance of this class gets
| the string that constitutes the external call as a constructor
| parameter. The second constructor parameter is the name of the
| HTML file that should be opened by a web browser if the documentation
| generation was successful.
|
private static class DocuRunStarter
extends Thread
{
private String[] docuCall;
private File showFile;
private File logFile;
private String logHeader;
private boolean openBrowser;
private static final Object mutex = new Object();
public DocuRunStarter(String[] call, File result, File log,
String header, boolean browse)
{
docuCall = call;
showFile = result;
logFile = log;
logHeader = header;
openBrowser = browse;
}
| Perform the call that was passed in as a constructor parameter.
| If this call was successful let the result be shown in a browser.
|
@Override
@OnThread(value = Tag.Worker, ignoreParent = true)
public void run()
{
PrintWriter logWriter = null;
int exitValue = -1;
try {
synchronized (mutex) {
OutputStream logStream = new FileOutputStream(logFile);
logWriter = new PrintWriter(logStream,true);
String[] docuCall2 = new String[docuCall.length-1];
logWriter.println(logHeader);
logWriter.println("<---- javadoc command: ---->");
int i;
for (i=0; i < docuCall.length; i++) {
logWriter.println(docuCall[i]);
if (i != 0)
docuCall2[i-1] = docuCall[i];
}
logWriter.println("<---- end of javadoc command ---->");
logWriter.flush();
DocumentationTool docTool = ToolProvider.getSystemDocumentationTool();
if (docTool != null)
{
exitValue = docTool.run(null, logStream, logStream, docuCall2);
}
else{
Process docuRun = Runtime.getRuntime().exec(docuCall);
}
EchoThread outEcho = new EchoThread(docuRun.getInputStream(),
logStream);
EchoThread errEcho = new EchoThread(docuRun.getErrorStream(),
logStream);
outEcho.start();
errEcho.start();
try {
docuRun.waitFor();
outEcho.join();
errEcho.join();
exitValue = docuRun.exitValue();
}
catch (InterruptedException ie) {
logWriter.println("Interrupted while waiting for javadoc process to complete.");
}
}
}
final int finalExitValue = exitValue;
Platform.runLater(new Runnable() {
public void run()
{
if (finalExitValue == 0) {
BlueJEvent.raiseEvent(BlueJEvent.DOCU_GENERATED, null);
if (!showFile.exists()) {
Debug.message("showfile does not exist - searching");
showFile = FileUtility.findFile(showFile.getParentFile(),
showFile.getName());
}
if (openBrowser) {
SwingUtilities.invokeLater(() -> Utility.openWebBrowser(showFile.getPath()));
}
}
else {
BlueJEvent.raiseEvent(BlueJEvent.DOCU_ABORTED, null);
DialogManager.showMessageWithTextFX(null,
"doctool-error",
logFile.getPath());
}
}
});
}
catch (IOException exc) {
Platform.runLater(() -> DialogManager.showMessageFX(null, "severe-doc-trouble"));
}
finally {
if (logWriter != null) {
logWriter.close();
}
}
}
}
| A thread which reads from an InputStream and echoes everything read
| to an OutputStream.
|
private static class EchoThread
extends Thread
{
private InputStream readStream;
private OutputStream outStream;
@OnThread(Tag.Any)
public EchoThread(InputStream r,OutputStream out)
{
readStream = r;
outStream = out;
}
@Override
@OnThread(value = Tag.Worker, ignoreParent = true)
public void run()
{
try {
byte[] buf = new byte[1024];
int n;
while ((n = readStream.read(buf)) != -1) {
outStream.write(buf, 0, n);
}
}
catch(IOException e) {
e.printStackTrace();
}
}
}
| Construct a documentation generator instance for a project.
| @param project the project this generator belongs to.
|
public DocuGenerator(Project project)
{
this.project = project;
projectDir = project.getProjectDir();
projectDirPath = projectDir.getPath();
docDir = new File(projectDir, docDirName);
docDirPath = docDir.getPath();
}
| Generate documentation for the whole project. As this is done in
| a different process this method just returns whether the preconditions
| for the generation that are immediately testable are fulfilled.
|
| @return "" if the external process was started, an error message
|* otherwise.
*/
public String generateProjectDocu()
{
// test whether the documentation directory is accessible.
String docDirStatus = testDocDir();
|
|if (! docDirStatus.equals("")) {
return docDirStatus;
}
File startPage = new File(docDir, "index.html");
File logFile = new File(docDir, "logfile.txt");
if (documentationExists(logFile))
{
int result = DialogManager.askQuestionFX(null, "show-or-generate");
if (result == 0)
{ // show only
SwingUtilities.invokeLater(() -> Utility.openWebBrowser(startPage.getPath()));
return "";
}
if (result == 2)
{ // cancel
return "";
}
}
// tool-specific infos for javadoc
ArrayList<String> call = new ArrayList<String>();
call.add(docCommand);
call.add("-sourcepath");
call.add(projectDirPath);
addGeneralOptions(call);
call.add("-doctitle");
call.add(project.getProjectName());
call.add("-windowtitle");
call.add(project.getProjectName());
call.addAll(Utility.dequoteCommandLine(fixedJavadocParams));
// get the parameter that enables javadoc to link the generated
// documentation to the API documentation
|
|addLinkParam(call);
|
|// add the names of all the targets for the documentation tool.
|
|// first: get the names of all packages that contain java sources.
|
|List<String> packageNames = project.getPackageNames();
|
|for (Iterator<String> names = packageNames.iterator(); names.hasNext(); )
|
|{
|
|String packageName = names.next();
|
|// as javadoc doesn't like packages with no java-files, we have to
|
|// pass only names of packages that really contain java files.
|
|Package pack = project.getPackage(packageName);
|
|if (FileUtility.containsFile(pack.getPath(), "." + SourceType.Java.toString().toLowerCase()))
{
if (packageName.length() > 0)
{
call.add(packageName);
}
}
|
|}
|
|// second: get class names of classes in unnamed package, if any
|
|List<String> classNames = project.getPackage("").getAllClassnamesWithSource();
String dirName = project.getProjectDir().getAbsolutePath();
for (Iterator<String> names = classNames.iterator(); names.hasNext(); )
{
call.add(dirName + "/" + names.next() + "." + SourceType.Java.toString().toLowerCase());
}
String[] javadocCall = call.toArray(new String[0]);
removeStylesheet();
generateDoc(javadocCall, startPage, logFile, projectLogHeader, true);
|
|return "";
}
/**
* Generate documentation for the class in file 'filename'. The
* documentation is generated in a temporary directory. If the
* generation was successful the result will be displayed in a web browser.
| @param filename the fully qualified filename of the class to be
| documented.
| @return the path of the HTML file that will be generated
|
public void generateClassDocu(String filename)
{
if (docDir == null) {
BlueJEvent.raiseEvent(BlueJEvent.DOCU_ABORTED, null);
}
String docDirStatus = testDocDir();
if (! docDirStatus.equals("")) {
BlueJEvent.raiseEvent(BlueJEvent.DOCU_ABORTED, null);
}
ArrayList<String> call = new ArrayList<String>();
call.add(docCommand);
call.addAll(Utility.dequoteCommandLine(fixedJavadocParams));
call.addAll(Utility.dequoteCommandLine(tmpJavadocParams));
addGeneralOptions(call);
call.add(filename);
String[] javadocCall = call.toArray(new String[0]);
File htmlFile = new File(getDocuPath(filename));
File logFile = new File(docDir, "logfile.txt");
removeStylesheet();
generateDoc(javadocCall, htmlFile, logFile, classLogHeader, false);
}
| Removes the stylesheet (used before generating Javadoc). Needed because versions
| before 4.1.0 would generate a stylesheet.css file that was different, but Javadoc
| does not do content comparison so in 4.1.0+ will not by itself regenerate the Javadoc,
| even though it may needs to replace the pre-4.1.0 version. We just delete it every
| time to make sure the stylesheet.css is up-to-date.
|
private void removeStylesheet()
{
File stylesheet = new File(projectDir, "doc/stylesheet.css");
if (stylesheet.exists())
{
stylesheet.delete();
}
}
private void addGeneralOptions(List<String> call)
{
String majorVersion = System.getProperty("java.specification.version");
call.add("-source");
call.add(majorVersion);
call.add("-classpath");
call.add(project.getClassLoader().getClassPathAsString());
call.add("-d");
call.add(docDirPath);
call.add("-encoding");
call.add(project.getProjectCharset().name());
call.add("-charset");
call.add(project.getProjectCharset().name());
}
| For a given filename, return the path where the html documentation
| file for that file would be generated.
|
public String getDocuPath(String filename)
{
if (filename.startsWith(projectDirPath))
filename = filename.substring(projectDirPath.length());
if (filename.endsWith("." + SourceType.Java.toString().toLowerCase()))
filename = filename.substring(0, filename.indexOf("." + SourceType.Java.toString().toLowerCase()));
return docDirPath + filename + ".html";
}
| Test whether documentation directory exists in project dir and
| create it, if necessary.
| @return "" if directory exists and is accessible, an error message
|
* otherwise.
*/
private String testDocDir()
{
if (docDir.exists()) {
if (!docDir.isDirectory())
return DialogManager.getMessage("docdir-blocked-by-file");
}
else {
try {
if (!docDir.mkdir())
return DialogManager.getMessage("docdir-not-created");
}
catch (SecurityException exc) {
return DialogManager.getMessage("no-permission-for-docdir");
}
}
return "";
}
/**
* Test whether project documentation exists for this project.
* @param the logfile in the doc directory
*/
@OnThread(Tag.Any)
private static boolean documentationExists(File logFile)
|
|
{
|
|
if (!logFile.exists())
|
|
return false;
|
|
// test whether last doc generation was for project (not single class)
|
|
try
|
|
{
|
|
BufferedReader logReader = new BufferedReader(new FileReader(logFile));
|
|
String header = logReader.readLine();
|
|
logReader.close();
|
|
return (header == null? false : header.equals(projectLogHeader));
|
|
}
|
|
catch(Exception e)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
/**
| javadoc can link the generated documentation to existing documentation.
| This method constructs the javadoc parameter to set the link to the
| Java API. To make sure that javadoc is happy we test whether the file
| that javadoc needs (a list of all package names of the API) is
| accessible via the link provided in the BlueJ properties file.
| @return the link parameter if the link is working, "" otherwise.
|
*/
private void addLinkParam(List<String> params)
{
String linkToLib = Config.getPropString("doctool.linkToStandardLib");
if (linkToLib.equals("true")) {
String docURL = Config.getPropString("bluej.url.javaStdLib");
if (docURL.endsWith("index.html")) {
docURL = docURL.substring(0, docURL.length() - "index.html".length());
}
params.add("-link");
params.add(docURL);
}
}
}