• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.google.turbine.processing;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static java.nio.charset.StandardCharsets.UTF_8;
21 import static java.util.Objects.requireNonNull;
22 
23 import com.google.common.base.Supplier;
24 import com.google.common.base.Suppliers;
25 import com.google.common.collect.ImmutableMap;
26 import com.google.turbine.diag.SourceFile;
27 import java.io.ByteArrayInputStream;
28 import java.io.ByteArrayOutputStream;
29 import java.io.FileNotFoundException;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.InputStreamReader;
33 import java.io.OutputStream;
34 import java.io.OutputStreamWriter;
35 import java.io.Reader;
36 import java.io.StringReader;
37 import java.io.Writer;
38 import java.net.URI;
39 import java.net.URISyntaxException;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.LinkedHashMap;
43 import java.util.List;
44 import java.util.Map;
45 import java.util.Set;
46 import java.util.function.Function;
47 import javax.annotation.processing.Filer;
48 import javax.annotation.processing.FilerException;
49 import javax.lang.model.element.Element;
50 import javax.lang.model.element.Modifier;
51 import javax.lang.model.element.NestingKind;
52 import javax.tools.FileObject;
53 import javax.tools.JavaFileManager.Location;
54 import javax.tools.JavaFileObject;
55 import javax.tools.JavaFileObject.Kind;
56 import javax.tools.StandardLocation;
57 
58 /** Turbine's implementation of {@link Filer}. */
59 public class TurbineFiler implements Filer {
60 
61   /**
62    * Existing paths of file objects that cannot be regenerated, including the original compilation
63    * inputs and source or class files generated during any annotation processing round.
64    */
65   private final Set<String> seen;
66 
67   /**
68    * File objects generated during the current processing round. Each entry has a unique path, which
69    * is enforced by {@link #seen}.
70    */
71   private final List<TurbineJavaFileObject> files = new ArrayList<>();
72 
73   /** Loads resources from the classpath. */
74   private final Function<String, Supplier<byte[]>> classPath;
75 
76   /** The {@link ClassLoader} for the annotation processor path, for loading resources. */
77   private final ClassLoader loader;
78 
79   private final Map<String, SourceFile> generatedSources = new LinkedHashMap<>();
80   private final Map<String, byte[]> generatedClasses = new LinkedHashMap<>();
81 
82   /** Generated source file objects from all rounds. */
generatedSources()83   public ImmutableMap<String, SourceFile> generatedSources() {
84     return ImmutableMap.copyOf(generatedSources);
85   }
86 
87   /** Generated class file objects from all rounds. */
generatedClasses()88   public ImmutableMap<String, byte[]> generatedClasses() {
89     return ImmutableMap.copyOf(generatedClasses);
90   }
91 
TurbineFiler( Set<String> seen, Function<String, Supplier<byte[]>> classPath, ClassLoader loader)92   public TurbineFiler(
93       Set<String> seen, Function<String, Supplier<byte[]>> classPath, ClassLoader loader) {
94     this.seen = seen;
95     this.classPath = classPath;
96     this.loader = loader;
97   }
98 
99   /**
100    * Called when the current annotation processing round is complete, and returns the sources
101    * generated in that round.
102    */
finishRound()103   public Collection<SourceFile> finishRound() {
104     Map<String, SourceFile> roundSources = new LinkedHashMap<>();
105     for (TurbineJavaFileObject e : files) {
106       String path = e.getName();
107       switch (e.getKind()) {
108         case SOURCE:
109           roundSources.put(path, new SourceFile(path, e.contents()));
110           break;
111         case CLASS:
112           generatedClasses.put(path, e.bytes());
113           break;
114         case OTHER:
115           switch (e.location()) {
116             case CLASS_OUTPUT:
117               generatedClasses.put(path, e.bytes());
118               break;
119             case SOURCE_OUTPUT:
120               this.generatedSources.put(path, new SourceFile(path, e.contents()));
121               break;
122             default:
123               throw new AssertionError(e.location());
124           }
125           break;
126         case HTML:
127           throw new UnsupportedOperationException(String.valueOf(e.getKind()));
128       }
129     }
130     files.clear();
131     this.generatedSources.putAll(roundSources);
132     return roundSources.values();
133   }
134 
135   @Override
createSourceFile(CharSequence n, Element... originatingElements)136   public JavaFileObject createSourceFile(CharSequence n, Element... originatingElements)
137       throws IOException {
138     String name = n.toString();
139     checkArgument(!name.contains("/"), "invalid type name: %s", name);
140     return create(StandardLocation.SOURCE_OUTPUT, Kind.SOURCE, name.replace('.', '/') + ".java");
141   }
142 
143   @Override
createClassFile(CharSequence n, Element... originatingElements)144   public JavaFileObject createClassFile(CharSequence n, Element... originatingElements)
145       throws IOException {
146     String name = n.toString();
147     checkArgument(!name.contains("/"), "invalid type name: %s", name);
148     return create(StandardLocation.CLASS_OUTPUT, Kind.CLASS, name.replace('.', '/') + ".class");
149   }
150 
151   @Override
createResource( Location location, CharSequence p, CharSequence r, Element... originatingElements)152   public FileObject createResource(
153       Location location, CharSequence p, CharSequence r, Element... originatingElements)
154       throws IOException {
155     checkArgument(location instanceof StandardLocation, "%s", location);
156     String pkg = p.toString();
157     String relativeName = r.toString();
158     checkArgument(!pkg.contains("/"), "invalid package: %s", pkg);
159     String path = packageRelativePath(pkg, relativeName);
160     return create((StandardLocation) location, Kind.OTHER, path);
161   }
162 
create(StandardLocation location, Kind kind, String path)163   private JavaFileObject create(StandardLocation location, Kind kind, String path)
164       throws FilerException {
165     checkArgument(location.isOutputLocation());
166     if (!seen.add(path)) {
167       throw new FilerException("already created " + path);
168     }
169     TurbineJavaFileObject result = new TurbineJavaFileObject(location, kind, path);
170     files.add(result);
171     return result;
172   }
173 
174   @Override
getResource(Location location, CharSequence p, CharSequence r)175   public FileObject getResource(Location location, CharSequence p, CharSequence r)
176       throws IOException {
177     String pkg = p.toString();
178     String relativeName = r.toString();
179     checkArgument(!pkg.contains("/"), "invalid package: %s", pkg);
180     checkArgument(location instanceof StandardLocation, "unsupported location %s", location);
181     StandardLocation standardLocation = (StandardLocation) location;
182     String path = packageRelativePath(pkg, relativeName);
183     switch (standardLocation) {
184       case CLASS_OUTPUT:
185         byte[] generated = generatedClasses.get(path);
186         if (generated == null) {
187           throw new FileNotFoundException(path);
188         }
189         return new BytesFileObject(path, Suppliers.ofInstance(generated));
190       case SOURCE_OUTPUT:
191         SourceFile source = generatedSources.get(path);
192         if (source == null) {
193           throw new FileNotFoundException(path);
194         }
195         return new SourceFileObject(path, source.source());
196       case ANNOTATION_PROCESSOR_PATH:
197         if (loader.getResource(path) == null) {
198           throw new FileNotFoundException(path);
199         }
200         return new ResourceFileObject(loader, path);
201       case CLASS_PATH:
202         Supplier<byte[]> bytes = classPath.apply(path);
203         if (bytes == null) {
204           throw new FileNotFoundException(path);
205         }
206         return new BytesFileObject(path, bytes);
207       default:
208         throw new IllegalArgumentException(standardLocation.getName());
209     }
210   }
211 
packageRelativePath(String pkg, String relativeName)212   private static String packageRelativePath(String pkg, String relativeName) {
213     if (pkg.isEmpty()) {
214       return relativeName;
215     }
216     return pkg.replace('.', '/') + '/' + relativeName;
217   }
218 
219   private abstract static class ReadOnlyFileObject implements FileObject {
220 
221     protected final String path;
222 
ReadOnlyFileObject(String path)223     public ReadOnlyFileObject(String path) {
224       this.path = path;
225     }
226 
227     @Override
getName()228     public final String getName() {
229       return path;
230     }
231 
232     @Override
toUri()233     public URI toUri() {
234       return URI.create("file:///" + path);
235     }
236 
237     @Override
openOutputStream()238     public final OutputStream openOutputStream() {
239       throw new IllegalStateException();
240     }
241 
242     @Override
openWriter()243     public final Writer openWriter() {
244       throw new IllegalStateException();
245     }
246 
247     @Override
getLastModified()248     public final long getLastModified() {
249       return 0;
250     }
251 
252     @Override
delete()253     public final boolean delete() {
254       throw new IllegalStateException();
255     }
256   }
257 
258   private abstract static class WriteOnlyFileObject implements FileObject {
259 
260     @Override
openInputStream()261     public final InputStream openInputStream() {
262       throw new IllegalStateException();
263     }
264 
265     @Override
openReader(boolean ignoreEncodingErrors)266     public final Reader openReader(boolean ignoreEncodingErrors) {
267       throw new IllegalStateException();
268     }
269 
270     @Override
getCharContent(boolean ignoreEncodingErrors)271     public final CharSequence getCharContent(boolean ignoreEncodingErrors) {
272       throw new IllegalStateException();
273     }
274   }
275 
276   private static class TurbineJavaFileObject extends WriteOnlyFileObject implements JavaFileObject {
277 
278     private final StandardLocation location;
279     private final Kind kind;
280     private final CharSequence name;
281     private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
282 
TurbineJavaFileObject(StandardLocation location, Kind kind, CharSequence name)283     public TurbineJavaFileObject(StandardLocation location, Kind kind, CharSequence name) {
284       this.location = location;
285       this.kind = kind;
286       this.name = name;
287     }
288 
289     @Override
getKind()290     public Kind getKind() {
291       return kind;
292     }
293 
294     @Override
isNameCompatible(String simpleName, Kind kind)295     public boolean isNameCompatible(String simpleName, Kind kind) {
296       throw new UnsupportedOperationException();
297     }
298 
299     @Override
getNestingKind()300     public NestingKind getNestingKind() {
301       throw new UnsupportedOperationException();
302     }
303 
304     @Override
getAccessLevel()305     public Modifier getAccessLevel() {
306       throw new UnsupportedOperationException();
307     }
308 
309     @Override
toUri()310     public URI toUri() {
311       return URI.create("file:///" + name + kind.extension);
312     }
313 
314     @Override
getName()315     public String getName() {
316       return name.toString();
317     }
318 
319     @Override
openOutputStream()320     public OutputStream openOutputStream() {
321       return baos;
322     }
323 
324     @Override
openWriter()325     public Writer openWriter() {
326       return new OutputStreamWriter(openOutputStream(), UTF_8);
327     }
328 
329     @Override
getLastModified()330     public long getLastModified() {
331       return 0;
332     }
333 
334     @Override
delete()335     public boolean delete() {
336       throw new IllegalStateException();
337     }
338 
bytes()339     public byte[] bytes() {
340       return baos.toByteArray();
341     }
342 
contents()343     public String contents() {
344       return new String(baos.toByteArray(), UTF_8);
345     }
346 
location()347     public StandardLocation location() {
348       return location;
349     }
350   }
351 
352   private static class ResourceFileObject extends ReadOnlyFileObject {
353 
354     private final ClassLoader loader;
355 
ResourceFileObject(ClassLoader loader, String path)356     public ResourceFileObject(ClassLoader loader, String path) {
357       super(path);
358       this.loader = loader;
359     }
360 
361     @Override
toUri()362     public URI toUri() {
363       try {
364         return requireNonNull(loader.getResource(path)).toURI();
365       } catch (URISyntaxException e) {
366         throw new AssertionError(e);
367       }
368     }
369 
370     @Override
openInputStream()371     public InputStream openInputStream() {
372       return loader.getResourceAsStream(path);
373     }
374 
375     @Override
openReader(boolean ignoreEncodingErrors)376     public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
377       return new InputStreamReader(openInputStream(), UTF_8);
378     }
379 
380     @Override
getCharContent(boolean ignoreEncodingErrors)381     public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
382       return new String(openInputStream().readAllBytes(), UTF_8);
383     }
384   }
385 
386   private static class BytesFileObject extends ReadOnlyFileObject {
387 
388     private final Supplier<byte[]> bytes;
389 
BytesFileObject(String path, Supplier<byte[]> bytes)390     public BytesFileObject(String path, Supplier<byte[]> bytes) {
391       super(path);
392       this.bytes = bytes;
393     }
394 
395     @Override
openInputStream()396     public InputStream openInputStream() {
397       return new ByteArrayInputStream(bytes.get());
398     }
399 
400     @Override
openReader(boolean ignoreEncodingErrors)401     public Reader openReader(boolean ignoreEncodingErrors) {
402       return new InputStreamReader(openInputStream(), UTF_8);
403     }
404 
405     @Override
getCharContent(boolean ignoreEncodingErrors)406     public CharSequence getCharContent(boolean ignoreEncodingErrors) {
407       return new String(bytes.get(), UTF_8);
408     }
409   }
410 
411   private static class SourceFileObject extends ReadOnlyFileObject {
412 
413     private final String source;
414 
SourceFileObject(String path, String source)415     public SourceFileObject(String path, String source) {
416       super(path);
417       this.source = source;
418     }
419 
420     @Override
openInputStream()421     public InputStream openInputStream() {
422       return new ByteArrayInputStream(source.getBytes(UTF_8));
423     }
424 
425     @Override
openReader(boolean ignoreEncodingErrors)426     public Reader openReader(boolean ignoreEncodingErrors) {
427       return new StringReader(source);
428     }
429 
430     @Override
getCharContent(boolean ignoreEncodingErrors)431     public CharSequence getCharContent(boolean ignoreEncodingErrors) {
432       return source;
433     }
434   }
435 }
436