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.Ascii;
     19 import com.google.common.base.Joiner;
     20 import com.google.common.base.Objects;
     21 import com.google.common.base.Optional;
     22 import com.google.common.base.Splitter;
     23 import com.google.common.collect.ImmutableList;
     24 import com.google.common.collect.ImmutableSet;
     25 import com.google.common.collect.Sets;
     26 import java.io.IOException;
     27 import java.util.ArrayList;
     28 import java.util.Collections;
     29 import java.util.Iterator;
     30 import java.util.List;
     31 import java.util.Set;
     32 import javax.lang.model.SourceVersion;
     33 import javax.lang.model.element.Element;
     34 import javax.lang.model.element.ElementKind;
     35 import javax.lang.model.element.NestingKind;
     36 import javax.lang.model.element.PackageElement;
     37 import javax.lang.model.element.TypeElement;
     38 
     39 import static com.google.common.base.Preconditions.checkArgument;
     40 import static com.google.common.base.Preconditions.checkNotNull;
     41 import static javax.lang.model.element.NestingKind.MEMBER;
     42 import static javax.lang.model.element.NestingKind.TOP_LEVEL;
     43 
     44 /**
     45  * Represents a fully-qualified class name for {@link NestingKind#TOP_LEVEL} and
     46  * {@link NestingKind#MEMBER} classes.
     47  *
     48  * @since 2.0
     49  */
     50 public final class ClassName implements TypeName, Comparable<ClassName> {
     51   private String fullyQualifiedName = null;
     52   private final String packageName;
     53   /* From top to bottom.  E.g.: this field will contain ["A", "B"] for pgk.A.B.C */
     54   private final ImmutableList<String> enclosingSimpleNames;
     55   private final String simpleName;
     56 
     57   private ClassName(String packageName, ImmutableList<String> enclosingSimpleNames,
     58       String simpleName) {
     59     this.packageName = packageName;
     60     this.enclosingSimpleNames = enclosingSimpleNames;
     61     this.simpleName = simpleName;
     62   }
     63 
     64   public String packageName() {
     65     return packageName;
     66   }
     67 
     68   public ImmutableList<String> enclosingSimpleNames() {
     69     return enclosingSimpleNames;
     70   }
     71 
     72   public Optional<ClassName> enclosingClassName() {
     73     return enclosingSimpleNames.isEmpty()
     74         ? Optional.<ClassName>absent()
     75         : Optional.of(new ClassName(packageName,
     76             enclosingSimpleNames.subList(0, enclosingSimpleNames.size() - 1),
     77             enclosingSimpleNames.get(enclosingSimpleNames.size() - 1)));
     78   }
     79 
     80   public String simpleName() {
     81     return simpleName;
     82   }
     83 
     84   public String canonicalName() {
     85     if (fullyQualifiedName == null) {
     86       StringBuilder builder = new StringBuilder(packageName());
     87       if (builder.length() > 0) {
     88         builder.append('.');
     89       }
     90       for (String enclosingSimpleName : enclosingSimpleNames()) {
     91         builder.append(enclosingSimpleName).append('.');
     92       }
     93       fullyQualifiedName = builder.append(simpleName()).toString();
     94     }
     95     return fullyQualifiedName;
     96   }
     97 
     98   /**
     99    * Equivalent to {@link #classFileName(char) classFileName('$')}
    100    */
    101   public String classFileName() {
    102     return classFileName('$');
    103   }
    104 
    105   /**
    106    * Returns the class name (excluding package).
    107    *
    108    * <p>The returned value includes the names of its enclosing classes (if any) but not the package
    109    * name. e.g. {@code fromClass(Map.Entry.class).classFileName('_')} will return {@code Map_Entry}.
    110    */
    111   public String classFileName(char separator) {
    112     StringBuilder builder = new StringBuilder();
    113     for (String enclosingSimpleName : enclosingSimpleNames) {
    114       builder.append(enclosingSimpleName).append(separator);
    115     }
    116     return builder.append(simpleName()).toString();
    117   }
    118 
    119   public ClassName topLevelClassName() {
    120     Iterator<String> enclosingIterator = enclosingSimpleNames().iterator();
    121     return enclosingIterator.hasNext()
    122         ? new ClassName(packageName(), ImmutableList.<String>of(),
    123             enclosingIterator.next())
    124         : this;
    125   }
    126 
    127   public ClassName nestedClassNamed(String memberClassName) {
    128     checkNotNull(memberClassName);
    129     checkArgument(SourceVersion.isIdentifier(memberClassName));
    130     return new ClassName(packageName(),
    131         new ImmutableList.Builder<String>()
    132             .addAll(enclosingSimpleNames())
    133             .add(simpleName())
    134             .build(),
    135         memberClassName);
    136   }
    137 
    138   public ClassName peerNamed(String peerClassName) {
    139     checkNotNull(peerClassName);
    140     checkArgument(SourceVersion.isIdentifier(peerClassName));
    141     return new ClassName(packageName(), enclosingSimpleNames(), peerClassName);
    142   }
    143 
    144   /**
    145    * Returns a parameterized type name with this as its raw type if {@code parameters} is not empty.
    146    * If {@code parameters} is empty, returns this object.
    147    */
    148   public TypeName withTypeParameters(List<? extends TypeName> parameters) {
    149     return parameters.isEmpty() ? this : ParameterizedTypeName.create(this, parameters);
    150   }
    151 
    152   private static final ImmutableSet<NestingKind> ACCEPTABLE_NESTING_KINDS =
    153       Sets.immutableEnumSet(TOP_LEVEL, MEMBER);
    154 
    155   public static ClassName fromTypeElement(TypeElement element) {
    156     checkNotNull(element);
    157     checkArgument(ACCEPTABLE_NESTING_KINDS.contains(element.getNestingKind()));
    158     String simpleName = element.getSimpleName().toString();
    159     List<String> enclosingNames = new ArrayList<String>();
    160     Element current = element.getEnclosingElement();
    161     while (current.getKind().isClass() || current.getKind().isInterface()) {
    162       checkArgument(ACCEPTABLE_NESTING_KINDS.contains(element.getNestingKind()));
    163       enclosingNames.add(current.getSimpleName().toString());
    164       current = current.getEnclosingElement();
    165     }
    166     PackageElement packageElement = getPackage(current);
    167     Collections.reverse(enclosingNames);
    168     return new ClassName(packageElement.getQualifiedName().toString(),
    169         ImmutableList.copyOf(enclosingNames), simpleName);
    170   }
    171 
    172   public static ClassName fromClass(Class<?> clazz) {
    173     checkNotNull(clazz);
    174     List<String> enclosingNames = new ArrayList<String>();
    175     Class<?> current = clazz.getEnclosingClass();
    176     while (current != null) {
    177       enclosingNames.add(current.getSimpleName());
    178       current = current.getEnclosingClass();
    179     }
    180     Collections.reverse(enclosingNames);
    181     return create(clazz.getPackage().getName(), enclosingNames, clazz.getSimpleName());
    182   }
    183 
    184   private static PackageElement getPackage(Element type) {
    185     while (type.getKind() != ElementKind.PACKAGE) {
    186       type = type.getEnclosingElement();
    187     }
    188     return (PackageElement) type;
    189   }
    190 
    191   /**
    192    * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
    193    * method assumes that the input is ASCII and follows typical Java style (lower-case package
    194    * names, upper-camel-case class names) and may produce incorrect results or throw
    195    * {@link IllegalArgumentException} otherwise. For that reason, {@link #fromClass(Class)} and
    196    * {@link #fromClass(Class)} should be preferred as they can correctly create {@link ClassName}
    197    * instances without such restrictions.
    198    */
    199   public static ClassName bestGuessFromString(String classNameString) {
    200     checkNotNull(classNameString);
    201     List<String> parts = Splitter.on('.').splitToList(classNameString);
    202     int firstClassPartIndex = -1;
    203     for (int i = 0; i < parts.size(); i++) {
    204       String part = parts.get(i);
    205       checkArgument(SourceVersion.isIdentifier(part));
    206       char firstChar = part.charAt(0);
    207       if (Ascii.isLowerCase(firstChar)) {
    208         // looks like a package part
    209         if (firstClassPartIndex >= 0) {
    210           throw new IllegalArgumentException("couldn't make a guess for " + classNameString);
    211         }
    212       } else if (Ascii.isUpperCase(firstChar)) {
    213         // looks like a class part
    214         if (firstClassPartIndex < 0) {
    215           firstClassPartIndex = i;
    216         }
    217       } else {
    218         throw new IllegalArgumentException("couldn't make a guess for " + classNameString);
    219       }
    220     }
    221     int lastIndex = parts.size() - 1;
    222     return new ClassName(
    223         Joiner.on('.').join(parts.subList(0, firstClassPartIndex)),
    224         firstClassPartIndex == lastIndex
    225             ? ImmutableList.<String>of()
    226             : ImmutableList.copyOf(parts.subList(firstClassPartIndex, lastIndex)),
    227         parts.get(lastIndex));
    228   }
    229 
    230   public static ClassName create(
    231       String packageName, List<String> enclosingSimpleNames, String simpleName) {
    232     return new ClassName(packageName, ImmutableList.copyOf(enclosingSimpleNames),
    233         simpleName);
    234   }
    235 
    236   public static ClassName create(String packageName, String simpleName) {
    237     return new ClassName(packageName, ImmutableList.<String>of(), simpleName);
    238   }
    239 
    240   @Override
    241   public String toString() {
    242     return canonicalName();
    243   }
    244 
    245   @Override
    246   public Appendable write(Appendable appendable, Context context) throws IOException {
    247     appendable.append(context.sourceReferenceForClassName(this));
    248     return appendable;
    249   }
    250 
    251   @Override
    252   public boolean equals(Object obj) {
    253     if (obj == this) {
    254       return true;
    255     } else if (obj instanceof ClassName) {
    256       ClassName that = (ClassName) obj;
    257       return this.packageName.equals(that.packageName)
    258           && this.enclosingSimpleNames.equals(that.enclosingSimpleNames)
    259           && this.simpleName.equals(that.simpleName);
    260     } else {
    261       return false;
    262     }
    263   }
    264 
    265   @Override
    266   public int hashCode() {
    267     return Objects.hashCode(packageName, enclosingSimpleNames, simpleName);
    268   }
    269 
    270   @Override
    271   public int compareTo(ClassName o) {
    272     return canonicalName().compareTo(o.canonicalName());
    273   }
    274 
    275   @Override
    276   public Set<ClassName> referencedClasses() {
    277     return ImmutableSet.of(this);
    278   }
    279 }
    280