001: /*
002: * Copyright 2005-2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.javac.processing;
027:
028: import com.sun.tools.javac.util.*;
029: import javax.annotation.processing.*;
030: import javax.lang.model.SourceVersion;
031: import javax.lang.model.element.NestingKind;
032: import javax.lang.model.element.Modifier;
033: import javax.lang.model.element.Element;
034: import java.util.*;
035:
036: import java.io.Closeable;
037: import java.io.File;
038: import java.io.InputStream;
039: import java.io.OutputStream;
040: import java.io.OutputStreamWriter;
041: import java.io.FilterOutputStream;
042: import java.io.Reader;
043: import java.io.Writer;
044: import java.io.FilterWriter;
045: import java.io.PrintWriter;
046: import java.io.IOException;
047: import java.net.URI;
048: import javax.tools.FileObject;
049:
050: import javax.tools.*;
051: import static java.util.Collections.*;
052:
053: import javax.tools.JavaFileManager.Location;
054: import static javax.tools.StandardLocation.SOURCE_OUTPUT;
055: import static javax.tools.StandardLocation.CLASS_OUTPUT;
056:
057: /**
058: * The FilerImplementation class must maintain a number of
059: * constraints. First, multiple attempts to open the same path within
060: * the same invocation of the tool results in an IOException being
061: * thrown. For example, trying to open the same source file twice:
062: *
063: * <pre>
064: * createSourceFile("foo.Bar")
065: * ...
066: * createSourceFile("foo.Bar")
067: * </pre>
068: *
069: * is disallowed as is opening a text file that happens to have
070: * the same name as a source file:
071: *
072: * <pre>
073: * createSourceFile("foo.Bar")
074: * ...
075: * createTextFile(SOURCE_TREE, "foo", new File("Bar"), null)
076: * </pre>
077: *
078: * <p>Additionally, creating a source file that corresponds to an
079: * already created class file (or vice versa) also results in an
080: * IOException since each type can only be created once. However, if
081: * the Filer is used to create a text file named *.java that happens
082: * to correspond to an existing class file, a warning is *not*
083: * generated. Similarly, a warning is not generated for a binary file
084: * named *.class and an existing source file.
085: *
086: * <p>The reason for this difference is that source files and class
087: * files are registered with the tool and can get passed on as
088: * declarations to the next round of processing. Files that are just
089: * named *.java and *.class are not processed in that manner; although
090: * having extra source files and class files on the source path and
091: * class path can alter the behavior of the tool and any final
092: * compile.
093: *
094: * <p><b>This is NOT part of any API supported by Sun Microsystems.
095: * If you write code that depends on this, you do so at your own risk.
096: * This code and its internal interfaces are subject to change or
097: * deletion without notice.</b>
098: */
099: @Version("@(#)JavacFiler.java 1.20 07/05/05")
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 = "Output stream or writer has already been opened.";
105: private static final String NOT_FOR_READING = "FileObject was not opened for reading.";
106: private static final String NOT_FOR_WRITING = "FileObject was not opened for writing.";
107:
108: /**
109: * Wrap a JavaFileObject to manage writing by the Filer.
110: */
111: private class FilerOutputFileObject extends
112: ForwardingFileObject<FileObject> {
113: private boolean opened = false;
114: private String name;
115:
116: FilerOutputFileObject(String name, FileObject fileObject) {
117: super (fileObject);
118: this .name = name;
119: }
120:
121: @Override
122: public synchronized OutputStream openOutputStream()
123: throws IOException {
124: if (opened)
125: throw new IOException(ALREADY_OPENED);
126: opened = true;
127: return new FilerOutputStream(name, fileObject);
128: }
129:
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: }
137:
138: // Three anti-literacy methods
139: @Override
140: public InputStream openInputStream() throws IOException {
141: throw new IllegalStateException(NOT_FOR_READING);
142: }
143:
144: @Override
145: public Reader openReader(boolean ignoreEncodingErrors)
146: throws IOException {
147: throw new IllegalStateException(NOT_FOR_READING);
148: }
149:
150: @Override
151: public CharSequence getCharContent(boolean ignoreEncodingErrors)
152: throws IOException {
153: throw new IllegalStateException(NOT_FOR_READING);
154: }
155:
156: @Override
157: public boolean delete() {
158: return false;
159: }
160: }
161:
162: private class FilerOutputJavaFileObject extends
163: FilerOutputFileObject implements JavaFileObject {
164: private final JavaFileObject javaFileObject;
165:
166: FilerOutputJavaFileObject(String name,
167: JavaFileObject javaFileObject) {
168: super (name, javaFileObject);
169: this .javaFileObject = javaFileObject;
170: }
171:
172: public JavaFileObject.Kind getKind() {
173: return javaFileObject.getKind();
174: }
175:
176: public boolean isNameCompatible(String simpleName,
177: JavaFileObject.Kind kind) {
178: return javaFileObject.isNameCompatible(simpleName, kind);
179: }
180:
181: public NestingKind getNestingKind() {
182: return javaFileObject.getNestingKind();
183: }
184:
185: public Modifier getAccessLevel() {
186: return javaFileObject.getAccessLevel();
187: }
188: }
189:
190: /**
191: * Wrap a JavaFileObject to manage reading by the Filer.
192: */
193: private class FilerInputFileObject extends
194: ForwardingFileObject<FileObject> {
195: FilerInputFileObject(FileObject fileObject) {
196: super (fileObject);
197: }
198:
199: @Override
200: public OutputStream openOutputStream() throws IOException {
201: throw new IllegalStateException(NOT_FOR_WRITING);
202: }
203:
204: @Override
205: public Writer openWriter() throws IOException {
206: throw new IllegalStateException(NOT_FOR_WRITING);
207: }
208:
209: @Override
210: public boolean delete() {
211: return false;
212: }
213: }
214:
215: private class FilerInputJavaFileObject extends FilerInputFileObject
216: implements JavaFileObject {
217: private final JavaFileObject javaFileObject;
218:
219: FilerInputJavaFileObject(JavaFileObject javaFileObject) {
220: super (javaFileObject);
221: this .javaFileObject = javaFileObject;
222: }
223:
224: public JavaFileObject.Kind getKind() {
225: return javaFileObject.getKind();
226: }
227:
228: public boolean isNameCompatible(String simpleName,
229: JavaFileObject.Kind kind) {
230: return javaFileObject.isNameCompatible(simpleName, kind);
231: }
232:
233: public NestingKind getNestingKind() {
234: return javaFileObject.getNestingKind();
235: }
236:
237: public Modifier getAccessLevel() {
238: return javaFileObject.getAccessLevel();
239: }
240: }
241:
242: /**
243: * Wrap a {@code OutputStream} returned from the {@code
244: * JavaFileManager} to properly register source or class files
245: * when they are closed.
246: */
247: private class FilerOutputStream extends FilterOutputStream {
248: String typeName;
249: FileObject fileObject;
250: boolean closed = false;
251:
252: /**
253: * @param typeName name of class or {@code null} if just a
254: * binary file
255: */
256: FilerOutputStream(String typeName, FileObject fileObject)
257: throws IOException {
258: super (fileObject.openOutputStream());
259: this .typeName = typeName;
260: this .fileObject = fileObject;
261: }
262:
263: public synchronized void close() throws IOException {
264: if (!closed) {
265: closed = true;
266: /*
267: * If an IOException occurs when closing the underlying
268: * stream, still try to process the file.
269: */
270:
271: closeFileObject(typeName, fileObject);
272: out.close();
273: }
274: }
275: }
276:
277: /**
278: * Wrap a {@code Writer} returned from the {@code JavaFileManager}
279: * to properly register source or class files when they are
280: * closed.
281: */
282: private class FilerWriter extends FilterWriter {
283: String typeName;
284: FileObject fileObject;
285: boolean closed = false;
286:
287: /**
288: * @param fileObject the fileObject to be written to
289: * @param typeName name of source file or {@code null} if just a
290: * text file
291: */
292: FilerWriter(String typeName, FileObject fileObject)
293: throws IOException {
294: super (fileObject.openWriter());
295: this .typeName = typeName;
296: this .fileObject = fileObject;
297: }
298:
299: public synchronized void close() throws IOException {
300: if (!closed) {
301: closed = true;
302: /*
303: * If an IOException occurs when closing the underlying
304: * Writer, still try to process the file.
305: */
306:
307: closeFileObject(typeName, fileObject);
308: out.close();
309: }
310: }
311: }
312:
313: JavaFileManager fileManager;
314: Log log;
315: Context context;
316: boolean lastRound;
317:
318: private final boolean lint;
319:
320: /**
321: * Logical names of all created files. This set must be
322: * synchronized.
323: */
324: private final Set<FileObject> fileObjectHistory;
325:
326: /**
327: * Names of types that have had files created but not closed.
328: */
329: private final Set<String> openTypeNames;
330:
331: /**
332: * Names of source files closed in this round. This set must be
333: * synchronized. Its iterators should preserve insertion order.
334: */
335: private Set<String> generatedSourceNames;
336:
337: /**
338: * Names and class files of the class files closed in this round.
339: * This set must be synchronized. Its iterators should preserve
340: * insertion order.
341: */
342: private final Map<String, JavaFileObject> generatedClasses;
343:
344: /**
345: * JavaFileObjects for source files closed in this round. This
346: * set must be synchronized. Its iterators should preserve
347: * insertion order.
348: */
349: private Set<JavaFileObject> generatedSourceFileObjects;
350:
351: /**
352: * Names of all created source files. Its iterators should
353: * preserve insertion order.
354: */
355: private final Set<String> aggregateGeneratedSourceNames;
356:
357: /**
358: * Names of all created class files. Its iterators should
359: * preserve insertion order.
360: */
361: private final Set<String> aggregateGeneratedClassNames;
362:
363: JavacFiler(Context context) {
364: this .context = context;
365: fileManager = context.get(JavaFileManager.class);
366:
367: log = Log.instance(context);
368:
369: fileObjectHistory = synchronizedSet(new LinkedHashSet<FileObject>());
370: generatedSourceNames = synchronizedSet(new LinkedHashSet<String>());
371: generatedSourceFileObjects = synchronizedSet(new LinkedHashSet<JavaFileObject>());
372:
373: generatedClasses = synchronizedMap(new LinkedHashMap<String, JavaFileObject>());
374:
375: openTypeNames = synchronizedSet(new LinkedHashSet<String>());
376:
377: aggregateGeneratedSourceNames = new LinkedHashSet<String>();
378: aggregateGeneratedClassNames = new LinkedHashSet<String>();
379:
380: lint = (Options.instance(context)).lint("processing");
381: }
382:
383: public JavaFileObject createSourceFile(CharSequence name,
384: Element... originatingElements) throws IOException {
385: return createSourceOrClassFile(true, name.toString());
386: }
387:
388: public JavaFileObject createClassFile(CharSequence name,
389: Element... originatingElements) throws IOException {
390: return createSourceOrClassFile(false, name.toString());
391: }
392:
393: private JavaFileObject createSourceOrClassFile(
394: boolean isSourceFile, String name) throws IOException {
395: checkNameAndExistence(name, isSourceFile);
396: Location loc = (isSourceFile ? SOURCE_OUTPUT : CLASS_OUTPUT);
397: JavaFileObject.Kind kind = (isSourceFile ? JavaFileObject.Kind.SOURCE
398: : JavaFileObject.Kind.CLASS);
399:
400: JavaFileObject fileObject = fileManager.getJavaFileForOutput(
401: loc, name, kind, null);
402: checkFileReopening(fileObject, true);
403:
404: if (lastRound)
405: log.warning("proc.file.create.last.round", name);
406:
407: if (isSourceFile)
408: aggregateGeneratedSourceNames.add(name);
409: else
410: aggregateGeneratedClassNames.add(name);
411: openTypeNames.add(name);
412:
413: return new FilerOutputJavaFileObject(name, fileObject);
414: }
415:
416: public FileObject createResource(JavaFileManager.Location location,
417: CharSequence pkg, CharSequence relativeName,
418: Element... originatingElements) throws IOException {
419: locationCheck(location);
420:
421: String strPkg = pkg.toString();
422: if (strPkg.length() > 0)
423: checkName(strPkg);
424:
425: FileObject fileObject = fileManager.getFileForOutput(location,
426: strPkg, relativeName.toString(), null);
427: checkFileReopening(fileObject, true);
428:
429: if (fileObject instanceof JavaFileObject)
430: return new FilerOutputJavaFileObject(null,
431: (JavaFileObject) fileObject);
432: else
433: return new FilerOutputFileObject(null, fileObject);
434: }
435:
436: private void locationCheck(JavaFileManager.Location location) {
437: if (location instanceof StandardLocation) {
438: StandardLocation stdLoc = (StandardLocation) location;
439: if (!stdLoc.isOutputLocation())
440: throw new IllegalArgumentException(
441: "Resource creation not supported in location "
442: + stdLoc);
443: }
444: }
445:
446: public FileObject getResource(JavaFileManager.Location location,
447: CharSequence pkg, CharSequence relativeName)
448: throws IOException {
449: String strPkg = pkg.toString();
450: if (strPkg.length() > 0)
451: checkName(strPkg);
452:
453: // TODO: Only support reading resources in selected output
454: // locations? Only allow reading of non-source, non-class
455: // files from the supported input locations?
456: FileObject fileObject = fileManager.getFileForOutput(location,
457: pkg.toString(), relativeName.toString(), null);
458: // If the path was already opened for writing, throw an exception.
459: checkFileReopening(fileObject, false);
460: return new FilerInputFileObject(fileObject);
461: }
462:
463: private void checkName(String name) throws FilerException {
464: checkName(name, false);
465: }
466:
467: private void checkName(String name, boolean allowUnnamedPackageInfo)
468: throws FilerException {
469: if (!SourceVersion.isName(name)
470: && !isPackageInfo(name, allowUnnamedPackageInfo)) {
471: if (lint)
472: log.warning("proc.illegal.file.name", name);
473: throw new FilerException("Illegal name " + name);
474: }
475: }
476:
477: private boolean isPackageInfo(String name,
478: boolean allowUnnamedPackageInfo) {
479: // Is the name of the form "package-info" or
480: // "foo.bar.package-info"?
481: final String PKG_INFO = "package-info";
482: int periodIndex = name.lastIndexOf(".");
483: if (periodIndex == -1) {
484: return allowUnnamedPackageInfo ? name.equals(PKG_INFO)
485: : false;
486: } else {
487: // "foo.bar.package-info." illegal
488: String prefix = name.substring(0, periodIndex);
489: String simple = name.substring(periodIndex + 1);
490: return SourceVersion.isName(prefix)
491: && simple.equals(PKG_INFO);
492: }
493: }
494:
495: private void checkNameAndExistence(String typename,
496: boolean allowUnnamedPackageInfo) throws FilerException {
497: // TODO: Check if type already exists on source or class path?
498: // If so, use warning message key proc.type.already.exists
499: checkName(typename, allowUnnamedPackageInfo);
500: if (aggregateGeneratedSourceNames.contains(typename)
501: || aggregateGeneratedClassNames.contains(typename)) {
502: if (lint)
503: log.warning("proc.type.recreate", typename);
504: throw new FilerException(
505: "Attempt to recreate a file for type " + typename);
506: }
507: }
508:
509: /**
510: * Check to see if the file has already been opened; if so, throw
511: * an exception, otherwise add it to the set of files.
512: */
513: private void checkFileReopening(FileObject fileObject,
514: boolean addToHistory) throws FilerException {
515: for (FileObject veteran : fileObjectHistory) {
516: if (fileManager.isSameFile(veteran, fileObject)) {
517: if (lint)
518: log.warning("proc.file.reopening", fileObject
519: .getName());
520: throw new FilerException(
521: "Attempt to reopen a file for path "
522: + fileObject.getName());
523: }
524: }
525: if (addToHistory)
526: fileObjectHistory.add(fileObject);
527: }
528:
529: public boolean newFiles() {
530: return (!generatedSourceNames.isEmpty())
531: || (!generatedClasses.isEmpty());
532: }
533:
534: public Set<String> getGeneratedSourceNames() {
535: return generatedSourceNames;
536: }
537:
538: public Set<JavaFileObject> getGeneratedSourceFileObjects() {
539: return generatedSourceFileObjects;
540: }
541:
542: public Map<String, JavaFileObject> getGeneratedClasses() {
543: return generatedClasses;
544: }
545:
546: public void warnIfUnclosedFiles() {
547: if (!openTypeNames.isEmpty())
548: log.warning("proc.unclosed.type.files", openTypeNames
549: .toString());
550: }
551:
552: /**
553: * Update internal state for a new round.
554: */
555: public void newRound(Context context, boolean lastRound) {
556: this .context = context;
557: this .log = Log.instance(context);
558: this .lastRound = lastRound;
559: clearRoundState();
560: }
561:
562: public void close() {
563: clearRoundState();
564: // Cross-round state
565: fileObjectHistory.clear();
566: openTypeNames.clear();
567: aggregateGeneratedSourceNames.clear();
568: aggregateGeneratedClassNames.clear();
569: }
570:
571: private void clearRoundState() {
572: generatedSourceNames.clear();
573: generatedSourceFileObjects.clear();
574: generatedClasses.clear();
575: }
576:
577: /**
578: * Debugging function to display internal state.
579: */
580: public void displayState() {
581: PrintWriter xout = context.get(Log.outKey);
582: xout.println("File Object History : " + fileObjectHistory);
583: xout.println("Open Type Names : " + openTypeNames);
584: xout.println("Gen. Src Names : " + generatedSourceNames);
585: xout.println("Gen. Cls Names : "
586: + generatedClasses.keySet());
587: xout.println("Agg. Gen. Src Names : "
588: + aggregateGeneratedSourceNames);
589: xout.println("Agg. Gen. Cls Names : "
590: + aggregateGeneratedClassNames);
591: }
592:
593: public String toString() {
594: return "javac Filer version @(#)JavacFiler.java 1.20 07/05/05";
595: }
596:
597: /**
598: * Upon close, register files opened by create{Source, Class}File
599: * for annotation processing.
600: */
601: private void closeFileObject(String typeName, FileObject fileObject) {
602: /*
603: * If typeName is non-null, the file object was opened as a
604: * source or class file by the user. If a file was opened as
605: * a resource, typeName will be null and the file is *not*
606: * subject to annotation processing.
607: */
608: if ((typeName != null)) {
609: if (!(fileObject instanceof JavaFileObject))
610: throw new AssertionError("JavaFileOject not found for "
611: + fileObject);
612: JavaFileObject javaFileObject = (JavaFileObject) fileObject;
613: switch (javaFileObject.getKind()) {
614: case SOURCE:
615: generatedSourceNames.add(typeName);
616: generatedSourceFileObjects.add(javaFileObject);
617: openTypeNames.remove(typeName);
618: break;
619:
620: case CLASS:
621: generatedClasses.put(typeName, javaFileObject);
622: openTypeNames.remove(typeName);
623: break;
624:
625: default:
626: break;
627: }
628: }
629: }
630:
631: }
|