All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tech.deplant.javapoet.ClassName Maven / Gradle / Ivy

The newest version!
/*
 * 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 tech.deplant.javapoet;

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 java.io.IOException;
import java.util.*;

/**
 * A fully-qualified class parameterName for top-level and member classes.
 */
public final class ClassName extends TypeName implements Comparable {
	/**
	 * The parameterName representing the default Java package.
	 */
	private static final String NO_PACKAGE = "";
	public static final ClassName OBJECT = ClassName.get(Object.class);
	/**
	 * The package parameterName 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 parameterName, like "Entry" for java.util.Map.Entry.
	 */
	final String simpleName;
	/**
	 * The full class parameterName like "java.util.Map.Entry".
	 */
	final String canonicalName;
	private List simpleNames;

	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 = Objects.requireNonNull(packageName, "packageName == null");
		this.enclosingClassName = enclosingClassName;
		this.simpleName = simpleName;
		this.canonicalName = enclosingClassName != null ? (enclosingClassName.canonicalName + '.' +
		                                                   simpleName) : (packageName.isEmpty() ? simpleName :
				packageName + '.' + simpleName);
	}

	public static ClassName get(Class clazz) {
		Util.checkNotNull(clazz, "clazz == null");
		Util.checkArgument(!clazz.isPrimitive(), "primitive types cannot be represented as a ClassName");
		Util.checkArgument(!void.class.equals(clazz), "'void' type cannot be represented as a ClassName");
		Util.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) : NO_PACKAGE;
			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 parameterName 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 parameterName, 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;
			Util.checkArgument(p != 0, "couldn't make a guess for %s", classNameString);
		}
		String packageName = p == 0 ? NO_PACKAGE : classNameString.substring(0, p - 1);

		// Add class names like "Map" and "Entry".
		ClassName className = null;
		for (String simpleName : classNameString.substring(p).split("\\.", -1)) {
			Util.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 parameterName created from the given parts. For example, calling this with package parameterName
	 * {@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 parameterName for {@code element}.
	 */
	public static ClassName get(TypeElement element) {
		Util.checkNotNull(element, "element == null");
		String simpleName = element.getSimpleName().toString();

		return element.getEnclosingElement().accept(new SimpleElementVisitor8() {
			@Override
			public ClassName visitUnknown(Element unknown, Void p) {
				return ClassName.get("", simpleName);
			}

			@Override
			public ClassName defaultAction(Element enclosingElement, Void p) {
				throw new IllegalArgumentException("Unexpected type nesting: " + element);
			}

			@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);
			}
		}, null);
	}

	@Override
	public ClassName annotated(List annotations) {
		return new ClassName(this.packageName,
		                     this.enclosingClassName,
		                     this.simpleName,
		                     concatAnnotations(annotations));
	}

	@Override
	public ClassName withoutAnnotations() {
		if (!isAnnotated()) {
			return this;
		}
		ClassName resultEnclosingClassName =
				this.enclosingClassName != null ? this.enclosingClassName.withoutAnnotations() : null;
		return new ClassName(this.packageName, resultEnclosingClassName, this.simpleName);
	}

	@Override
	public boolean isAnnotated() {
		return super.isAnnotated() || (this.enclosingClassName != null && this.enclosingClassName.isAnnotated());
	}

	@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 the package parameterName, like {@code "java.util"} for {@code Map.Entry}. Returns the empty
	 * string for the default package.
	 */
	public String packageName() {
		return this.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 this.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 this.enclosingClassName != null ? this.enclosingClassName.topLevelClassName() : this;
	}

	/**
	 * Return the binary parameterName of a class.
	 */
	public String reflectionName() {
		return this.enclosingClassName != null ? (this.enclosingClassName.reflectionName() + '$' +
		                                          this.simpleName) : (this.packageName.isEmpty() ? this.simpleName :
				this.packageName + '.' + this.simpleName);
	}

	public List simpleNames() {
		if (this.simpleNames != null) {
			return this.simpleNames;
		}

		if (this.enclosingClassName == null) {
			this.simpleNames = Collections.singletonList(this.simpleName);
		} else {
			List mutableNames = new ArrayList<>();
			mutableNames.addAll(enclosingClassName().simpleNames());
			mutableNames.add(this.simpleName);
			this.simpleNames = Collections.unmodifiableList(mutableNames);
		}
		return this.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(parameterName)}. Otherwise
	 * it is equivalent to {@code get(packageName(), parameterName)}.
	 */
	public ClassName peerClass(String name) {
		return new ClassName(this.packageName, this.enclosingClassName, name);
	}

	/**
	 * Returns a new {@link ClassName} instance for the specified {@code parameterName} as nested inside this
	 * class.
	 */
	public ClassName nestedClass(String name) {
		return new ClassName(this.packageName, this, name);
	}

	/**
	 * Returns the simple parameterName of this class, like {@code "Entry"} for {@link Map.Entry}.
	 */
	public String simpleName() {
		return this.simpleName;
	}

	/**
	 * Returns the full class parameterName of this class.
	 * Like {@code "java.util.Map.Entry"} for {@link Map.Entry}.
	 */
	public String canonicalName() {
		return this.canonicalName;
	}

	@Override
	public int compareTo(ClassName o) {
		return this.canonicalName.compareTo(o.canonicalName);
	}

	/**
	 * 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;
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy