Thu, 24 Jul 2008 19:06:57 +0100
6717241: some diagnostic argument is prematurely converted into a String object
Summary: removed early toString() conversions applied to diagnostic arguments
Reviewed-by: jjg
1 /*
2 * Copyright 2005-2006 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.javac.processing;
28 import com.sun.tools.javac.util.*;
29 import javax.annotation.processing.*;
30 import javax.lang.model.SourceVersion;
31 import javax.lang.model.element.NestingKind;
32 import javax.lang.model.element.Modifier;
33 import javax.lang.model.element.Element;
34 import java.util.*;
36 import java.io.Closeable;
37 import java.io.File;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.io.OutputStreamWriter;
41 import java.io.FilterOutputStream;
42 import java.io.Reader;
43 import java.io.Writer;
44 import java.io.FilterWriter;
45 import java.io.PrintWriter;
46 import java.io.IOException;
47 import java.net.URI;
48 import javax.tools.FileObject;
50 import javax.tools.*;
51 import static java.util.Collections.*;
53 import javax.tools.JavaFileManager.Location;
54 import static javax.tools.StandardLocation.SOURCE_OUTPUT;
55 import static javax.tools.StandardLocation.CLASS_OUTPUT;
57 /**
58 * The FilerImplementation class must maintain a number of
59 * constraints. First, multiple attempts to open the same path within
60 * the same invocation of the tool results in an IOException being
61 * thrown. For example, trying to open the same source file twice:
62 *
63 * <pre>
64 * createSourceFile("foo.Bar")
65 * ...
66 * createSourceFile("foo.Bar")
67 * </pre>
68 *
69 * is disallowed as is opening a text file that happens to have
70 * the same name as a source file:
71 *
72 * <pre>
73 * createSourceFile("foo.Bar")
74 * ...
75 * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
76 * </pre>
77 *
78 * <p>Additionally, creating a source file that corresponds to an
79 * already created class file (or vice versa) also results in an
80 * IOException since each type can only be created once. However, if
81 * the Filer is used to create a text file named *.java that happens
82 * to correspond to an existing class file, a warning is *not*
83 * generated. Similarly, a warning is not generated for a binary file
84 * named *.class and an existing source file.
85 *
86 * <p>The reason for this difference is that source files and class
87 * files are registered with the tool and can get passed on as
88 * declarations to the next round of processing. Files that are just
89 * named *.java and *.class are not processed in that manner; although
90 * having extra source files and class files on the source path and
91 * class path can alter the behavior of the tool and any final
92 * compile.
93 *
94 * <p><b>This is NOT part of any API supported by Sun Microsystems.
95 * If you write code that depends on this, you do so at your own risk.
96 * This code and its internal interfaces are subject to change or
97 * deletion without notice.</b>
98 */
99 public class JavacFiler implements Filer, Closeable {
100 // TODO: Implement different transaction model for updating the
101 // Filer's record keeping on file close.
103 private static final String ALREADY_OPENED =
104 "Output stream or writer has already been opened.";
105 private static final String NOT_FOR_READING =
106 "FileObject was not opened for reading.";
107 private static final String NOT_FOR_WRITING =
108 "FileObject was not opened for writing.";
110 /**
111 * Wrap a JavaFileObject to manage writing by the Filer.
112 */
113 private class FilerOutputFileObject extends ForwardingFileObject<FileObject> {
114 private boolean opened = false;
115 private String name;
117 FilerOutputFileObject(String name, FileObject fileObject) {
118 super(fileObject);
119 this.name = name;
120 }
122 @Override
123 public synchronized OutputStream openOutputStream() throws IOException {
124 if (opened)
125 throw new IOException(ALREADY_OPENED);
126 opened = true;
127 return new FilerOutputStream(name, fileObject);
128 }
130 @Override
131 public synchronized Writer openWriter() throws IOException {
132 if (opened)
133 throw new IOException(ALREADY_OPENED);
134 opened = true;
135 return new FilerWriter(name, fileObject);
136 }
138 // Three anti-literacy methods
139 @Override
140 public InputStream openInputStream() throws IOException {
141 throw new IllegalStateException(NOT_FOR_READING);
142 }
144 @Override
145 public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
146 throw new IllegalStateException(NOT_FOR_READING);
147 }
149 @Override
150 public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
151 throw new IllegalStateException(NOT_FOR_READING);
152 }
154 @Override
155 public boolean delete() {
156 return false;
157 }
158 }
160 private class FilerOutputJavaFileObject extends FilerOutputFileObject implements JavaFileObject {
161 private final JavaFileObject javaFileObject;
162 FilerOutputJavaFileObject(String name, JavaFileObject javaFileObject) {
163 super(name, javaFileObject);
164 this.javaFileObject = javaFileObject;
165 }
167 public JavaFileObject.Kind getKind() {
168 return javaFileObject.getKind();
169 }
171 public boolean isNameCompatible(String simpleName,
172 JavaFileObject.Kind kind) {
173 return javaFileObject.isNameCompatible(simpleName, kind);
174 }
176 public NestingKind getNestingKind() {
177 return javaFileObject.getNestingKind();
178 }
180 public Modifier getAccessLevel() {
181 return javaFileObject.getAccessLevel();
182 }
183 }
185 /**
186 * Wrap a JavaFileObject to manage reading by the Filer.
187 */
188 private class FilerInputFileObject extends ForwardingFileObject<FileObject> {
189 FilerInputFileObject(FileObject fileObject) {
190 super(fileObject);
191 }
193 @Override
194 public OutputStream openOutputStream() throws IOException {
195 throw new IllegalStateException(NOT_FOR_WRITING);
196 }
198 @Override
199 public Writer openWriter() throws IOException {
200 throw new IllegalStateException(NOT_FOR_WRITING);
201 }
203 @Override
204 public boolean delete() {
205 return false;
206 }
207 }
209 private class FilerInputJavaFileObject extends FilerInputFileObject implements JavaFileObject {
210 private final JavaFileObject javaFileObject;
211 FilerInputJavaFileObject(JavaFileObject javaFileObject) {
212 super(javaFileObject);
213 this.javaFileObject = javaFileObject;
214 }
216 public JavaFileObject.Kind getKind() {
217 return javaFileObject.getKind();
218 }
220 public boolean isNameCompatible(String simpleName,
221 JavaFileObject.Kind kind) {
222 return javaFileObject.isNameCompatible(simpleName, kind);
223 }
225 public NestingKind getNestingKind() {
226 return javaFileObject.getNestingKind();
227 }
229 public Modifier getAccessLevel() {
230 return javaFileObject.getAccessLevel();
231 }
232 }
235 /**
236 * Wrap a {@code OutputStream} returned from the {@code
237 * JavaFileManager} to properly register source or class files
238 * when they are closed.
239 */
240 private class FilerOutputStream extends FilterOutputStream {
241 String typeName;
242 FileObject fileObject;
243 boolean closed = false;
245 /**
246 * @param typeName name of class or {@code null} if just a
247 * binary file
248 */
249 FilerOutputStream(String typeName, FileObject fileObject) throws IOException {
250 super(fileObject.openOutputStream());
251 this.typeName = typeName;
252 this.fileObject = fileObject;
253 }
255 public synchronized void close() throws IOException {
256 if (!closed) {
257 closed = true;
258 /*
259 * If an IOException occurs when closing the underlying
260 * stream, still try to process the file.
261 */
263 closeFileObject(typeName, fileObject);
264 out.close();
265 }
266 }
267 }
269 /**
270 * Wrap a {@code Writer} returned from the {@code JavaFileManager}
271 * to properly register source or class files when they are
272 * closed.
273 */
274 private class FilerWriter extends FilterWriter {
275 String typeName;
276 FileObject fileObject;
277 boolean closed = false;
279 /**
280 * @param fileObject the fileObject to be written to
281 * @param typeName name of source file or {@code null} if just a
282 * text file
283 */
284 FilerWriter(String typeName, FileObject fileObject) throws IOException {
285 super(fileObject.openWriter());
286 this.typeName = typeName;
287 this.fileObject = fileObject;
288 }
290 public synchronized void close() throws IOException {
291 if (!closed) {
292 closed = true;
293 /*
294 * If an IOException occurs when closing the underlying
295 * Writer, still try to process the file.
296 */
298 closeFileObject(typeName, fileObject);
299 out.close();
300 }
301 }
302 }
304 JavaFileManager fileManager;
305 Log log;
306 Context context;
307 boolean lastRound;
309 private final boolean lint;
311 /**
312 * Logical names of all created files. This set must be
313 * synchronized.
314 */
315 private final Set<FileObject> fileObjectHistory;
317 /**
318 * Names of types that have had files created but not closed.
319 */
320 private final Set<String> openTypeNames;
322 /**
323 * Names of source files closed in this round. This set must be
324 * synchronized. Its iterators should preserve insertion order.
325 */
326 private Set<String> generatedSourceNames;
328 /**
329 * Names and class files of the class files closed in this round.
330 * This set must be synchronized. Its iterators should preserve
331 * insertion order.
332 */
333 private final Map<String, JavaFileObject> generatedClasses;
335 /**
336 * JavaFileObjects for source files closed in this round. This
337 * set must be synchronized. Its iterators should preserve
338 * insertion order.
339 */
340 private Set<JavaFileObject> generatedSourceFileObjects;
342 /**
343 * Names of all created source files. Its iterators should
344 * preserve insertion order.
345 */
346 private final Set<String> aggregateGeneratedSourceNames;
348 /**
349 * Names of all created class files. Its iterators should
350 * preserve insertion order.
351 */
352 private final Set<String> aggregateGeneratedClassNames;
355 JavacFiler(Context context) {
356 this.context = context;
357 fileManager = context.get(JavaFileManager.class);
359 log = Log.instance(context);
361 fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
362 generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
363 generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());
365 generatedClasses = synchronizedMap(new LinkedHashMap<String, JavaFileObject>());
367 openTypeNames = synchronizedSet(new LinkedHashSet<String>());
369 aggregateGeneratedSourceNames = new LinkedHashSet<String>();
370 aggregateGeneratedClassNames = new LinkedHashSet<String>();
372 lint = (Options.instance(context)).lint("processing");
373 }
375 public JavaFileObject createSourceFile(CharSequence name,
376 Element... originatingElements) throws IOException {
377 return createSourceOrClassFile(true, name.toString());
378 }
380 public JavaFileObject createClassFile(CharSequence name,
381 Element... originatingElements) throws IOException {
382 return createSourceOrClassFile(false, name.toString());
383 }
385 private JavaFileObject createSourceOrClassFile(boolean isSourceFile, String name) throws IOException {
386 checkNameAndExistence(name, isSourceFile);
387 Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
388 JavaFileObject.Kind kind = (isSourceFile ?
389 JavaFileObject.Kind.SOURCE :
390 JavaFileObject.Kind.CLASS);
392 JavaFileObject fileObject =
393 fileManager.getJavaFileForOutput(loc, name, kind, null);
394 checkFileReopening(fileObject, true);
396 if (lastRound)
397 log.warning("proc.file.create.last.round", name);
399 if (isSourceFile)
400 aggregateGeneratedSourceNames.add(name);
401 else
402 aggregateGeneratedClassNames.add(name);
403 openTypeNames.add(name);
405 return new FilerOutputJavaFileObject(name, fileObject);
406 }
408 public FileObject createResource(JavaFileManager.Location location,
409 CharSequence pkg,
410 CharSequence relativeName,
411 Element... originatingElements) throws IOException {
412 locationCheck(location);
414 String strPkg = pkg.toString();
415 if (strPkg.length() > 0)
416 checkName(strPkg);
418 FileObject fileObject =
419 fileManager.getFileForOutput(location, strPkg,
420 relativeName.toString(), null);
421 checkFileReopening(fileObject, true);
423 if (fileObject instanceof JavaFileObject)
424 return new FilerOutputJavaFileObject(null, (JavaFileObject)fileObject);
425 else
426 return new FilerOutputFileObject(null, fileObject);
427 }
429 private void locationCheck(JavaFileManager.Location location) {
430 if (location instanceof StandardLocation) {
431 StandardLocation stdLoc = (StandardLocation) location;
432 if (!stdLoc.isOutputLocation())
433 throw new IllegalArgumentException("Resource creation not supported in location " +
434 stdLoc);
435 }
436 }
438 public FileObject getResource(JavaFileManager.Location location,
439 CharSequence pkg,
440 CharSequence relativeName) throws IOException {
441 String strPkg = pkg.toString();
442 if (strPkg.length() > 0)
443 checkName(strPkg);
445 // TODO: Only support reading resources in selected output
446 // locations? Only allow reading of non-source, non-class
447 // files from the supported input locations?
448 FileObject fileObject = fileManager.getFileForOutput(location,
449 pkg.toString(),
450 relativeName.toString(),
451 null);
452 // If the path was already opened for writing, throw an exception.
453 checkFileReopening(fileObject, false);
454 return new FilerInputFileObject(fileObject);
455 }
457 private void checkName(String name) throws FilerException {
458 checkName(name, false);
459 }
461 private void checkName(String name, boolean allowUnnamedPackageInfo) throws FilerException {
462 if (!SourceVersion.isName(name) && !isPackageInfo(name, allowUnnamedPackageInfo)) {
463 if (lint)
464 log.warning("proc.illegal.file.name", name);
465 throw new FilerException("Illegal name " + name);
466 }
467 }
469 private boolean isPackageInfo(String name, boolean allowUnnamedPackageInfo) {
470 // Is the name of the form "package-info" or
471 // "foo.bar.package-info"?
472 final String PKG_INFO = "package-info";
473 int periodIndex = name.lastIndexOf(".");
474 if (periodIndex == -1) {
475 return allowUnnamedPackageInfo ? name.equals(PKG_INFO) : false;
476 } else {
477 // "foo.bar.package-info." illegal
478 String prefix = name.substring(0, periodIndex);
479 String simple = name.substring(periodIndex+1);
480 return SourceVersion.isName(prefix) && simple.equals(PKG_INFO);
481 }
482 }
484 private void checkNameAndExistence(String typename, boolean allowUnnamedPackageInfo) throws FilerException {
485 // TODO: Check if type already exists on source or class path?
486 // If so, use warning message key proc.type.already.exists
487 checkName(typename, allowUnnamedPackageInfo);
488 if (aggregateGeneratedSourceNames.contains(typename) ||
489 aggregateGeneratedClassNames.contains(typename)) {
490 if (lint)
491 log.warning("proc.type.recreate", typename);
492 throw new FilerException("Attempt to recreate a file for type " + typename);
493 }
494 }
496 /**
497 * Check to see if the file has already been opened; if so, throw
498 * an exception, otherwise add it to the set of files.
499 */
500 private void checkFileReopening(FileObject fileObject, boolean addToHistory) throws FilerException {
501 for(FileObject veteran : fileObjectHistory) {
502 if (fileManager.isSameFile(veteran, fileObject)) {
503 if (lint)
504 log.warning("proc.file.reopening", fileObject.getName());
505 throw new FilerException("Attempt to reopen a file for path " + fileObject.getName());
506 }
507 }
508 if (addToHistory)
509 fileObjectHistory.add(fileObject);
510 }
512 public boolean newFiles() {
513 return (!generatedSourceNames.isEmpty())
514 || (!generatedClasses.isEmpty());
515 }
517 public Set<String> getGeneratedSourceNames() {
518 return generatedSourceNames;
519 }
521 public Set<JavaFileObject> getGeneratedSourceFileObjects() {
522 return generatedSourceFileObjects;
523 }
525 public Map<String, JavaFileObject> getGeneratedClasses() {
526 return generatedClasses;
527 }
529 public void warnIfUnclosedFiles() {
530 if (!openTypeNames.isEmpty())
531 log.warning("proc.unclosed.type.files", openTypeNames.toString());
532 }
534 /**
535 * Update internal state for a new round.
536 */
537 public void newRound(Context context, boolean lastRound) {
538 this.context = context;
539 this.log = Log.instance(context);
540 this.lastRound = lastRound;
541 clearRoundState();
542 }
544 public void close() {
545 clearRoundState();
546 // Cross-round state
547 fileObjectHistory.clear();
548 openTypeNames.clear();
549 aggregateGeneratedSourceNames.clear();
550 aggregateGeneratedClassNames.clear();
551 }
553 private void clearRoundState() {
554 generatedSourceNames.clear();
555 generatedSourceFileObjects.clear();
556 generatedClasses.clear();
557 }
559 /**
560 * Debugging function to display internal state.
561 */
562 public void displayState() {
563 PrintWriter xout = context.get(Log.outKey);
564 xout.println("File Object History : " + fileObjectHistory);
565 xout.println("Open Type Names : " + openTypeNames);
566 xout.println("Gen. Src Names : " + generatedSourceNames);
567 xout.println("Gen. Cls Names : " + generatedClasses.keySet());
568 xout.println("Agg. Gen. Src Names : " + aggregateGeneratedSourceNames);
569 xout.println("Agg. Gen. Cls Names : " + aggregateGeneratedClassNames);
570 }
572 public String toString() {
573 return "javac Filer";
574 }
576 /**
577 * Upon close, register files opened by create{Source, Class}File
578 * for annotation processing.
579 */
580 private void closeFileObject(String typeName, FileObject fileObject) {
581 /*
582 * If typeName is non-null, the file object was opened as a
583 * source or class file by the user. If a file was opened as
584 * a resource, typeName will be null and the file is *not*
585 * subject to annotation processing.
586 */
587 if ((typeName != null)) {
588 if (!(fileObject instanceof JavaFileObject))
589 throw new AssertionError("JavaFileOject not found for " + fileObject);
590 JavaFileObject javaFileObject = (JavaFileObject)fileObject;
591 switch(javaFileObject.getKind()) {
592 case SOURCE:
593 generatedSourceNames.add(typeName);
594 generatedSourceFileObjects.add(javaFileObject);
595 openTypeNames.remove(typeName);
596 break;
598 case CLASS:
599 generatedClasses.put(typeName, javaFileObject);
600 openTypeNames.remove(typeName);
601 break;
603 default:
604 break;
605 }
606 }
607 }
609 }