• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 Google, Inc.
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 package dagger.internal.codegen.writer;
17 
18 import com.google.common.base.Function;
19 import com.google.common.base.Optional;
20 import com.google.common.collect.BiMap;
21 import com.google.common.collect.FluentIterable;
22 import com.google.common.collect.HashBiMap;
23 import com.google.common.collect.ImmutableSet;
24 import com.google.common.collect.ImmutableSortedSet;
25 import com.google.common.collect.Iterables;
26 import com.google.common.collect.Lists;
27 import com.google.common.collect.Ordering;
28 import com.google.common.collect.Sets;
29 import com.google.common.io.CharSink;
30 import com.google.common.io.CharSource;
31 import com.google.googlejavaformat.java.Formatter;
32 import com.google.googlejavaformat.java.FormatterException;
33 import dagger.internal.codegen.writer.Writable.Context;
34 import java.io.IOException;
35 import java.io.Writer;
36 import java.util.ArrayDeque;
37 import java.util.Deque;
38 import java.util.List;
39 import java.util.Set;
40 import javax.annotation.processing.Filer;
41 import javax.lang.model.element.Element;
42 import javax.lang.model.element.PackageElement;
43 import javax.tools.JavaFileObject;
44 
45 import static com.google.common.base.Preconditions.checkNotNull;
46 import static java.util.Collections.unmodifiableList;
47 
48 /**
49  * Writes a single compilation unit.
50  */
51 public final class JavaWriter {
inPackage(String packageName)52   public static JavaWriter inPackage(String packageName) {
53     return new JavaWriter(packageName);
54   }
55 
inPackage(Package enclosingPackage)56   public static JavaWriter inPackage(Package enclosingPackage) {
57     return new JavaWriter(enclosingPackage.getName());
58   }
59 
inPackage(PackageElement packageElement)60   public static JavaWriter inPackage(PackageElement packageElement) {
61     return new JavaWriter(packageElement.getQualifiedName().toString());
62   }
63 
64   private final String packageName;
65   // TODO(gak): disallow multiple types in a file?
66   private final List<TypeWriter> typeWriters;
67   private final List<ClassName> explicitImports;
68 
JavaWriter(String packageName)69   private JavaWriter(String packageName) {
70     this.packageName = packageName;
71     this.typeWriters = Lists.newArrayList();
72     this.explicitImports = Lists.newArrayList();
73   }
74 
getTypeWriters()75   public List<TypeWriter> getTypeWriters() {
76     return unmodifiableList(typeWriters);
77   }
78 
addImport(Class<?> importedClass)79   public JavaWriter addImport(Class<?> importedClass) {
80     explicitImports.add(ClassName.fromClass(importedClass));
81     return this;
82   }
83 
addClass(String simpleName)84   public ClassWriter addClass(String simpleName) {
85     checkNotNull(simpleName);
86     ClassWriter classWriter = new ClassWriter(ClassName.create(packageName, simpleName));
87     typeWriters.add(classWriter);
88     return classWriter;
89   }
90 
addEnum(String simpleName)91   public EnumWriter addEnum(String simpleName) {
92     checkNotNull(simpleName);
93     EnumWriter writer = new EnumWriter(ClassName.create(packageName, simpleName));
94     typeWriters.add(writer);
95     return writer;
96   }
97 
addInterface(String simpleName)98   public InterfaceWriter addInterface(String simpleName) {
99     InterfaceWriter writer = new InterfaceWriter(ClassName.create(packageName, simpleName));
100     typeWriters.add(writer);
101     return writer;
102   }
103 
write(A appendable)104   public <A extends Appendable> A write(A appendable) throws IOException {
105     if (!packageName.isEmpty()) {
106       appendable.append("package ").append(packageName).append(";\n\n");
107     }
108 
109     // write imports
110     ImmutableSet<ClassName> classNames = FluentIterable.from(typeWriters)
111         .transformAndConcat(new Function<HasClassReferences, Set<ClassName>>() {
112           @Override
113           public Set<ClassName> apply(HasClassReferences input) {
114             return input.referencedClasses();
115           }
116         })
117         .toSet();
118 
119     ImmutableSortedSet<ClassName> importCandidates = ImmutableSortedSet.<ClassName>naturalOrder()
120         .addAll(explicitImports)
121         .addAll(classNames)
122         .build();
123     ImmutableSet<ClassName> typeNames = FluentIterable.from(typeWriters)
124         .transform(new Function<TypeWriter, ClassName>() {
125           @Override public ClassName apply(TypeWriter input) {
126             return input.name;
127           }
128         })
129         .toSet();
130 
131     ImmutableSet.Builder<String> declaredSimpleNamesBuilder = ImmutableSet.builder();
132     Deque<TypeWriter> declaredTypes = new ArrayDeque<>(typeWriters);
133     while (!declaredTypes.isEmpty()) {
134       TypeWriter currentType = declaredTypes.pop();
135       declaredSimpleNamesBuilder.add(currentType.name().simpleName());
136       declaredTypes.addAll(currentType.nestedTypeWriters);
137     }
138 
139     ImmutableSet<String> declaredSimpleNames = declaredSimpleNamesBuilder.build();
140 
141     BiMap<String, ClassName> importedClassIndex = HashBiMap.create();
142     for (ClassName className : importCandidates) {
143       if (!(className.packageName().equals(packageName)
144               && !className.enclosingClassName().isPresent())
145           && !(className.packageName().equals("java.lang")
146               && className.enclosingSimpleNames().isEmpty())
147           && !typeNames.contains(className.topLevelClassName())) {
148         Optional<ClassName> importCandidate = Optional.of(className);
149         while (importCandidate.isPresent()
150             && (importedClassIndex.containsKey(importCandidate.get().simpleName())
151                 || declaredSimpleNames.contains(importCandidate.get().simpleName()))) {
152           importCandidate = importCandidate.get().enclosingClassName();
153         }
154         if (importCandidate.isPresent()) {
155           appendable.append("import ").append(importCandidate.get().canonicalName()).append(";\n");
156           importedClassIndex.put(importCandidate.get().simpleName(), importCandidate.get());
157         }
158       }
159     }
160 
161     appendable.append('\n');
162 
163     CompilationUnitContext context =
164         new CompilationUnitContext(packageName, ImmutableSet.copyOf(importedClassIndex.values()));
165 
166     // write types
167     for (TypeWriter typeWriter : typeWriters) {
168       typeWriter.write(appendable, context.createSubcontext(typeNames)).append('\n');
169     }
170     return appendable;
171   }
172 
file(Filer filer, Iterable<? extends Element> originatingElements)173   public void file(Filer filer, Iterable<? extends Element> originatingElements)
174       throws IOException {
175     file(filer, Iterables.getOnlyElement(typeWriters).name.canonicalName(), originatingElements);
176   }
177 
file(Filer filer, CharSequence name, Iterable<? extends Element> originatingElements)178   public void file(Filer filer, CharSequence name,  Iterable<? extends Element> originatingElements)
179       throws IOException {
180     final JavaFileObject sourceFile = filer.createSourceFile(name,
181         Iterables.toArray(originatingElements, Element.class));
182     try {
183       new Formatter().formatSource(
184           CharSource.wrap(write(new StringBuilder())),
185           new CharSink() {
186             @Override public Writer openStream() throws IOException {
187               return sourceFile.openWriter();
188             }
189           });
190     } catch (FormatterException e) {
191       throw new IllegalStateException(
192           "The writer produced code that could not be parsed by the formatter", e);
193     }
194   }
195 
196   @Override
toString()197   public String toString() {
198     try {
199       return write(new StringBuilder()).toString();
200     } catch (IOException e) {
201       throw new AssertionError();
202     }
203   }
204 
205   static final class CompilationUnitContext implements Context {
206     private final String packageName;
207     private final ImmutableSortedSet<ClassName> visibleClasses;
208 
CompilationUnitContext(String packageName, Set<ClassName> visibleClasses)209     CompilationUnitContext(String packageName, Set<ClassName> visibleClasses) {
210       this.packageName = packageName;
211       this.visibleClasses =
212           ImmutableSortedSet.copyOf(Ordering.natural().reverse(), visibleClasses);
213     }
214 
215     @Override
createSubcontext(Set<ClassName> newTypes)216     public Context createSubcontext(Set<ClassName> newTypes) {
217       return new CompilationUnitContext(packageName, Sets.union(visibleClasses, newTypes));
218     }
219 
220     @Override
sourceReferenceForClassName(ClassName className)221     public String sourceReferenceForClassName(ClassName className) {
222       if (isImported(className)) {
223         return className.simpleName();
224       }
225       Optional<ClassName> enclosingClassName = className.enclosingClassName();
226       while (enclosingClassName.isPresent()) {
227         if (isImported(enclosingClassName.get())) {
228           return enclosingClassName.get().simpleName()
229               + className.canonicalName()
230                   .substring(enclosingClassName.get().canonicalName().length());
231         }
232         enclosingClassName = enclosingClassName.get().enclosingClassName();
233       }
234       return className.canonicalName();
235     }
236 
collidesWithVisibleClass(ClassName className)237     private boolean collidesWithVisibleClass(ClassName className) {
238       return collidesWithVisibleClass(className.simpleName());
239     }
240 
collidesWithVisibleClass(String simpleName)241     private boolean collidesWithVisibleClass(String simpleName) {
242       return FluentIterable.from(visibleClasses)
243           .transform(new Function<ClassName, String>() {
244             @Override public String apply(ClassName input) {
245               return input.simpleName();
246             }
247           })
248           .contains(simpleName);
249     }
250 
isImported(ClassName className)251     private boolean isImported(ClassName className) {
252       return (packageName.equals(className.packageName())
253               && !className.enclosingClassName().isPresent()
254               && !collidesWithVisibleClass(className)) // need to account for scope & hiding
255           || visibleClasses.contains(className)
256           || (className.packageName().equals("java.lang")
257               && className.enclosingSimpleNames().isEmpty());
258     }
259   }
260 }
261