src/share/classes/com/sun/tools/javac/processing/JavacFiler.java

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

mercurial