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 }