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