1 /*
   2  * Copyright (c) 2001, 2011, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.tools.javadoc;
  27 
  28 import java.io.File;
  29 import java.io.IOException;
  30 import java.util.Collection;
  31 import java.util.EnumSet;
  32 import java.util.HashMap;
  33 import java.util.Map;
  34 import java.util.Set;
  35 import javax.tools.JavaFileManager.Location;
  36 import javax.tools.JavaFileObject;
  37 import javax.tools.StandardJavaFileManager;
  38 import javax.tools.StandardLocation;
  39 
  40 import com.sun.tools.javac.code.Symbol.CompletionFailure;
  41 import com.sun.tools.javac.comp.Annotate;
  42 import com.sun.tools.javac.tree.JCTree;
  43 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
  44 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
  45 import com.sun.tools.javac.util.Abort;
  46 import com.sun.tools.javac.util.Context;
  47 import com.sun.tools.javac.util.List;
  48 import com.sun.tools.javac.util.ListBuffer;
  49 import com.sun.tools.javac.util.Position;
  50 
  51 
  52 /**
  53  *  This class could be the main entry point for Javadoc when Javadoc is used as a
  54  *  component in a larger software system. It provides operations to
  55  *  construct a new javadoc processor, and to run it on a set of source
  56  *  files.
  57  *  @author Neal Gafter
  58  */
  59 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
  60     DocEnv docenv;
  61 
  62     final Context context;
  63     final Messager messager;
  64     final JavadocClassReader reader;
  65     final JavadocEnter enter;
  66     final Annotate annotate;
  67 
  68     /**
  69      * Construct a new JavaCompiler processor, using appropriately
  70      * extended phases of the underlying compiler.
  71      */
  72     protected JavadocTool(Context context) {
  73         super(context);
  74         this.context = context;
  75         messager = Messager.instance0(context);
  76         reader = JavadocClassReader.instance0(context);
  77         enter = JavadocEnter.instance0(context);
  78         annotate = Annotate.instance(context);
  79     }
  80 
  81     /**
  82      * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
  83      */
  84     protected boolean keepComments() {
  85         return true;
  86     }
  87 
  88     /**
  89      *  Construct a new javadoc tool.
  90      */
  91     public static JavadocTool make0(Context context) {
  92         Messager messager = null;
  93         try {
  94             // force the use of Javadoc's class reader
  95             JavadocClassReader.preRegister(context);
  96 
  97             // force the use of Javadoc's own enter phase
  98             JavadocEnter.preRegister(context);
  99 
 100             // force the use of Javadoc's own member enter phase
 101             JavadocMemberEnter.preRegister(context);
 102 
 103             // force the use of Javadoc's own todo phase
 104             JavadocTodo.preRegister(context);
 105 
 106             // force the use of Messager as a Log
 107             messager = Messager.instance0(context);
 108 
 109             return new JavadocTool(context);
 110         } catch (CompletionFailure ex) {
 111             messager.error(Position.NOPOS, ex.getMessage());
 112             return null;
 113         }
 114     }
 115 
 116     public RootDocImpl getRootDocImpl(String doclocale,
 117                                       String encoding,
 118                                       ModifierFilter filter,
 119                                       List<String> javaNames,
 120                                       List<String[]> options,
 121                                       boolean breakiterator,
 122                                       List<String> subPackages,
 123                                       List<String> excludedPackages,
 124                                       boolean docClasses,
 125                                       boolean legacyDoclet,
 126                       boolean quiet) throws IOException {
 127         docenv = DocEnv.instance(context);
 128         docenv.showAccess = filter;
 129         docenv.quiet = quiet;
 130         docenv.breakiterator = breakiterator;
 131         docenv.setLocale(doclocale);
 132         docenv.setEncoding(encoding);
 133         docenv.docClasses = docClasses;
 134         docenv.legacyDoclet = legacyDoclet;
 135         reader.sourceCompleter = docClasses ? null : this;
 136 
 137         ListBuffer<String> names = new ListBuffer<String>();
 138         ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<JCCompilationUnit>();
 139         ListBuffer<JCCompilationUnit> packTrees = new ListBuffer<JCCompilationUnit>();
 140 
 141         try {
 142             StandardJavaFileManager fm = (StandardJavaFileManager) docenv.fileManager;
 143             for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
 144                 String name = it.head;
 145                 if (!docClasses && name.endsWith(".java") && new File(name).exists()) {
 146                     JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next();
 147                     docenv.notice("main.Loading_source_file", name);
 148                     JCCompilationUnit tree = parse(fo);
 149                     classTrees.append(tree);
 150                 } else if (isValidPackageName(name)) {
 151                     names = names.append(name);
 152                 } else if (name.endsWith(".java")) {
 153                     docenv.error(null, "main.file_not_found", name);
 154                 } else {
 155                     docenv.error(null, "main.illegal_package_name", name);
 156                 }
 157             }
 158 
 159             if (!docClasses) {
 160                 // Recursively search given subpackages.  If any packages
 161                 //are found, add them to the list.
 162                 Map<String,List<JavaFileObject>> packageFiles =
 163                         searchSubPackages(subPackages, names, excludedPackages);
 164 
 165                 // Parse the packages
 166                 for (List<String> packs = names.toList(); packs.nonEmpty(); packs = packs.tail) {
 167                     // Parse sources ostensibly belonging to package.
 168                     String packageName = packs.head;
 169                     parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages);
 170                 }
 171 
 172                 if (messager.nerrors() != 0) return null;
 173 
 174                 // Enter symbols for all files
 175                 docenv.notice("main.Building_tree");
 176                 enter.main(classTrees.toList().appendList(packTrees.toList()));
 177             }
 178         } catch (Abort ex) {}
 179 
 180         if (messager.nerrors() != 0)
 181             return null;
 182 
 183         if (docClasses)
 184             return new RootDocImpl(docenv, javaNames, options);
 185         else
 186             return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options);
 187     }
 188 
 189     /** Is the given string a valid package name? */
 190     boolean isValidPackageName(String s) {
 191         int index;
 192         while ((index = s.indexOf('.')) != -1) {
 193             if (!isValidClassName(s.substring(0, index))) return false;
 194             s = s.substring(index+1);
 195         }
 196         return isValidClassName(s);
 197     }
 198 
 199     /**
 200      * search all directories in path for subdirectory name. Add all
 201      * .java files found in such a directory to args.
 202      */
 203     private void parsePackageClasses(String name,
 204             Iterable<JavaFileObject> files,
 205             ListBuffer<JCCompilationUnit> trees,
 206             List<String> excludedPackages)
 207             throws IOException {
 208         if (excludedPackages.contains(name)) {
 209             return;
 210         }
 211 
 212         boolean hasFiles = false;
 213         docenv.notice("main.Loading_source_files_for_package", name);
 214 
 215         if (files == null) {
 216             Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
 217                     ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
 218             ListBuffer<JavaFileObject> lb = new ListBuffer<JavaFileObject>();
 219             for (JavaFileObject fo: docenv.fileManager.list(
 220                     location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) {
 221                 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
 222                 String simpleName = getSimpleName(binaryName);
 223                 if (isValidClassName(simpleName)) {
 224                     lb.append(fo);
 225                 }
 226             }
 227             files = lb.toList();
 228         }
 229 
 230         for (JavaFileObject fo : files) {
 231             // messager.notice("main.Loading_source_file", fn);
 232             trees.append(parse(fo));
 233             hasFiles = true;
 234         }
 235 
 236         if (!hasFiles) {
 237             messager.warning(null, "main.no_source_files_for_package",
 238                     name.replace(File.separatorChar, '.'));
 239         }
 240     }
 241 
 242     /**
 243      * Recursively search all directories in path for subdirectory name.
 244      * Add all packages found in such a directory to packages list.
 245      */
 246     private Map<String,List<JavaFileObject>> searchSubPackages(
 247             List<String> subPackages,
 248             ListBuffer<String> packages,
 249             List<String> excludedPackages)
 250             throws IOException {
 251         Map<String,List<JavaFileObject>> packageFiles =
 252                 new HashMap<String,List<JavaFileObject>>();
 253 
 254         Map<String,Boolean> includedPackages = new HashMap<String,Boolean>();
 255         includedPackages.put("", true);
 256         for (String p: excludedPackages)
 257             includedPackages.put(p, false);
 258 
 259         StandardLocation path = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
 260                 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
 261 
 262         searchSubPackages(subPackages,
 263                 includedPackages,
 264                 packages, packageFiles,
 265                 path,
 266                 EnumSet.of(JavaFileObject.Kind.SOURCE));
 267 
 268         return packageFiles;
 269     }
 270 
 271     private void searchSubPackages(List<String> subPackages,
 272             Map<String,Boolean> includedPackages,
 273             ListBuffer<String> packages,
 274             Map<String, List<JavaFileObject>> packageFiles,
 275             StandardLocation location, Set<JavaFileObject.Kind> kinds)
 276             throws IOException {
 277         for (String subPackage: subPackages) {
 278             if (!isIncluded(subPackage, includedPackages))
 279                 continue;
 280 
 281             for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) {
 282                 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
 283                 String packageName = getPackageName(binaryName);
 284                 String simpleName = getSimpleName(binaryName);
 285                 if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) {
 286                     List<JavaFileObject> list = packageFiles.get(packageName);
 287                     list = (list == null ? List.of(fo) : list.prepend(fo));
 288                     packageFiles.put(packageName, list);
 289                     if (!packages.contains(packageName))
 290                         packages.add(packageName);
 291                 }
 292             }
 293         }
 294     }
 295 
 296     private String getPackageName(String name) {
 297         int lastDot = name.lastIndexOf(".");
 298         return (lastDot == -1 ? "" : name.substring(0, lastDot));
 299     }
 300 
 301     private String getSimpleName(String name) {
 302         int lastDot = name.lastIndexOf(".");
 303         return (lastDot == -1 ? name : name.substring(lastDot + 1));
 304     }
 305 
 306     private boolean isIncluded(String packageName, Map<String,Boolean> includedPackages) {
 307         Boolean b = includedPackages.get(packageName);
 308         if (b == null) {
 309             b = isIncluded(getPackageName(packageName), includedPackages);
 310             includedPackages.put(packageName, b);
 311         }
 312         return b;
 313     }
 314 
 315     /**
 316      * Recursively search all directories in path for subdirectory name.
 317      * Add all packages found in such a directory to packages list.
 318      */
 319     private void searchSubPackage(String packageName,
 320                                   ListBuffer<String> packages,
 321                                   List<String> excludedPackages,
 322                                   Collection<File> pathnames) {
 323         if (excludedPackages.contains(packageName))
 324             return;
 325 
 326         String packageFilename = packageName.replace('.', File.separatorChar);
 327         boolean addedPackage = false;
 328         for (File pathname : pathnames) {
 329             File f = new File(pathname, packageFilename);
 330             String filenames[] = f.list();
 331             // if filenames not null, then found directory
 332             if (filenames != null) {
 333                 for (String filename : filenames) {
 334                     if (!addedPackage
 335                             && (isValidJavaSourceFile(filename) ||
 336                                 isValidJavaClassFile(filename))
 337                             && !packages.contains(packageName)) {
 338                         packages.append(packageName);
 339                         addedPackage = true;
 340                     } else if (isValidClassName(filename) &&
 341                                (new File(f, filename)).isDirectory()) {
 342                         searchSubPackage(packageName + "." + filename,
 343                                          packages, excludedPackages, pathnames);
 344                     }
 345                 }
 346             }
 347         }
 348     }
 349 
 350     /**
 351      * Return true if given file name is a valid class file name.
 352      * @param file the name of the file to check.
 353      * @return true if given file name is a valid class file name
 354      * and false otherwise.
 355      */
 356     private static boolean isValidJavaClassFile(String file) {
 357         if (!file.endsWith(".class")) return false;
 358         String clazzName = file.substring(0, file.length() - ".class".length());
 359         return isValidClassName(clazzName);
 360     }
 361 
 362     /**
 363      * Return true if given file name is a valid Java source file name.
 364      * @param file the name of the file to check.
 365      * @return true if given file name is a valid Java source file name
 366      * and false otherwise.
 367      */
 368     private static boolean isValidJavaSourceFile(String file) {
 369         if (!file.endsWith(".java")) return false;
 370         String clazzName = file.substring(0, file.length() - ".java".length());
 371         return isValidClassName(clazzName);
 372     }
 373 
 374     /** Are surrogates supported?
 375      */
 376     final static boolean surrogatesSupported = surrogatesSupported();
 377     private static boolean surrogatesSupported() {
 378         try {
 379             boolean b = Character.isHighSurrogate('a');
 380             return true;
 381         } catch (NoSuchMethodError ex) {
 382             return false;
 383         }
 384     }
 385 
 386     /**
 387      * Return true if given file name is a valid class name
 388      * (including "package-info").
 389      * @param clazzname the name of the class to check.
 390      * @return true if given class name is a valid class name
 391      * and false otherwise.
 392      */
 393     public static boolean isValidClassName(String s) {
 394         if (s.length() < 1) return false;
 395         if (s.equals("package-info")) return true;
 396         if (surrogatesSupported) {
 397             int cp = s.codePointAt(0);
 398             if (!Character.isJavaIdentifierStart(cp))
 399                 return false;
 400             for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
 401                 cp = s.codePointAt(j);
 402                 if (!Character.isJavaIdentifierPart(cp))
 403                     return false;
 404             }
 405         } else {
 406             if (!Character.isJavaIdentifierStart(s.charAt(0)))
 407                 return false;
 408             for (int j=1; j<s.length(); j++)
 409                 if (!Character.isJavaIdentifierPart(s.charAt(j)))
 410                     return false;
 411         }
 412         return true;
 413     }
 414 
 415     /**
 416      * From a list of top level trees, return the list of contained class definitions
 417      */
 418     List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
 419         ListBuffer<JCClassDecl> result = new ListBuffer<JCClassDecl>();
 420         for (JCCompilationUnit t : trees) {
 421             for (JCTree def : t.defs) {
 422                 if (def.hasTag(JCTree.Tag.CLASSDEF))
 423                     result.append((JCClassDecl)def);
 424             }
 425         }
 426         return result.toList();
 427     }
 428 
 429 }