/* * Copyright (C) 2014 Google, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.squareup.javapoet; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import javax.lang.model.element.Element; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.util.SimpleElementVisitor8; import static com.squareup.javapoet.Util.checkArgument; import static com.squareup.javapoet.Util.checkNotNull; /** A fully-qualified class name for top-level and member classes. */ public final class ClassName extends TypeName implements Comparable { public static final ClassName OBJECT = ClassName.get(Object.class); /** The package name of this class, or "" if this is in the default package. */ final String packageName; /** The enclosing class, or null if this is not enclosed in another class. */ final ClassName enclosingClassName; /** This class name, like "Entry" for java.util.Map.Entry. */ final String simpleName; /** The full class name like "java.util.Map.Entry". */ final String canonicalName; private ClassName(String packageName, ClassName enclosingClassName, String simpleName) { this(packageName, enclosingClassName, simpleName, Collections.emptyList()); } private ClassName(String packageName, ClassName enclosingClassName, String simpleName, List annotations) { super(annotations); this.packageName = packageName; this.enclosingClassName = enclosingClassName; this.simpleName = simpleName; this.canonicalName = enclosingClassName != null ? (enclosingClassName.canonicalName + '.' + simpleName) : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName); } @Override public ClassName annotated(List annotations) { return new ClassName(packageName, enclosingClassName, simpleName, concatAnnotations(annotations)); } @Override public ClassName withoutAnnotations() { if (!isAnnotated()) return this; ClassName resultEnclosingClassName = enclosingClassName != null ? enclosingClassName.withoutAnnotations() : null; return new ClassName(packageName, resultEnclosingClassName, simpleName); } @Override public boolean isAnnotated() { return super.isAnnotated() || (enclosingClassName != null && enclosingClassName.isAnnotated()); } /** * Returns the package name, like {@code "java.util"} for {@code Map.Entry}. Returns the empty * string for the default package. */ public String packageName() { return packageName; } /** * Returns the enclosing class, like {@link Map} for {@code Map.Entry}. Returns null if this class * is not nested in another class. */ public ClassName enclosingClassName() { return enclosingClassName; } /** * Returns the top class in this nesting group. Equivalent to chained calls to {@link * #enclosingClassName()} until the result's enclosing class is null. */ public ClassName topLevelClassName() { return enclosingClassName != null ? enclosingClassName.topLevelClassName() : this; } /** Return the binary name of a class. */ public String reflectionName() { return enclosingClassName != null ? (enclosingClassName.reflectionName() + '$' + simpleName) : (packageName.isEmpty() ? simpleName : packageName + '.' + simpleName); } public List simpleNames() { List simpleNames = new ArrayList<>(); if (enclosingClassName != null) { simpleNames.addAll(enclosingClassName().simpleNames()); } simpleNames.add(simpleName); return simpleNames; } /** * Returns a class that shares the same enclosing package or class. If this class is enclosed by * another class, this is equivalent to {@code enclosingClassName().nestedClass(name)}. Otherwise * it is equivalent to {@code get(packageName(), name)}. */ public ClassName peerClass(String name) { return new ClassName(packageName, enclosingClassName, name); } /** * Returns a new {@link ClassName} instance for the specified {@code name} as nested inside this * class. */ public ClassName nestedClass(String name) { return new ClassName(packageName, this, name); } /** Returns the simple name of this class, like {@code "Entry"} for {@link Map.Entry}. */ public String simpleName() { return simpleName; } public static ClassName get(Class clazz) { checkNotNull(clazz, "clazz == null"); checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName"); checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName"); checkArgument(!clazz.isArray(), "array types cannot be represented as a ClassName"); String anonymousSuffix = ""; while (clazz.isAnonymousClass()) { int lastDollar = clazz.getName().lastIndexOf('$'); anonymousSuffix = clazz.getName().substring(lastDollar) + anonymousSuffix; clazz = clazz.getEnclosingClass(); } String name = clazz.getSimpleName() + anonymousSuffix; if (clazz.getEnclosingClass() == null) { // Avoid unreliable Class.getPackage(). https://github.com/square/javapoet/issues/295 int lastDot = clazz.getName().lastIndexOf('.'); String packageName = (lastDot != -1) ? clazz.getName().substring(0, lastDot) : null; return new ClassName(packageName, null, name); } return ClassName.get(clazz.getEnclosingClass()).nestedClass(name); } /** * Returns a new {@link ClassName} instance for the given fully-qualified class name string. This * method assumes that the input is ASCII and follows typical Java style (lowercase package * names, UpperCamelCase class names) and may produce incorrect results or throw * {@link IllegalArgumentException} otherwise. For that reason, {@link #get(Class)} and * {@link #get(Class)} should be preferred as they can correctly create {@link ClassName} * instances without such restrictions. */ public static ClassName bestGuess(String classNameString) { // Add the package name, like "java.util.concurrent", or "" for no package. int p = 0; while (p < classNameString.length() && Character.isLowerCase(classNameString.codePointAt(p))) { p = classNameString.indexOf('.', p) + 1; checkArgument(p != 0, "couldn't make a guess for %s", classNameString); } String packageName = p == 0 ? "" : classNameString.substring(0, p - 1); // Add class names like "Map" and "Entry". ClassName className = null; for (String simpleName : classNameString.substring(p).split("\\.", -1)) { checkArgument(!simpleName.isEmpty() && Character.isUpperCase(simpleName.codePointAt(0)), "couldn't make a guess for %s", classNameString); className = new ClassName(packageName, className, simpleName); } return className; } /** * Returns a class name created from the given parts. For example, calling this with package name * {@code "java.util"} and simple names {@code "Map"}, {@code "Entry"} yields {@link Map.Entry}. */ public static ClassName get(String packageName, String simpleName, String... simpleNames) { ClassName className = new ClassName(packageName, null, simpleName); for (String name : simpleNames) { className = className.nestedClass(name); } return className; } /** Returns the class name for {@code element}. */ public static ClassName get(TypeElement element) { checkNotNull(element, "element == null"); String simpleName = element.getSimpleName().toString(); return element.getEnclosingElement().accept(new SimpleElementVisitor8() { @Override public ClassName visitPackage(PackageElement packageElement, Void p) { return new ClassName(packageElement.getQualifiedName().toString(), null, simpleName); } @Override public ClassName visitType(TypeElement enclosingClass, Void p) { return ClassName.get(enclosingClass).nestedClass(simpleName); } @Override public ClassName visitUnknown(Element unknown, Void p) { return get("", simpleName); } @Override public ClassName defaultAction(Element enclosingElement, Void p) { throw new IllegalArgumentException("Unexpected type nesting: " + element); } }, null); } @Override public int compareTo(ClassName o) { return canonicalName.compareTo(o.canonicalName); } @Override CodeWriter emit(CodeWriter out) throws IOException { boolean charsEmitted = false; for (ClassName className : enclosingClasses()) { String simpleName; if (charsEmitted) { // We've already emitted an enclosing class. Emit as we go. out.emit("."); simpleName = className.simpleName; } else if (className.isAnnotated() || className == this) { // We encountered the first enclosing class that must be emitted. String qualifiedName = out.lookupName(className); int dot = qualifiedName.lastIndexOf('.'); if (dot != -1) { out.emitAndIndent(qualifiedName.substring(0, dot + 1)); simpleName = qualifiedName.substring(dot + 1); charsEmitted = true; } else { simpleName = qualifiedName; } } else { // Don't emit this enclosing type. Keep going so we can be more precise. continue; } if (className.isAnnotated()) { if (charsEmitted) out.emit(" "); className.emitAnnotations(out); } out.emit(simpleName); charsEmitted = true; } return out; } /** Returns all enclosing classes in this, outermost first. */ private List enclosingClasses() { List result = new ArrayList<>(); for (ClassName c = this; c != null; c = c.enclosingClassName) { result.add(c); } Collections.reverse(result); return result; } }