Tue, 11 Aug 2009 01:13:14 +0100
6521805: Regression: JDK5/JDK6 javac allows write access to outer class reference
Summary: javac should warn/complain about identifiers with the same name as synthetic symbol
Reviewed-by: jjg
1 /*
2 * Copyright 2001-2009 Sun Microsystems, Inc. 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. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 */
26 package com.sun.tools.javadoc;
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;
40 import com.sun.tools.javac.code.Symbol.CompletionFailure;
41 import com.sun.tools.javac.comp.Annotate;
42 import com.sun.tools.javac.parser.DocCommentScanner;
43 import com.sun.tools.javac.tree.JCTree;
44 import com.sun.tools.javac.tree.JCTree.JCClassDecl;
45 import com.sun.tools.javac.tree.JCTree.JCCompilationUnit;
46 import com.sun.tools.javac.util.Abort;
47 import com.sun.tools.javac.util.Context;
48 import com.sun.tools.javac.util.List;
49 import com.sun.tools.javac.util.ListBuffer;
50 import com.sun.tools.javac.util.Position;
53 /**
54 * This class could be the main entry point for Javadoc when Javadoc is used as a
55 * component in a larger software system. It provides operations to
56 * construct a new javadoc processor, and to run it on a set of source
57 * files.
58 * @author Neal Gafter
59 */
60 public class JavadocTool extends com.sun.tools.javac.main.JavaCompiler {
61 DocEnv docenv;
63 final Context context;
64 final Messager messager;
65 final JavadocClassReader reader;
66 final JavadocEnter enter;
67 final Annotate annotate;
69 /**
70 * Construct a new JavaCompiler processor, using appropriately
71 * extended phases of the underlying compiler.
72 */
73 protected JavadocTool(Context context) {
74 super(context);
75 this.context = context;
76 messager = Messager.instance0(context);
77 reader = JavadocClassReader.instance0(context);
78 enter = JavadocEnter.instance0(context);
79 annotate = Annotate.instance(context);
80 }
82 /**
83 * For javadoc, the parser needs to keep comments. Overrides method from JavaCompiler.
84 */
85 protected boolean keepComments() {
86 return true;
87 }
89 /**
90 * Construct a new javadoc tool.
91 */
92 public static JavadocTool make0(Context context) {
93 Messager messager = null;
94 try {
95 // force the use of Javadoc's class reader
96 JavadocClassReader.preRegister(context);
98 // force the use of Javadoc's own enter phase
99 JavadocEnter.preRegister(context);
101 // force the use of Javadoc's own member enter phase
102 JavadocMemberEnter.preRegister(context);
104 // force the use of Javadoc's own todo phase
105 JavadocTodo.preRegister(context);
107 // force the use of Messager as a Log
108 messager = Messager.instance0(context);
110 // force the use of the scanner that captures Javadoc comments
111 DocCommentScanner.Factory.preRegister(context);
113 return new JavadocTool(context);
114 } catch (CompletionFailure ex) {
115 messager.error(Position.NOPOS, ex.getMessage());
116 return null;
117 }
118 }
120 public RootDocImpl getRootDocImpl(String doclocale,
121 String encoding,
122 ModifierFilter filter,
123 List<String> javaNames,
124 List<String[]> options,
125 boolean breakiterator,
126 List<String> subPackages,
127 List<String> excludedPackages,
128 boolean docClasses,
129 boolean legacyDoclet,
130 boolean quiet) throws IOException {
131 docenv = DocEnv.instance(context);
132 docenv.showAccess = filter;
133 docenv.quiet = quiet;
134 docenv.breakiterator = breakiterator;
135 docenv.setLocale(doclocale);
136 docenv.setEncoding(encoding);
137 docenv.docClasses = docClasses;
138 docenv.legacyDoclet = legacyDoclet;
139 reader.sourceCompleter = docClasses ? null : this;
141 ListBuffer<String> names = new ListBuffer<String>();
142 ListBuffer<JCCompilationUnit> classTrees = new ListBuffer<JCCompilationUnit>();
143 ListBuffer<JCCompilationUnit> packTrees = new ListBuffer<JCCompilationUnit>();
145 try {
146 StandardJavaFileManager fm = (StandardJavaFileManager) docenv.fileManager;
147 for (List<String> it = javaNames; it.nonEmpty(); it = it.tail) {
148 String name = it.head;
149 if (!docClasses && name.endsWith(".java") && new File(name).exists()) {
150 JavaFileObject fo = fm.getJavaFileObjects(name).iterator().next();
151 docenv.notice("main.Loading_source_file", name);
152 JCCompilationUnit tree = parse(fo);
153 classTrees.append(tree);
154 } else if (isValidPackageName(name)) {
155 names = names.append(name);
156 } else if (name.endsWith(".java")) {
157 docenv.error(null, "main.file_not_found", name);
158 } else {
159 docenv.error(null, "main.illegal_package_name", name);
160 }
161 }
163 if (!docClasses) {
164 // Recursively search given subpackages. If any packages
165 //are found, add them to the list.
166 Map<String,List<JavaFileObject>> packageFiles =
167 searchSubPackages(subPackages, names, excludedPackages);
169 // Parse the packages
170 for (List<String> packs = names.toList(); packs.nonEmpty(); packs = packs.tail) {
171 // Parse sources ostensibly belonging to package.
172 String packageName = packs.head;
173 parsePackageClasses(packageName, packageFiles.get(packageName), packTrees, excludedPackages);
174 }
176 if (messager.nerrors() != 0) return null;
178 // Enter symbols for all files
179 docenv.notice("main.Building_tree");
180 enter.main(classTrees.toList().appendList(packTrees.toList()));
181 }
182 } catch (Abort ex) {}
184 if (messager.nerrors() != 0)
185 return null;
187 if (docClasses)
188 return new RootDocImpl(docenv, javaNames, options);
189 else
190 return new RootDocImpl(docenv, listClasses(classTrees.toList()), names.toList(), options);
191 }
193 /** Is the given string a valid package name? */
194 boolean isValidPackageName(String s) {
195 int index;
196 while ((index = s.indexOf('.')) != -1) {
197 if (!isValidClassName(s.substring(0, index))) return false;
198 s = s.substring(index+1);
199 }
200 return isValidClassName(s);
201 }
203 /**
204 * search all directories in path for subdirectory name. Add all
205 * .java files found in such a directory to args.
206 */
207 private void parsePackageClasses(String name,
208 Iterable<JavaFileObject> files,
209 ListBuffer<JCCompilationUnit> trees,
210 List<String> excludedPackages)
211 throws IOException {
212 if (excludedPackages.contains(name)) {
213 return;
214 }
216 boolean hasFiles = false;
217 docenv.notice("main.Loading_source_files_for_package", name);
219 if (files == null) {
220 Location location = docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)
221 ? StandardLocation.SOURCE_PATH : StandardLocation.CLASS_PATH;
222 ListBuffer<JavaFileObject> lb = new ListBuffer<JavaFileObject>();
223 for (JavaFileObject fo: docenv.fileManager.list(
224 location, name, EnumSet.of(JavaFileObject.Kind.SOURCE), false)) {
225 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
226 String simpleName = getSimpleName(binaryName);
227 if (isValidClassName(simpleName)) {
228 lb.append(fo);
229 }
230 }
231 files = lb.toList();
232 }
234 for (JavaFileObject fo : files) {
235 // messager.notice("main.Loading_source_file", fn);
236 trees.append(parse(fo));
237 hasFiles = true;
238 }
240 if (!hasFiles) {
241 messager.warning(null, "main.no_source_files_for_package",
242 name.replace(File.separatorChar, '.'));
243 }
244 }
246 /**
247 * Recursively search all directories in path for subdirectory name.
248 * Add all packages found in such a directory to packages list.
249 */
250 private Map<String,List<JavaFileObject>> searchSubPackages(
251 List<String> subPackages,
252 ListBuffer<String> packages,
253 List<String> excludedPackages)
254 throws IOException {
255 Map<String,List<JavaFileObject>> packageFiles =
256 new HashMap<String,List<JavaFileObject>>();
258 Map<String,Boolean> includedPackages = new HashMap<String,Boolean>();
259 includedPackages.put("", true);
260 for (String p: excludedPackages)
261 includedPackages.put(p, false);
263 if (docenv.fileManager.hasLocation(StandardLocation.SOURCE_PATH)) {
264 searchSubPackages(subPackages,
265 includedPackages,
266 packages, packageFiles,
267 StandardLocation.SOURCE_PATH,
268 EnumSet.of(JavaFileObject.Kind.SOURCE));
269 searchSubPackages(subPackages,
270 includedPackages,
271 packages, packageFiles,
272 StandardLocation.CLASS_PATH,
273 EnumSet.of(JavaFileObject.Kind.CLASS));
274 } else {
275 searchSubPackages(subPackages,
276 includedPackages,
277 packages, packageFiles,
278 StandardLocation.CLASS_PATH,
279 EnumSet.of(JavaFileObject.Kind.SOURCE, JavaFileObject.Kind.CLASS));
280 }
281 return packageFiles;
282 }
284 private void searchSubPackages(List<String> subPackages,
285 Map<String,Boolean> includedPackages,
286 ListBuffer<String> packages,
287 Map<String, List<JavaFileObject>> packageFiles,
288 StandardLocation location, Set<JavaFileObject.Kind> kinds)
289 throws IOException {
290 for (String subPackage: subPackages) {
291 if (!isIncluded(subPackage, includedPackages))
292 continue;
294 for (JavaFileObject fo: docenv.fileManager.list(location, subPackage, kinds, true)) {
295 String binaryName = docenv.fileManager.inferBinaryName(location, fo);
296 String packageName = getPackageName(binaryName);
297 String simpleName = getSimpleName(binaryName);
298 if (isIncluded(packageName, includedPackages) && isValidClassName(simpleName)) {
299 List<JavaFileObject> list = packageFiles.get(packageName);
300 list = (list == null ? List.of(fo) : list.prepend(fo));
301 packageFiles.put(packageName, list);
302 if (!packages.contains(packageName))
303 packages.add(packageName);
304 }
305 }
306 }
307 }
309 private String getPackageName(String name) {
310 int lastDot = name.lastIndexOf(".");
311 return (lastDot == -1 ? "" : name.substring(0, lastDot));
312 }
314 private String getSimpleName(String name) {
315 int lastDot = name.lastIndexOf(".");
316 return (lastDot == -1 ? name : name.substring(lastDot + 1));
317 }
319 private boolean isIncluded(String packageName, Map<String,Boolean> includedPackages) {
320 Boolean b = includedPackages.get(packageName);
321 if (b == null) {
322 b = isIncluded(getPackageName(packageName), includedPackages);
323 includedPackages.put(packageName, b);
324 }
325 return b;
326 }
328 /**
329 * Recursively search all directories in path for subdirectory name.
330 * Add all packages found in such a directory to packages list.
331 */
332 private void searchSubPackage(String packageName,
333 ListBuffer<String> packages,
334 List<String> excludedPackages,
335 Collection<File> pathnames) {
336 if (excludedPackages.contains(packageName))
337 return;
339 String packageFilename = packageName.replace('.', File.separatorChar);
340 boolean addedPackage = false;
341 for (File pathname : pathnames) {
342 File f = new File(pathname, packageFilename);
343 String filenames[] = f.list();
344 // if filenames not null, then found directory
345 if (filenames != null) {
346 for (String filename : filenames) {
347 if (!addedPackage
348 && (isValidJavaSourceFile(filename) ||
349 isValidJavaClassFile(filename))
350 && !packages.contains(packageName)) {
351 packages.append(packageName);
352 addedPackage = true;
353 } else if (isValidClassName(filename) &&
354 (new File(f, filename)).isDirectory()) {
355 searchSubPackage(packageName + "." + filename,
356 packages, excludedPackages, pathnames);
357 }
358 }
359 }
360 }
361 }
363 /**
364 * Return true if given file name is a valid class file name.
365 * @param file the name of the file to check.
366 * @return true if given file name is a valid class file name
367 * and false otherwise.
368 */
369 private static boolean isValidJavaClassFile(String file) {
370 if (!file.endsWith(".class")) return false;
371 String clazzName = file.substring(0, file.length() - ".class".length());
372 return isValidClassName(clazzName);
373 }
375 /**
376 * Return true if given file name is a valid Java source file name.
377 * @param file the name of the file to check.
378 * @return true if given file name is a valid Java source file name
379 * and false otherwise.
380 */
381 private static boolean isValidJavaSourceFile(String file) {
382 if (!file.endsWith(".java")) return false;
383 String clazzName = file.substring(0, file.length() - ".java".length());
384 return isValidClassName(clazzName);
385 }
387 /** Are surrogates supported?
388 */
389 final static boolean surrogatesSupported = surrogatesSupported();
390 private static boolean surrogatesSupported() {
391 try {
392 boolean b = Character.isHighSurrogate('a');
393 return true;
394 } catch (NoSuchMethodError ex) {
395 return false;
396 }
397 }
399 /**
400 * Return true if given file name is a valid class name
401 * (including "package-info").
402 * @param clazzname the name of the class to check.
403 * @return true if given class name is a valid class name
404 * and false otherwise.
405 */
406 public static boolean isValidClassName(String s) {
407 if (s.length() < 1) return false;
408 if (s.equals("package-info")) return true;
409 if (surrogatesSupported) {
410 int cp = s.codePointAt(0);
411 if (!Character.isJavaIdentifierStart(cp))
412 return false;
413 for (int j=Character.charCount(cp); j<s.length(); j+=Character.charCount(cp)) {
414 cp = s.codePointAt(j);
415 if (!Character.isJavaIdentifierPart(cp))
416 return false;
417 }
418 } else {
419 if (!Character.isJavaIdentifierStart(s.charAt(0)))
420 return false;
421 for (int j=1; j<s.length(); j++)
422 if (!Character.isJavaIdentifierPart(s.charAt(j)))
423 return false;
424 }
425 return true;
426 }
428 /**
429 * From a list of top level trees, return the list of contained class definitions
430 */
431 List<JCClassDecl> listClasses(List<JCCompilationUnit> trees) {
432 ListBuffer<JCClassDecl> result = new ListBuffer<JCClassDecl>();
433 for (JCCompilationUnit t : trees) {
434 for (JCTree def : t.defs) {
435 if (def.getTag() == JCTree.CLASSDEF)
436 result.append((JCClassDecl)def);
437 }
438 }
439 return result.toList();
440 }
442 }