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