/* -*- Mode: JDE; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
*
* This file is part of the debugger and core tools for the AspectJ(tm)
* programming language; see http://aspectj.org
*
* The contents of this file are subject to the Mozilla Public License
* Version 1.1 (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
* either http://www.mozilla.org/MPL/ or http://aspectj.org/MPL/.
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is AspectJ.
*
* The Initial Developer of the Original Code is Xerox Corporation. Portions
* created by Xerox Corporation are Copyright (C) 1999-2002 Xerox Corporation.
* All Rights Reserved.
*/
package org.aspectj.tools.ajdoc;
import org.aspectj.compiler.base.AbstractCompilerPass;
import org.aspectj.compiler.base.ErrorHandler;
import org.aspectj.compiler.base.ast.CompilationUnit;
import org.aspectj.compiler.base.ast.Dec;
import org.aspectj.compiler.base.ast.Decs;
import org.aspectj.compiler.base.ast.TypeDec;
import org.aspectj.compiler.base.ast.World;
import org.aspectj.compiler.crosscuts.AspectJCompiler;
import com.sun.javadoc.DocErrorReporter;
import com.sun.javadoc.RootDoc;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
/**
* Extension of the AspectJCompiler to provide
* functionality for creating documentation.
*
* @author Jeff Palm
*/
public class AjdocCompiler extends AspectJCompiler implements RootDocMaker {
/** The name of the program. */
protected final String programName;
/** The error printer we use. */
protected final ErrPrinter err;
/**
* Construct a new ajdoc compile with the
* error handler errorHandler
and
* name programName
*
* @param errorHandler the error handler.
* @param programName the name of the program.
*/
public AjdocCompiler(ErrorHandler errorHandler, String programName) {
super(errorHandler);
getOptions().preprocess = true;
getOptions().nocomments = true;
(errorHandler = err =
new ErrPrinter(this.programName = programName)).
setCompiler(this);
}
/**
* Construct a new ajdoc compile with the
* name programName
.
*
* @param programName the name of the program.
*/
public AjdocCompiler(String programName) {
this(null, programName);
}
/**
* Returns the ErrPrinter currently used.
*
* @return the ErrPrinter currently used.
*/
public ErrPrinter err() {
return err;
}
/** The packages found on the command line. */
private Set pkgnames = new HashSet();
/** The classes found on the command line and from files. */
private Set classnames = new HashSet();
/** The source files on the command line. */
private Set files = new HashSet();
/** The list of source files to compile. */
protected final List srcfiles = new ArrayList();
/** The list of filenames that came from user-specified source files. */
protected List srcSrcfilenames = new ArrayList();
/** The list of filenames that came from user-specified packages. */
protected List pkgSrcfilenames = new ArrayList();
/** The list of filenames that came from user-specified classes. */
protected List clsSrcfilenames = new ArrayList();
/** The source path with which to search. */
protected final List sourcepaths = new ArrayList();
{
sourcepaths.add(new File("."));
}
/** The list of filenames that came from user-specified classes. */
protected AccessChecker filter;
/**
* Create the RootDoc.
*/
public RootDoc makeRootDoc(String sourcepath,
String classpath,
String bootclasspath,
String extdirs,
long flags,
String encoding,
String locale,
String source,
List filenamesAndPackages,
List options,
DocErrorReporter err,
String programName,
AccessChecker filter)
throws CannotMakeRootDocException {
if ((null != filter) && (this.filter != filter)) {
this.filter = filter;
}
if (null == this.filter) {
this.filter = AccessChecker.PROTECTED;
}
if (classpath != null) {
getOptions().classpath = classpath;
}
if (bootclasspath != null) {
getOptions().bootclasspath = bootclasspath;
}
if (extdirs != null) {
getOptions().extdirs = extdirs;
}
if (source != null) {
getOptions().source = source;
}
resolveSourcePath(sourcepath);
resolveFilesAndPackages(filenamesAndPackages);
Collections.sort(pkgSrcfilenames);
Collections.sort(clsSrcfilenames);
Collections.sort(srcSrcfilenames);
srcfiles.addAll(pkgSrcfilenames);
srcfiles.addAll(clsSrcfilenames);
srcfiles.addAll(srcSrcfilenames);
err().notice("starting_internal_compile");
for (Iterator i = options.iterator(); i.hasNext();) {
String[] opts = (String[])i.next();
if (opts.length == 1) {
if (opts[0].equals("-verbose")) {
getOptions().verbose = true;
}
} else if (opts.length == 2) {
if (opts[0].equals("-classpath")) {
getOptions().classpath = opts[1];
} else if (opts[1].equals("-bootclasspath")) {
getOptions().bootclasspath = opts[1];
} else if (opts[1].equals("-extdirs")) {
getOptions().extdirs = opts[1];
}
}
}
// Compile the srcfiles - have to add passes first
addPasses();
internalCompile(srcfiles);
// This is the world with which we create the root
World world = getWorld();
// Add all the classes found in the source files
// to the list of specified classnames
for (Iterator i = world.getCompilationUnits().iterator();
i.hasNext();) {
Decs decs = ((CompilationUnit)i.next()).getDecs();
for (int j = 0, N = decs.size(); j < N; j++) {
Dec dec = decs.get(j);
if (dec instanceof TypeDec) {
classnames.add(((TypeDec)dec).getFullName());
}
}
}
// Initialize and return the root created
// from the our world
err().notice("creating_root");
RootDoc result = init(this, (String[][])options.toArray
(new String[options.size()][]));
// do another pass at RootDoc after constructed
com.sun.javadoc.ClassDoc[] cds = result.classes();
for (int i = 0; i < cds.length; i++) {
if (cds[i] instanceof ClassDocImpl) {
ClassDocImpl cd = (ClassDocImpl) cds[i];
cd.postProcess();
}
}
return result;
}
private static AjdocCompiler instance;
{ instance = this; }
public static AjdocCompiler instance() {
return instance;
}
/**
* The entry point to initialize a world created
* from an AspectJCompiler.
*
* @param ajc the compiler.
* @param options the ajdoc options.
* @return a RootDocImpl representing the
* documentation tree.
*/
public static RootDocImpl init(AspectJCompiler ajc, String[][] options) {
if (ajc == null) return null; //TODO: make empty
World world = ajc.getWorld();
Collection classnames = null;
Collection pkgnames = null;
if (ajc instanceof AjdocCompiler) {
AjdocCompiler ajdoc = (AjdocCompiler)ajc;
pkgnames = ajdoc.pkgnames;
classnames = ajdoc.classnames;
}
PackageDocImpl.init(ajc);
AccessChecker filter = AccessChecker.PUBLIC;
if (ajc instanceof AjdocCompiler) {
filter = ((AjdocCompiler) ajc).getFilter();
}
RootDocImpl root = new RootDocImpl(world,
options,
pkgnames,
classnames,
filter);
return root;
}
/** set filter associated with this compiler */
protected void setFilter(AccessChecker filter, String arg) {
this.filter = filter;
}
/** get filter associated with this compiler */
public final AccessChecker getFilter() {
return filter;
}
protected final void expandAtFile(String filename,
List args) throws IOException {
BufferedReader in = new BufferedReader(new FileReader(filename));
String dirfile = new File(filename).getParent();
File basedir = new File(null == dirfile ? "." : dirfile ) ;
String line;
while ((line = in.readLine()) != null) {
if (line == null || line.length() < 1) continue;
line = line.trim();
if (line.startsWith("//")) continue;
if (line.startsWith("#")) continue;
if (line.startsWith("@")) {
line = line.substring(1);
File newfile = new File(line);
newfile = newfile.isAbsolute() ?
newfile : new File(basedir, line);
expandAtFile(newfile.getPath(), args);
} else {
File newfile = new File(line);
newfile = newfile.isAbsolute() ?
newfile : new File(basedir, line);
if (newfile.exists()) {
boolean result = maybeAdd(newfile, args);
if (!result) {
// we only support valid filenames, not options
cantResolve(newfile);
}
} else {
boolean addedFile = false;
FileFilter filter = null;
String name = newfile.getName().trim();
if (name.equals("*.java")) {
filter = new FileFilter() {
public boolean accept(File f) {
return f != null &&
f.getName().endsWith(".java");
}
};
} else if (name.equals("*.aj")) {
filter = new FileFilter() {
public boolean accept(File f) {
return f != null &&
f.getName().endsWith(".java");
}
};
} else if (name.equals("*")) {
filter = new FileFilter() {
public boolean accept(File f) {
return f != null &&
(f.getName().endsWith(".java")
|| f.getName().endsWith(".aj"));
}
};
}
if (null != filter) {
File parentDir = newfile.getParentFile();
File[] javafiles = parentDir.listFiles(filter);
if (javafiles != null) {
for (int i = 0; i < javafiles.length; i++) {
if (maybeAdd(javafiles[i], args)) {
if (!addedFile) addedFile = true;
} else {
cantResolve(javafiles[i]);
}
}
}
}
if (!addedFile) {
if (isValidPkg(line)) {
args.add(line);
} else {
cantResolve(newfile);
}
}
}
}
}
in.close();
}
protected final void cantResolve(File f) {
err().error("cant_resolve_file", f.getAbsolutePath());
}
private void resolveSourcePath(String sourcepath) {
if (sourcepath != null) {
sourcepaths.remove(0);
for (StringTokenizer t = new StringTokenizer(sourcepath,
File.pathSeparator);
t.hasMoreTokens();) {
File path = new File(t.nextToken().trim());
if (path.exists() && path.isDirectory()) {
sourcepaths.add(path);
}
}
// TODO: don't want this, I think ????
//sourcepaths.add(new File("."));
}
}
private void resolveFilesAndPackages(List filenamesAndPackages) {
Collection pkgnamesFromCmd = new HashSet();
for (Iterator i = filenamesAndPackages.iterator(); i.hasNext();) {
String str = (String)i.next();
File file = new File(str);
if (/*file.isAbsolute() &&*/ maybeAdd(file, srcSrcfilenames)) {
addFile(file);
continue;
} else {
for (Iterator j = sourcepaths.iterator(); j.hasNext();) {
File sourcepath = (File)j.next();
file = new File(sourcepath, str);
if (maybeAdd(file, srcSrcfilenames)) {
addFile(file);
continue;
}
}
}
pkgnamesFromCmd.add(str);
}
for (Iterator i = pkgnamesFromCmd.iterator(); i.hasNext();) {
resolvePackageOrClass((String)i.next());
}
}
private void resolvePackageOrClass(String pkgOrClassName) {
boolean recurse;
String pkgOrClass =
(recurse = (pkgOrClassName.endsWith(".*"))) ?
pkgOrClassName.substring(0, pkgOrClassName.length()-2) :
pkgOrClassName;
for (Iterator i = sourcepaths.iterator(); i.hasNext();) {
File sourcepath = (File)i.next();
File possiblePkg = new File(sourcepath,
pkgOrClass.replace
('.', File.separatorChar));
if (possiblePkg.exists() && possiblePkg.isDirectory()) {
if (recurse) {
File[] dirs = possiblePkg.listFiles
(new FileFilter() {
public boolean accept(File f) {
return f != null && f.isDirectory();
}
});
for (int j = 0; j < dirs.length; j++) {
String pkgname = pkgOrClass + '.' + dirs[j].getName();
resolvePackageOrClass(pkgname + ".*");
}
}
File[] javafiles = possiblePkg.listFiles
(new FileFilter() {
public boolean accept(File f) {
return f != null && !f.isDirectory();
}
});
if (javafiles.length > 0) {
pkgnames.add(pkgOrClass);
}
boolean addedPkg = false;
for (int j = 0; j < javafiles.length; j++) {
if (maybeAdd(javafiles[j], pkgSrcfilenames) && !addedPkg) {
addPkg(pkgOrClass, javafiles[j]);
addedPkg = true;
}
}
break;
} else {
String pkgname = "";
String classname = pkgOrClass;
int ilastdot = pkgOrClass.lastIndexOf('.');
if (ilastdot != -1) {
pkgname = pkgOrClass.substring(0, ilastdot).
replace('.', File.separatorChar) + File.separatorChar;
classname = pkgOrClass.substring(ilastdot+1);
}
File file = new File(sourcepath,
pkgname + classname + ".java");
if (maybeAdd(file, clsSrcfilenames)) {
addClass(pkgOrClass, file);
break;
}
}
}
}
protected final File findFile(String filename, boolean isDir) {
for (Iterator i = sourcepaths.iterator(); i.hasNext();) {
File sourcepath = (File)i.next();
File file = new File(sourcepath, filename);
if (file.exists() && !(isDir ^ file.isDirectory())) {
return file;
}
}
return null;
}
protected static boolean maybeAddPkg(String pkgname,
Collection pkgnames) {
if (isValidPkg(pkgname)) {
pkgnames.add(pkgname);
return true;
}
return false;
}
protected final Map filesToClassnames = new HashMap();
protected final void addClass(String classname, File file) {
if (!(maybeAddClass(classname))) {
err().error("invalid_class_name", classname);
} else {
filesToClassnames.put(file.getAbsoluteFile(), classname);
}
}
protected final boolean maybeAddClass(String classname) {
return maybeAddClass(classname, classnames);
}
protected static boolean maybeAddClass(String classname,
Collection classnames) {
if (isValidClass(classname)) {
classnames.add(classname);
return true;
}
return false;
}
protected final static boolean isValidClass(String classname) {
return isValidPkg(classname);
}
protected final Map filesToPkgnames = new HashMap();
protected final void addPkg(String pkgname, File file) {
if (!maybeAddPkg(pkgname)) {
err().error("invalid_package_name", pkgname);
} else {
filesToPkgnames.put(file.getAbsoluteFile(), pkgname);
}
}
protected final boolean maybeAddPkg(String pkgname) {
return maybeAddPkg(pkgname, pkgnames);
}
protected final Map filesToFilenames = new HashMap();
protected final void addFile(File file) {
files.add(file);
filesToFilenames.put(file.getAbsoluteFile(), file.getAbsolutePath());
}
protected static boolean maybeAdd(File file, Collection files) {
if (isValidJavaFile(file)) {
files.add(file.getAbsolutePath());
return true;
}
return false;
}
protected final static boolean isValidJavaFile(File file) {
return file != null && file.exists() && !file.isDirectory()
&& (file.getName().endsWith(".java")
|| file.getName().endsWith(".aj")) ;
}
protected final static boolean isValidPkg(String pkgname) {
if (pkgname == null) {
return false;
}
if (pkgname.length() < 1) {
return true;
}
if (!Character.isJavaIdentifierStart(pkgname.charAt(0))) {
return false;
}
for (int i = 1; i < pkgname.length(); i++) {
char c = pkgname.charAt(i);
if (c == '.' && i == pkgname.length()-1) {
return false;
}
if (!(c == '.' || Character.isJavaIdentifierPart(c))) {
return false;
}
}
return true;
}
protected void loading(CompilationUnit cu) {
File srcfile = cu.getSourceFile().getAbsoluteFile();
String pkgname, classname, filename;
if ((pkgname = (String)filesToPkgnames.get(srcfile))!= null) {
AjdocCompiler.this.err().notice
("Loading_source_files_for_package", pkgname);
} else if ((classname = (String)filesToClassnames.get(srcfile)) != null) {
AjdocCompiler.this.err().notice
("Loading_source_file_for_class", classname);
} else if ((filename = (String)filesToFilenames.get(srcfile)) != null) {
AjdocCompiler.this.err().notice
("Loading_source_file", filename);
}
}
protected AbstractCompilerPass createParserPass() {
return new PrintingParserPass(this);
}
protected static class PrintingParserPass extends AspectJCompiler.ParserPass {
public PrintingParserPass(AjdocCompiler jc) { super(jc); }
public void transform(CompilationUnit cu) {
((AjdocCompiler)getCompiler()).loading(cu);
super.transform(cu);
}
}
protected void addPasses() {
passes = new ArrayList();
addPreSymbolPasses();
}
}