/*
* Copyright 2008-2009 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
* CA 95054 USA or visit www.sun.com if you need additional information or
* have any questions.
*/
package org.visage.tools.visagedoc;
import java.io.*;
import java.util.Collection;
import com.sun.tools.mjavac.code.Symbol.*;
import com.sun.tools.mjavac.parser.DocCommentScanner;
import com.sun.tools.mjavac.util.Paths;
import org.visage.tools.tree.*;
import com.sun.tools.mjavac.util.*;
import org.visage.tools.code.VisageSymtab;
import org.visage.tools.code.VisageTypes;
import org.visage.tools.util.VisageFileManager;
import org.visage.tools.comp.VisageClassReader;
/**
* This class could be the main entry point for Visagedoc when Visagedoc is
* used as a component in a larger software system. It provides operations to
* construct a new javadoc processor, and to run it on a set of source
* files.
* @author Neal Gafter, Javadoc source
* @author Tom Ball, Visagedoc port
*/
public class VisagedocTool extends org.visage.tools.main.VisageCompiler {
DocEnv docenv;
final Context ctx;
final Messager messager;
final VisageClassReader clsreader;
final VisagedocEnter jdenter;
private final Paths paths;
/**
* Construct a new JavaCompiler processor, using appropriately
* extended phases of the underlying compiler.
*/
protected VisagedocTool(Context context) {
super(context);
this.ctx = context;
messager = Messager.instance0(context);
clsreader = VisageClassReader.instance(context);
jdenter = VisagedocEnter.instance0(context);
paths = Paths.instance(context);
}
@Override
protected void registerServices(final Context context) {
Messager.instance0(context);
}
/**
* For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
*/
@Override
protected boolean keepComments() {
return true;
}
/**
* Construct a new javadoc tool.
*/
public static VisagedocTool make0(Context context) {
try {
// Because of circularities we need to register these services
// before we allocate VisageClassReader, which needs to be done
// before we allocate VisagedocClassReader, which needs to be
// done before we allocate VisagedocTool. Hence we do all this
// stuff here rather than in registerServices. Sigh.
VisageFileManager.preRegister(context);
VisagedocEnter.preRegister(context);
VisagedocMemberEnter.preRegister(context);
VisageSymtab.preRegister(context);
JavadocTodo.preRegister(context);
VisageTypes.preRegister(context);
DocCommentScanner.Factory.preRegister(context);
VisageClassReader reader = VisageClassReader.instance(context);
VisagedocClassReader jclsreader = new VisagedocClassReader(context);
reader.jreader = jclsreader;
return new VisagedocTool(context);
} catch (CompletionFailure ex) {
Messager messager = Messager.instance0(context);
if (messager != null)
messager.error(Position.NOPOS, ex.getMessage());
return null;
}
}
public RootDocImpl getRootDocImpl(String doclocale,
String encoding,
ModifierFilter filter,
List<String> javaNames,
List<String[]> options,
boolean breakiterator,
List<String> subPackages,
List<String> excludedPackages,
boolean docClasses,
boolean legacyDoclet,
boolean quiet) throws IOException {
docenv = DocEnv.instance(ctx);
docenv.showAccess = filter;
docenv.quiet = quiet;
docenv.breakiterator = breakiterator;
docenv.setLocale(doclocale);
docenv.setEncoding(encoding);
docenv.docClasses = docClasses;
docenv.legacyDoclet = legacyDoclet;
clsreader.sourceCompleter = docClasses ? null : this;
ListBuffer<String> filenames = new ListBuffer<String>();
ListBuffer<VisageScript> classTrees = new ListBuffer<VisageScript>();
ListBuffer<VisageScript> packTrees = new ListBuffer<VisageScript>();
try {
for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
String name = it.head;
if (!docClasses && name.endsWith(".visage") && new File(name).exists()) {
docenv.notice("main.Loading_source_file", name);
VisageScript tree = parse(name);
classTrees.append(tree);
} else if (isValidPackageName(name)) {
filenames = filenames.append(name);
} else if (name.endsWith(".visage")) {
docenv.error(null, "main.file_not_found", name);
} else {
docenv.error(null, "main.illegal_package_name", name);
}
}
if (!docClasses) {
// Recursively search given subpackages. If any packages
//are found, add them to the list.
searchSubPackages(subPackages, filenames, excludedPackages);
// Parse the packages
for (List<String> packs = filenames.toList(); packs.nonEmpty(); packs = packs.tail) {
// Parse sources ostensibly belonging to package.
parsePackageClasses(packs.head, packTrees, excludedPackages);
}
if (messager.nerrors() != 0) return null;
// Enter symbols for all files
docenv.notice("main.Building_tree");
enterTrees(classTrees.toList().appendList(packTrees.toList()));
}
} catch (Abort ex) {}
if (messager.nerrors() != 0) return null;
if (docClasses)
return new RootDocImpl(docenv, javaNames, options);
else
return new RootDocImpl(docenv, listClasses(classTrees.toList()), filenames.toList(), options);
}
/** Is the given string a valid package name? */
boolean isValidPackageName(String s) {
int index;
while ((index = s.indexOf('.')) != -1) {
if (!isValidClassName(s.substring(0, index))) return false;
s = s.substring(index+1);
}
return isValidClassName(s);
}
/**
* search all directories in path for subdirectory name. Add all
* .java files found in such a directory to args.
*/
private void parsePackageClasses(String name,
ListBuffer<VisageScript> trees,
List<String> excludedPackages)
throws IOException {
if (excludedPackages.contains(name)) {
return;
}
boolean hasFiles = false;
docenv.notice("main.Loading_source_files_for_package", name);
name = name.replace('.', File.separatorChar);
for (File pathname : paths.sourceSearchPath()) {
File f = new File(pathname, name);
String filenames[] = f.list();
// if names not null, then found directory with source files
if (filenames != null) {
String dir = f.getAbsolutePath();
if (!dir.endsWith(File.separator))
dir = dir + File.separator;
for (int j = 0; j < filenames.length; j++) {
if (isValidVisageSourceFile(filenames[j])) {
String fn = dir + filenames[j];
// messager.notice("main.Loading_source_file", fn);
trees.append(parse(fn));
hasFiles = true;
}
}
}
}
if (!hasFiles)
messager.warning("main.no_source_files_for_package",
name.replace(File.separatorChar, '.'));
}
/**
* Recursively search all directories in path for subdirectory name.
* Add all packages found in such a directory to packages list.
*/
private void searchSubPackages(List<String> subPackages,
ListBuffer<String> packages,
List<String> excludedPackages) {
// FIXME: This search path is bogus.
// Only the effective source path should be searched for sources.
// Only the effective class path should be searched for classes.
// Should the bootclasspath/extdirs also be searched for classes?
java.util.List<File> pathnames = new java.util.ArrayList<File>();
if (paths.sourcePath() != null)
for (File elt : paths.sourcePath())
pathnames.add(elt);
for (File elt : paths.userClassPath())
pathnames.add(elt);
for (String subPackage : subPackages)
searchSubPackage(subPackage, packages, excludedPackages, pathnames);
}
/**
* Recursively search all directories in path for subdirectory name.
* Add all packages found in such a directory to packages list.
*/
private void searchSubPackage(String packageName,
ListBuffer<String> packages,
List<String> excludedPackages,
Collection<File> pathnames) {
if (excludedPackages.contains(packageName))
return;
String packageFilename = packageName.replace('.', File.separatorChar);
boolean addedPackage = false;
for (File pathname : pathnames) {
File f = new File(pathname, packageFilename);
String filenames[] = f.list();
// if filenames not null, then found directory
if (filenames != null) {
for (String filename : filenames) {
if (!addedPackage
&& (isValidVisageSourceFile(filename) ||
isValidClassFile(filename))
&& !packages.contains(packageName)) {
packages.append(packageName);
addedPackage = true;
} else if (isValidClassName(filename) &&
(new File(f, filename)).isDirectory()) {
searchSubPackage(packageName + "." + filename,
packages, excludedPackages, pathnames);
}
}
}
}
}
/**
* Return true if given file name is a valid class file name.
* @param file the name of the file to check.
* @return true if given file name is a valid class file name
* and false otherwise.
*/
private static boolean isValidClassFile(String file) {
if (!file.endsWith(".class")) return false;
String clazzName = file.substring(0, file.length() - ".class".length());
return isValidClassName(clazzName);
}
/**
* Return true if given file name is a valid Java source file name.
* @param file the name of the file to check.
* @return true if given file name is a valid Java source file name
* and false otherwise.
*/
private static boolean isValidVisageSourceFile(String file) {
if (!file.endsWith(".visage")) return false;
String clazzName = file.substring(0, file.length() - ".visage".length());
return isValidClassName(clazzName);
}
/** Are surrogates supported?
*/
final static boolean surrogatesSupported = surrogatesSupported();
private static boolean surrogatesSupported() {
try {
Character.isHighSurrogate('a');
return true;
} catch (NoSuchMethodError ex) {
return false;
}
}
/**
* Return true if given file name is a valid class name
* (including "package-info").
* @param clazzname the name of the class to check.
* @return true if given class name is a valid class name
* and false otherwise.
*/
public static boolean isValidClassName(String s) {
if (s.length() < 1) return false;
if (s.equals("package-info")) return true;
if (surrogatesSupported) {
int cp = s.codePointAt(0);
if (!Character.isJavaIdentifierStart(cp))
return false;
for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
cp = s.codePointAt(j);
if (!Character.isJavaIdentifierPart(cp))
return false;
}
} else {
if (!Character.isJavaIdentifierStart(s.charAt(0)))
return false;
for (int j=1; j<s.length(); j++)
if (!Character.isJavaIdentifierPart(s.charAt(j)))
return false;
}
return true;
}
/**
* From a list of top level trees, return the list of contained class definitions
*/
List<VisageClassDeclaration> listClasses(List<VisageScript> trees) {
ListBuffer<VisageClassDeclaration> result = new ListBuffer<VisageClassDeclaration>();
for (VisageScript t : trees) {
for (VisageTree def : t.defs) {
if (def instanceof VisageClassDeclaration)
result.append((VisageClassDeclaration)def);
}
}
return result.toList();
}
private VisageScript parse(String filename) throws IOException {
JavacFileManager fm = (JavacFileManager)fileManager;
return parse(fm.getJavaFileObjectsFromStrings(List.of(filename)).iterator().next());
}
}