• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 Square, 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.lang.reflect.GenericArrayType;
20 import java.lang.reflect.ParameterizedType;
21 import java.lang.reflect.Type;
22 import java.lang.reflect.TypeVariable;
23 import java.lang.reflect.WildcardType;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import javax.lang.model.element.Modifier;
30 import javax.lang.model.element.TypeElement;
31 import javax.lang.model.element.TypeParameterElement;
32 import javax.lang.model.type.ArrayType;
33 import javax.lang.model.type.DeclaredType;
34 import javax.lang.model.type.ErrorType;
35 import javax.lang.model.type.NoType;
36 import javax.lang.model.type.PrimitiveType;
37 import javax.lang.model.type.TypeKind;
38 import javax.lang.model.type.TypeMirror;
39 import javax.lang.model.util.SimpleTypeVisitor8;
40 
41 /**
42  * Any type in Java's type system, plus {@code void}. This class is an identifier for primitive
43  * types like {@code int} and raw reference types like {@code String} and {@code List}. It also
44  * identifies composite types like {@code char[]} and {@code Set<Long>}.
45  *
46  * <p>Type names are dumb identifiers only and do not model the values they name. For example, the
47  * type name for {@code java.util.List} doesn't know about the {@code size()} method, the fact that
48  * lists are collections, or even that it accepts a single type parameter.
49  *
50  * <p>Instances of this class are immutable value objects that implement {@code equals()} and {@code
51  * hashCode()} properly.
52  *
53  * <h3>Referencing existing types</h3>
54  *
55  * <p>Primitives and void are constants that you can reference directly: see {@link #INT}, {@link
56  * #DOUBLE}, and {@link #VOID}.
57  *
58  * <p>In an annotation processor you can get a type name instance for a type mirror by calling
59  * {@link #get(TypeMirror)}. In reflection code, you can use {@link #get(Type)}.
60  *
61  * <h3>Defining new types</h3>
62  *
63  * <p>Create new reference types like {@code com.example.HelloWorld} with {@link
64  * ClassName#get(String, String, String...)}. To build composite types like {@code char[]} and
65  * {@code Set<Long>}, use the factory methods on {@link ArrayTypeName}, {@link
66  * ParameterizedTypeName}, {@link TypeVariableName}, and {@link WildcardTypeName}.
67  */
68 public class TypeName {
69   public static final TypeName VOID = new TypeName("void");
70   public static final TypeName BOOLEAN = new TypeName("boolean");
71   public static final TypeName BYTE = new TypeName("byte");
72   public static final TypeName SHORT = new TypeName("short");
73   public static final TypeName INT = new TypeName("int");
74   public static final TypeName LONG = new TypeName("long");
75   public static final TypeName CHAR = new TypeName("char");
76   public static final TypeName FLOAT = new TypeName("float");
77   public static final TypeName DOUBLE = new TypeName("double");
78   public static final ClassName OBJECT = ClassName.get("java.lang", "Object");
79 
80   private static final ClassName BOXED_VOID = ClassName.get("java.lang", "Void");
81   private static final ClassName BOXED_BOOLEAN = ClassName.get("java.lang", "Boolean");
82   private static final ClassName BOXED_BYTE = ClassName.get("java.lang", "Byte");
83   private static final ClassName BOXED_SHORT = ClassName.get("java.lang", "Short");
84   private static final ClassName BOXED_INT = ClassName.get("java.lang", "Integer");
85   private static final ClassName BOXED_LONG = ClassName.get("java.lang", "Long");
86   private static final ClassName BOXED_CHAR = ClassName.get("java.lang", "Character");
87   private static final ClassName BOXED_FLOAT = ClassName.get("java.lang", "Float");
88   private static final ClassName BOXED_DOUBLE = ClassName.get("java.lang", "Double");
89 
90   /** The name of this type if it is a keyword, or null. */
91   private final String keyword;
92   public final List<AnnotationSpec> annotations;
93 
94   /** Lazily-initialized toString of this type name. */
95   private String cachedString;
96 
TypeName(String keyword)97   private TypeName(String keyword) {
98     this(keyword, new ArrayList<>());
99   }
100 
TypeName(String keyword, List<AnnotationSpec> annotations)101   private TypeName(String keyword, List<AnnotationSpec> annotations) {
102     this.keyword = keyword;
103     this.annotations = Util.immutableList(annotations);
104   }
105 
106   // Package-private constructor to prevent third-party subclasses.
TypeName(List<AnnotationSpec> annotations)107   TypeName(List<AnnotationSpec> annotations) {
108     this(null, annotations);
109   }
110 
annotated(AnnotationSpec... annotations)111   public final TypeName annotated(AnnotationSpec... annotations) {
112     return annotated(Arrays.asList(annotations));
113   }
114 
annotated(List<AnnotationSpec> annotations)115   public TypeName annotated(List<AnnotationSpec> annotations) {
116     Util.checkNotNull(annotations, "annotations == null");
117     return new TypeName(keyword, concatAnnotations(annotations));
118   }
119 
withoutAnnotations()120   public TypeName withoutAnnotations() {
121     return new TypeName(keyword);
122   }
123 
concatAnnotations(List<AnnotationSpec> annotations)124   protected final List<AnnotationSpec> concatAnnotations(List<AnnotationSpec> annotations) {
125     List<AnnotationSpec> allAnnotations = new ArrayList<>(this.annotations);
126     allAnnotations.addAll(annotations);
127     return allAnnotations;
128   }
129 
isAnnotated()130   public boolean isAnnotated() {
131     return !annotations.isEmpty();
132   }
133 
134   /**
135    * Returns true if this is a primitive type like {@code int}. Returns false for all other types
136    * types including boxed primitives and {@code void}.
137    */
isPrimitive()138   public boolean isPrimitive() {
139     return keyword != null && this != VOID;
140   }
141 
142   /**
143    * Returns true if this is a boxed primitive type like {@code Integer}. Returns false for all
144    * other types types including unboxed primitives and {@code java.lang.Void}.
145    */
isBoxedPrimitive()146   public boolean isBoxedPrimitive() {
147     return this.equals(BOXED_BOOLEAN)
148         || this.equals(BOXED_BYTE)
149         || this.equals(BOXED_SHORT)
150         || this.equals(BOXED_INT)
151         || this.equals(BOXED_LONG)
152         || this.equals(BOXED_CHAR)
153         || this.equals(BOXED_FLOAT)
154         || this.equals(BOXED_DOUBLE);
155   }
156 
157   /**
158    * Returns a boxed type if this is a primitive type (like {@code Integer} for {@code int}) or
159    * {@code void}. Returns this type if boxing doesn't apply.
160    */
box()161   public TypeName box() {
162     if (keyword == null) return this; // Doesn't need boxing.
163     if (this == VOID) return BOXED_VOID;
164     if (this == BOOLEAN) return BOXED_BOOLEAN;
165     if (this == BYTE) return BOXED_BYTE;
166     if (this == SHORT) return BOXED_SHORT;
167     if (this == INT) return BOXED_INT;
168     if (this == LONG) return BOXED_LONG;
169     if (this == CHAR) return BOXED_CHAR;
170     if (this == FLOAT) return BOXED_FLOAT;
171     if (this == DOUBLE) return BOXED_DOUBLE;
172     throw new AssertionError(keyword);
173   }
174 
175   /**
176    * Returns an unboxed type if this is a boxed primitive type (like {@code int} for {@code
177    * Integer}) or {@code Void}. Returns this type if it is already unboxed.
178    *
179    * @throws UnsupportedOperationException if this type isn't eligible for unboxing.
180    */
unbox()181   public TypeName unbox() {
182     if (keyword != null) return this; // Already unboxed.
183     if (this.equals(BOXED_VOID)) return VOID;
184     if (this.equals(BOXED_BOOLEAN)) return BOOLEAN;
185     if (this.equals(BOXED_BYTE)) return BYTE;
186     if (this.equals(BOXED_SHORT)) return SHORT;
187     if (this.equals(BOXED_INT)) return INT;
188     if (this.equals(BOXED_LONG)) return LONG;
189     if (this.equals(BOXED_CHAR)) return CHAR;
190     if (this.equals(BOXED_FLOAT)) return FLOAT;
191     if (this.equals(BOXED_DOUBLE)) return DOUBLE;
192     throw new UnsupportedOperationException("cannot unbox " + this);
193   }
194 
equals(Object o)195   @Override public final boolean equals(Object o) {
196     if (this == o) return true;
197     if (o == null) return false;
198     if (getClass() != o.getClass()) return false;
199     return toString().equals(o.toString());
200   }
201 
hashCode()202   @Override public final int hashCode() {
203     return toString().hashCode();
204   }
205 
toString()206   @Override public final String toString() {
207     String result = cachedString;
208     if (result == null) {
209       try {
210         StringBuilder resultBuilder = new StringBuilder();
211         CodeWriter codeWriter = new CodeWriter(resultBuilder);
212         emit(codeWriter);
213         result = resultBuilder.toString();
214         cachedString = result;
215       } catch (IOException e) {
216         throw new AssertionError();
217       }
218     }
219     return result;
220   }
221 
emit(CodeWriter out)222   CodeWriter emit(CodeWriter out) throws IOException {
223     if (keyword == null) throw new AssertionError();
224 
225     if (isAnnotated()) {
226       out.emit("");
227       emitAnnotations(out);
228     }
229     return out.emitAndIndent(keyword);
230   }
231 
emitAnnotations(CodeWriter out)232   CodeWriter emitAnnotations(CodeWriter out) throws IOException {
233     for (AnnotationSpec annotation : annotations) {
234       annotation.emit(out, true);
235       out.emit(" ");
236     }
237     return out;
238   }
239 
240 
241   /** Returns a type name equivalent to {@code mirror}. */
get(TypeMirror mirror)242   public static TypeName get(TypeMirror mirror) {
243     return get(mirror, new LinkedHashMap<>());
244   }
245 
get(TypeMirror mirror, final Map<TypeParameterElement, TypeVariableName> typeVariables)246   static TypeName get(TypeMirror mirror,
247       final Map<TypeParameterElement, TypeVariableName> typeVariables) {
248     return mirror.accept(new SimpleTypeVisitor8<TypeName, Void>() {
249       @Override public TypeName visitPrimitive(PrimitiveType t, Void p) {
250         switch (t.getKind()) {
251           case BOOLEAN:
252             return TypeName.BOOLEAN;
253           case BYTE:
254             return TypeName.BYTE;
255           case SHORT:
256             return TypeName.SHORT;
257           case INT:
258             return TypeName.INT;
259           case LONG:
260             return TypeName.LONG;
261           case CHAR:
262             return TypeName.CHAR;
263           case FLOAT:
264             return TypeName.FLOAT;
265           case DOUBLE:
266             return TypeName.DOUBLE;
267           default:
268             throw new AssertionError();
269         }
270       }
271 
272       @Override public TypeName visitDeclared(DeclaredType t, Void p) {
273         ClassName rawType = ClassName.get((TypeElement) t.asElement());
274         TypeMirror enclosingType = t.getEnclosingType();
275         TypeName enclosing =
276             (enclosingType.getKind() != TypeKind.NONE)
277                     && !t.asElement().getModifiers().contains(Modifier.STATIC)
278                 ? enclosingType.accept(this, null)
279                 : null;
280         if (t.getTypeArguments().isEmpty() && !(enclosing instanceof ParameterizedTypeName)) {
281           return rawType;
282         }
283 
284         List<TypeName> typeArgumentNames = new ArrayList<>();
285         for (TypeMirror mirror : t.getTypeArguments()) {
286           typeArgumentNames.add(get(mirror, typeVariables));
287         }
288         return enclosing instanceof ParameterizedTypeName
289             ? ((ParameterizedTypeName) enclosing).nestedClass(
290             rawType.simpleName(), typeArgumentNames)
291             : new ParameterizedTypeName(null, rawType, typeArgumentNames);
292       }
293 
294       @Override public TypeName visitError(ErrorType t, Void p) {
295         return visitDeclared(t, p);
296       }
297 
298       @Override public ArrayTypeName visitArray(ArrayType t, Void p) {
299         return ArrayTypeName.get(t, typeVariables);
300       }
301 
302       @Override public TypeName visitTypeVariable(javax.lang.model.type.TypeVariable t, Void p) {
303         return TypeVariableName.get(t, typeVariables);
304       }
305 
306       @Override public TypeName visitWildcard(javax.lang.model.type.WildcardType t, Void p) {
307         return WildcardTypeName.get(t, typeVariables);
308       }
309 
310       @Override public TypeName visitNoType(NoType t, Void p) {
311         if (t.getKind() == TypeKind.VOID) return TypeName.VOID;
312         return super.visitUnknown(t, p);
313       }
314 
315       @Override protected TypeName defaultAction(TypeMirror e, Void p) {
316         throw new IllegalArgumentException("Unexpected type mirror: " + e);
317       }
318     }, null);
319   }
320 
321   /** Returns a type name equivalent to {@code type}. */
322   public static TypeName get(Type type) {
323     return get(type, new LinkedHashMap<>());
324   }
325 
326   static TypeName get(Type type, Map<Type, TypeVariableName> map) {
327     if (type instanceof Class<?>) {
328       Class<?> classType = (Class<?>) type;
329       if (type == void.class) return VOID;
330       if (type == boolean.class) return BOOLEAN;
331       if (type == byte.class) return BYTE;
332       if (type == short.class) return SHORT;
333       if (type == int.class) return INT;
334       if (type == long.class) return LONG;
335       if (type == char.class) return CHAR;
336       if (type == float.class) return FLOAT;
337       if (type == double.class) return DOUBLE;
338       if (classType.isArray()) return ArrayTypeName.of(get(classType.getComponentType(), map));
339       return ClassName.get(classType);
340 
341     } else if (type instanceof ParameterizedType) {
342       return ParameterizedTypeName.get((ParameterizedType) type, map);
343 
344     } else if (type instanceof WildcardType) {
345       return WildcardTypeName.get((WildcardType) type, map);
346 
347     } else if (type instanceof TypeVariable<?>) {
348       return TypeVariableName.get((TypeVariable<?>) type, map);
349 
350     } else if (type instanceof GenericArrayType) {
351       return ArrayTypeName.get((GenericArrayType) type, map);
352 
353     } else {
354       throw new IllegalArgumentException("unexpected type: " + type);
355     }
356   }
357 
358   /** Converts an array of types to a list of type names. */
359   static List<TypeName> list(Type[] types) {
360     return list(types, new LinkedHashMap<>());
361   }
362 
363   static List<TypeName> list(Type[] types, Map<Type, TypeVariableName> map) {
364     List<TypeName> result = new ArrayList<>(types.length);
365     for (Type type : types) {
366       result.add(get(type, map));
367     }
368     return result;
369   }
370 
371   /** Returns the array component of {@code type}, or null if {@code type} is not an array. */
372   static TypeName arrayComponent(TypeName type) {
373     return type instanceof ArrayTypeName
374         ? ((ArrayTypeName) type).componentType
375         : null;
376   }
377 
378   /** Returns {@code type} as an array, or null if {@code type} is not an array. */
379   static ArrayTypeName asArray(TypeName type) {
380     return type instanceof ArrayTypeName
381         ? ((ArrayTypeName) type)
382         : null;
383   }
384 
385 }
386