Home | History | Annotate | Download | only in writer
      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 {
     52   public static JavaWriter inPackage(String packageName) {
     53     return new JavaWriter(packageName);
     54   }
     55 
     56   public static JavaWriter inPackage(Package enclosingPackage) {
     57     return new JavaWriter(enclosingPackage.getName());
     58   }
     59 
     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 
     69   private JavaWriter(String packageName) {
     70     this.packageName = packageName;
     71     this.typeWriters = Lists.newArrayList();
     72     this.explicitImports = Lists.newArrayList();
     73   }
     74 
     75   public List<TypeWriter> getTypeWriters() {
     76     return unmodifiableList(typeWriters);
     77   }
     78 
     79   public JavaWriter addImport(Class<?> importedClass) {
     80     explicitImports.add(ClassName.fromClass(importedClass));
     81     return this;
     82   }
     83 
     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 
     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 
     98   public InterfaceWriter addInterface(String simpleName) {
     99     InterfaceWriter writer = new InterfaceWriter(ClassName.create(packageName, simpleName));
    100     typeWriters.add(writer);
    101     return writer;
    102   }
    103 
    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 
    173   public void file(Filer filer, Iterable<? extends Element> originatingElements)
    174       throws IOException {
    175     file(filer, Iterables.getOnlyElement(typeWriters).name.canonicalName(), originatingElements);
    176   }
    177 
    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
    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 
    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
    216     public Context createSubcontext(Set<ClassName> newTypes) {
    217       return new CompilationUnitContext(packageName, Sets.union(visibleClasses, newTypes));
    218     }
    219 
    220     @Override
    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 
    237     private boolean collidesWithVisibleClass(ClassName className) {
    238       return collidesWithVisibleClass(className.simpleName());
    239     }
    240 
    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 
    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