• 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 com.squareup.javapoet;
17 
18 import java.io.IOException;
19 import java.util.ArrayList;
20 import java.util.Collections;
21 import java.util.List;
22 import java.util.Map;
23 import java.util.Objects;
24 import javax.lang.model.element.Element;
25 import javax.lang.model.element.PackageElement;
26 import javax.lang.model.element.TypeElement;
27 import javax.lang.model.util.SimpleElementVisitor8;
28 
29 import static com.squareup.javapoet.Util.checkArgument;
30 import static com.squareup.javapoet.Util.checkNotNull;
31 
32 /** A fully-qualified class name for top-level and member classes. */
33 public final class ClassName extends TypeName implements Comparable<ClassName> {
34   public static final ClassName OBJECT = ClassName.get(Object.class);
35 
36   /** The name representing the default Java package. */
37   private static final String NO_PACKAGE = "";
38 
39   /** The package name of this class, or "" if this is in the default package. */
40   final String packageName;
41 
42   /** The enclosing class, or null if this is not enclosed in another class. */
43   final ClassName enclosingClassName;
44 
45   /** This class name, like "Entry" for java.util.Map.Entry. */
46   final String simpleName;
47 
48   private List<String> simpleNames;
49 
50   /** The full class name like "java.util.Map.Entry". */
51   final String canonicalName;
52 
ClassName(String packageName, ClassName enclosingClassName, String simpleName)53   private ClassName(String packageName, ClassName enclosingClassName, String simpleName) {
54     this(packageName, enclosingClassName, simpleName, Collections.emptyList());
55   }
56 
ClassName(String packageName, ClassName enclosingClassName, String simpleName, List<AnnotationSpec> annotations)57   private ClassName(String packageName, ClassName enclosingClassName, String simpleName,
58       List<AnnotationSpec> annotations) {
59     super(annotations);
60     this.packageName = Objects.requireNonNull(packageName, "packageName == null");
61     this.enclosingClassName = enclosingClassName;
62     this.simpleName = simpleName;
63     this.canonicalName = enclosingClassName != null
64         ? (enclosingClassName.canonicalName + '.' + simpleName)
65         : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
66   }
67 
annotated(List<AnnotationSpec> annotations)68   @Override public ClassName annotated(List<AnnotationSpec> annotations) {
69     return new ClassName(packageName, enclosingClassName, simpleName,
70         concatAnnotations(annotations));
71   }
72 
withoutAnnotations()73   @Override public ClassName withoutAnnotations() {
74     if (!isAnnotated()) return this;
75     ClassName resultEnclosingClassName = enclosingClassName != null
76         ? enclosingClassName.withoutAnnotations()
77         : null;
78     return new ClassName(packageName, resultEnclosingClassName, simpleName);
79   }
80 
isAnnotated()81   @Override public boolean isAnnotated() {
82     return super.isAnnotated() || (enclosingClassName != null && enclosingClassName.isAnnotated());
83   }
84 
85   /**
86    * Returns the package name, like {@code "java.util"} for {@code Map.Entry}. Returns the empty
87    * string for the default package.
88    */
packageName()89   public String packageName() {
90     return packageName;
91   }
92 
93   /**
94    * Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class
95    * is not nested in another class.
96    */
enclosingClassName()97   public ClassName enclosingClassName() {
98     return enclosingClassName;
99   }
100 
101   /**
102    * Returns the top class in this nesting group. Equivalent to chained calls to {@link
103    * #enclosingClassName()} until the result's enclosing class is null.
104    */
topLevelClassName()105   public ClassName topLevelClassName() {
106     return enclosingClassName != null ? enclosingClassName.topLevelClassName() : this;
107   }
108 
109   /** Return the binary name of a class. */
reflectionName()110   public String reflectionName() {
111     return enclosingClassName != null
112         ? (enclosingClassName.reflectionName() + '$' + simpleName)
113         : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName);
114   }
115 
simpleNames()116   public List<String> simpleNames() {
117     if (simpleNames != null) {
118       return simpleNames;
119     }
120 
121     if (enclosingClassName == null) {
122       simpleNames = Collections.singletonList(simpleName);
123     } else {
124       List<String> mutableNames = new ArrayList<>();
125       mutableNames.addAll(enclosingClassName().simpleNames());
126       mutableNames.add(simpleName);
127       simpleNames = Collections.unmodifiableList(mutableNames);
128     }
129     return simpleNames;
130   }
131 
132   /**
133    * Returns a class that shares the same enclosing package or class. If this class is enclosed by
134    * another class, this is equivalent to {@code enclosingClassName().nestedClass(name)}. Otherwise
135    * it is equivalent to {@code get(packageName(), name)}.
136    */
peerClass(String name)137   public ClassName peerClass(String name) {
138     return new ClassName(packageName, enclosingClassName, name);
139   }
140 
141   /**
142    * Returns a new {@link ClassName} instance for the specified {@code name} as nested inside this
143    * class.
144    */
nestedClass(String name)145   public ClassName nestedClass(String name) {
146     return new ClassName(packageName, this, name);
147   }
148 
149   /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */
simpleName()150   public String simpleName() {
151     return simpleName;
152   }
153 
154   /**
155    * Returns the full class name of this class.
156    * Like {@code "java.util.Map.Entry"} for {@link Map.Entry}.
157    * */
canonicalName()158   public String canonicalName() {
159     return canonicalName;
160   }
161 
get(Class<?> clazz)162   public static ClassName get(Class<?> clazz) {
163     checkNotNull(clazz, "clazz == null");
164     checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
165     checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
166     checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName");
167 
168     String anonymousSuffix = "";
169     while (clazz.isAnonymousClass()) {
170       int lastDollar = clazz.getName().lastIndexOf('$');
171       anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix;
172       clazz = clazz.getEnclosingClass();
173     }
174     String name = clazz.getSimpleName() + anonymousSuffix;
175 
176     if (clazz.getEnclosingClass() == null) {
177       // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295
178       int lastDot = clazz.getName().lastIndexOf('.');
179       String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : NO_PACKAGE;
180       return new ClassName(packageName, null, name);
181     }
182 
183     return ClassName.get(clazz.getEnclosingClass()).nestedClass(name);
184   }
185 
186   /**
187    * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This
188    * method assumes that the input is ASCII and follows typical Java style (lowercase package
189    * names, UpperCamelCase class names) and may produce incorrect results or throw
190    * {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and
191    * {@link #get(Class)} should be preferred as they can correctly create {@link ClassName}
192    * instances without such restrictions.
193    */
bestGuess(String classNameString)194   public static ClassName bestGuess(String classNameString) {
195     // Add the package name, like "java.util.concurrent", or "" for no package.
196     int p = 0;
197     while (p < classNameString.length() && Character.isLowerCase(classNameString.codePointAt(p))) {
198       p = classNameString.indexOf('.', p) + 1;
199       checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
200     }
201     String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1);
202 
203     // Add class names like "Map" and "Entry".
204     ClassName className = null;
205     for (String simpleName : classNameString.substring(p).split("\\.", -1)) {
206       checkArgument(!simpleName.isEmpty() && Character.isUpperCase(simpleName.codePointAt(0)),
207           "couldn't make a guess for %s", classNameString);
208       className = new ClassName(packageName, className, simpleName);
209     }
210 
211     return className;
212   }
213 
214   /**
215    * Returns a class name created from the given parts. For example, calling this with package name
216    * {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}.
217    */
get(String packageName, String simpleName, String... simpleNames)218   public static ClassName get(String packageName, String simpleName, String... simpleNames) {
219     ClassName className = new ClassName(packageName, null, simpleName);
220     for (String name : simpleNames) {
221       className = className.nestedClass(name);
222     }
223     return className;
224   }
225 
226   /** Returns the class name for {@code element}. */
get(TypeElement element)227   public static ClassName get(TypeElement element) {
228     checkNotNull(element, "element == null");
229     String simpleName = element.getSimpleName().toString();
230 
231     return element.getEnclosingElement().accept(new SimpleElementVisitor8<ClassName, Void>() {
232       @Override public ClassName visitPackage(PackageElement packageElement, Void p) {
233         return new ClassName(packageElement.getQualifiedName().toString(), null, simpleName);
234       }
235 
236       @Override public ClassName visitType(TypeElement enclosingClass, Void p) {
237         return ClassName.get(enclosingClass).nestedClass(simpleName);
238       }
239 
240       @Override public ClassName visitUnknown(Element unknown, Void p) {
241         return get("", simpleName);
242       }
243 
244       @Override public ClassName defaultAction(Element enclosingElement, Void p) {
245         throw new IllegalArgumentException("Unexpected type nesting: " + element);
246       }
247     }, null);
248   }
249 
250   @Override public int compareTo(ClassName o) {
251     return canonicalName.compareTo(o.canonicalName);
252   }
253 
254   @Override CodeWriter emit(CodeWriter out) throws IOException {
255     boolean charsEmitted = false;
256     for (ClassName className : enclosingClasses()) {
257       String simpleName;
258       if (charsEmitted) {
259         // We've already emitted an enclosing class. Emit as we go.
260         out.emit(".");
261         simpleName = className.simpleName;
262 
263       } else if (className.isAnnotated() || className == this) {
264         // We encountered the first enclosing class that must be emitted.
265         String qualifiedName = out.lookupName(className);
266         int dot = qualifiedName.lastIndexOf('.');
267         if (dot != -1) {
268           out.emitAndIndent(qualifiedName.substring(0, dot + 1));
269           simpleName = qualifiedName.substring(dot + 1);
270           charsEmitted = true;
271         } else {
272           simpleName = qualifiedName;
273         }
274 
275       } else {
276         // Don't emit this enclosing type. Keep going so we can be more precise.
277         continue;
278       }
279 
280       if (className.isAnnotated()) {
281         if (charsEmitted) out.emit(" ");
282         className.emitAnnotations(out);
283       }
284 
285       out.emit(simpleName);
286       charsEmitted = true;
287     }
288 
289     return out;
290   }
291 
292   /** Returns all enclosing classes in this, outermost first. */
293   private List<ClassName> enclosingClasses() {
294     List<ClassName> result = new ArrayList<>();
295     for (ClassName c = this; c != null; c = c.enclosingClassName) {
296       result.add(c);
297     }
298     Collections.reverse(result);
299     return result;
300   }
301 }
302