/* * Copyright 2014 Google Inc. All rights reserved. * * 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 org.inferred.freebuilder.processor; import static com.google.common.base.Functions.toStringFunction; import static com.google.common.base.Objects.equal; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.Iterables.any; import static com.google.common.collect.Iterables.transform; import static com.google.common.collect.Iterables.tryFind; import static com.google.common.collect.Maps.newLinkedHashMap; import static javax.lang.model.element.ElementKind.INTERFACE; import static javax.lang.model.util.ElementFilter.constructorsIn; import static javax.lang.model.util.ElementFilter.typesIn; import static javax.tools.Diagnostic.Kind.ERROR; import static javax.tools.Diagnostic.Kind.NOTE; import static org.inferred.freebuilder.processor.BuilderFactory.NO_ARGS_CONSTRUCTOR; import static org.inferred.freebuilder.processor.GwtSupport.gwtMetadata; import static org.inferred.freebuilder.processor.MethodFinder.methodsOn; import static org.inferred.freebuilder.processor.naming.NamingConventions.determineNamingConvention; import static org.inferred.freebuilder.processor.util.ModelUtils.asElement; import static org.inferred.freebuilder.processor.util.ModelUtils.getReturnType; import static org.inferred.freebuilder.processor.util.ModelUtils.maybeAsTypeElement; import static org.inferred.freebuilder.processor.util.ModelUtils.maybeType; import com.google.common.base.Optional; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import org.inferred.freebuilder.processor.Metadata.Property; import org.inferred.freebuilder.processor.Metadata.StandardMethod; import org.inferred.freebuilder.processor.Metadata.UnderrideLevel; import org.inferred.freebuilder.processor.PropertyCodeGenerator.Config; import org.inferred.freebuilder.processor.naming.NamingConvention; import org.inferred.freebuilder.processor.util.ModelUtils; import org.inferred.freebuilder.processor.util.ParameterizedType; import org.inferred.freebuilder.processor.util.QualifiedName; import java.io.Serializable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.processing.Messager; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.lang.model.element.PackageElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.ErrorType; import javax.lang.model.type.PrimitiveType; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.WildcardType; import javax.lang.model.util.Elements; import javax.lang.model.util.SimpleTypeVisitor6; import javax.lang.model.util.Types; /** * Analyses a {@link org.inferred.freebuilder.FreeBuilder FreeBuilder} * type, returning metadata about it in a format amenable to code generation. * * <p>Any deviations from the FreeBuilder spec in the user's class will result in errors being * issued, but unless code generation is totally impossible, metadata will still be returned. * This allows the user to extend an existing type without worrying that a mistake will cause * compiler errors in all dependent code—which would make it very hard to find the real * error. */ class Analyser { /** * Thrown when a @FreeBuilder type cannot have a Builder type generated, for instance if * it is private. */ public static class CannotGenerateCodeException extends Exception { } /** * Factories of {@link PropertyCodeGenerator} instances. Note: order is important; the default * factory should always be last. */ private static final List<PropertyCodeGenerator.Factory> PROPERTY_FACTORIES = ImmutableList.of( new NullablePropertyFactory(), // Must be first, as no other factory supports nulls new ListPropertyFactory(), new SetPropertyFactory(), new SortedSetPropertyFactory(), new MapPropertyFactory(), new MultisetPropertyFactory(), new ListMultimapPropertyFactory(), new SetMultimapPropertyFactory(), new OptionalPropertyFactory(), new BuildablePropertyFactory(), new DefaultPropertyFactory()); // Must be last, as it will always return a CodeGenerator private static final String BUILDER_SIMPLE_NAME_TEMPLATE = "%s_Builder"; private static final String USER_BUILDER_NAME = "Builder"; private final Elements elements; private final Messager messager; private final MethodIntrospector methodIntrospector; private final Types types; Analyser( Elements elements, Messager messager, MethodIntrospector methodIntrospector, Types types) { this.elements = elements; this.messager = messager; this.methodIntrospector = methodIntrospector; this.types = types; } /** * Returns a {@link Metadata} metadata object for {@code type}. * * @throws CannotGenerateCodeException if code cannot be generated, e.g. if the type is private */ Metadata analyse(TypeElement type) throws CannotGenerateCodeException { PackageElement pkg = elements.getPackageOf(type); verifyType(type, pkg); ImmutableSet<ExecutableElement> methods = methodsOn(type, elements); QualifiedName generatedBuilder = QualifiedName.of( pkg.getQualifiedName().toString(), generatedBuilderSimpleName(type)); Optional<TypeElement> builder = tryFindBuilder(generatedBuilder, type); Optional<BuilderFactory> builderFactory = builderFactory(builder); QualifiedName valueType = generatedBuilder.nestedType("Value"); QualifiedName partialType = generatedBuilder.nestedType("Partial"); QualifiedName propertyType = generatedBuilder.nestedType("Property"); List<? extends TypeParameterElement> typeParameters = type.getTypeParameters(); Map<ExecutableElement, Property> properties = findProperties(type, removeNonGetterMethods(builder, methods)); Metadata.Builder metadataBuilder = new Metadata.Builder() .setType(QualifiedName.of(type).withParameters(typeParameters)) .setInterfaceType(type.getKind().isInterface()) .setBuilder(parameterized(builder, typeParameters)) .setBuilderFactory(builderFactory) .setGeneratedBuilder(generatedBuilder.withParameters(typeParameters)) .setValueType(valueType.withParameters(typeParameters)) .setPartialType(partialType.withParameters(typeParameters)) .setPropertyEnum(propertyType.withParameters()) .addVisibleNestedTypes(valueType) .addVisibleNestedTypes(partialType) .addVisibleNestedTypes(propertyType) .addAllVisibleNestedTypes(visibleTypesIn(type)) // Because we inherit from type .putAllStandardMethodUnderrides(findUnderriddenMethods(methods)) .setHasToBuilderMethod(hasToBuilderMethod(builder, builderFactory, methods)) .setBuilderSerializable(shouldBuilderBeSerializable(builder)) .addAllProperties(properties.values()); Metadata baseMetadata = metadataBuilder.build(); metadataBuilder.mergeFrom(gwtMetadata(type, baseMetadata)); if (builder.isPresent()) { metadataBuilder .clearProperties() .addAllProperties(codeGenerators(properties, baseMetadata, builder.get())); } return metadataBuilder.build(); } private static Set<QualifiedName> visibleTypesIn(TypeElement type) { ImmutableSet.Builder<QualifiedName> visibleTypes = ImmutableSet.builder(); for (TypeElement nestedType : typesIn(type.getEnclosedElements())) { visibleTypes.add(QualifiedName.of(nestedType)); } visibleTypes.addAll(visibleTypesIn(maybeType(type.getEnclosingElement()))); visibleTypes.addAll(visibleTypesIn(maybeAsTypeElement(type.getSuperclass()))); return visibleTypes.build(); } private static Set<QualifiedName> visibleTypesIn(Optional<TypeElement> type) { if (!type.isPresent()) { return ImmutableSet.of(); } else { return visibleTypesIn(type.get()); } } /** Basic sanity-checking to ensure we can fulfil the @FreeBuilder contract for this type. */ private void verifyType(TypeElement type, PackageElement pkg) throws CannotGenerateCodeException { if (pkg.isUnnamed()) { messager.printMessage(ERROR, "@FreeBuilder does not support types in unnamed packages", type); throw new CannotGenerateCodeException(); } switch (type.getNestingKind()) { case TOP_LEVEL: break; case MEMBER: if (!type.getModifiers().contains(Modifier.STATIC)) { messager.printMessage( ERROR, "Inner classes cannot be @FreeBuilder types (did you forget the static keyword?)", type); throw new CannotGenerateCodeException(); } if (type.getModifiers().contains(Modifier.PRIVATE)) { messager.printMessage(ERROR, "@FreeBuilder types cannot be private", type); throw new CannotGenerateCodeException(); } for (Element e = type.getEnclosingElement(); e != null; e = e.getEnclosingElement()) { if (e.getModifiers().contains(Modifier.PRIVATE)) { messager.printMessage( ERROR, "@FreeBuilder types cannot be private, but enclosing type " + e.getSimpleName() + " is inaccessible", type); throw new CannotGenerateCodeException(); } } break; default: messager.printMessage( ERROR, "Only top-level or static nested types can be @FreeBuilder types", type); throw new CannotGenerateCodeException(); } switch (type.getKind()) { case ANNOTATION_TYPE: messager.printMessage(ERROR, "@FreeBuilder does not support annotation types", type); throw new CannotGenerateCodeException(); case CLASS: verifyTypeIsConstructible(type); break; case ENUM: messager.printMessage(ERROR, "@FreeBuilder does not support enum types", type); throw new CannotGenerateCodeException(); case INTERFACE: // Nothing extra needs to be checked on an interface break; default: throw new AssertionError("Unexpected element kind " + type.getKind()); } } /** Issues an error if {@code type} does not have a package-visible no-args constructor. */ private void verifyTypeIsConstructible(TypeElement type) throws CannotGenerateCodeException { List<ExecutableElement> constructors = constructorsIn(type.getEnclosedElements()); if (constructors.isEmpty()) { return; } for (ExecutableElement constructor : constructors) { if (constructor.getParameters().isEmpty()) { if (constructor.getModifiers().contains(Modifier.PRIVATE)) { messager.printMessage( ERROR, "@FreeBuilder types must have a package-visible no-args constructor", constructor); throw new CannotGenerateCodeException(); } return; } } messager.printMessage( ERROR, "@FreeBuilder types must have a package-visible no-args constructor", type); throw new CannotGenerateCodeException(); } /** Find any standard methods the user has 'underridden' in their type. */ private Map<StandardMethod, UnderrideLevel> findUnderriddenMethods( Iterable<ExecutableElement> methods) { Map<StandardMethod, ExecutableElement> standardMethods = new LinkedHashMap<StandardMethod, ExecutableElement>(); for (ExecutableElement method : methods) { Optional<StandardMethod> standardMethod = maybeStandardMethod(method); if (standardMethod.isPresent() && isUnderride(method)) { standardMethods.put(standardMethod.get(), method); } } if (standardMethods.containsKey(StandardMethod.EQUALS) != standardMethods.containsKey(StandardMethod.HASH_CODE)) { ExecutableElement underriddenMethod = standardMethods.containsKey(StandardMethod.EQUALS) ? standardMethods.get(StandardMethod.EQUALS) : standardMethods.get(StandardMethod.HASH_CODE); messager.printMessage(ERROR, "hashCode and equals must be implemented together on @FreeBuilder types", underriddenMethod); } ImmutableMap.Builder<StandardMethod, UnderrideLevel> result = ImmutableMap.builder(); for (StandardMethod standardMethod : standardMethods.keySet()) { if (standardMethods.get(standardMethod).getModifiers().contains(Modifier.FINAL)) { result.put(standardMethod, UnderrideLevel.FINAL); } else { result.put(standardMethod, UnderrideLevel.OVERRIDEABLE); } } return result.build(); } /** Find a toBuilder method, if the user has provided one. * @param builderFactory */ private boolean hasToBuilderMethod( Optional<TypeElement> builder, Optional<BuilderFactory> builderFactory, Iterable<ExecutableElement> methods) { if (!builder.isPresent()) { return false; } for (ExecutableElement method : methods) { if (isToBuilderMethod(builder.get(), method)) { if (!builderFactory.isPresent()) { messager.printMessage(ERROR, "No accessible no-args Builder constructor available to implement toBuilder", method); } return true; } } return false; } private static boolean isToBuilderMethod(TypeElement builder, ExecutableElement method) { if (method.getSimpleName().contentEquals("toBuilder") && method.getModifiers().contains(Modifier.ABSTRACT) && method.getParameters().isEmpty()) { Optional<TypeElement> returnType = ModelUtils.maybeAsTypeElement(method.getReturnType()); if (returnType.isPresent() && returnType.get().equals(builder)) { return true; } } return false; } private static Set<ExecutableElement> removeNonGetterMethods( Optional<TypeElement> builder, Iterable<ExecutableElement> methods) { ImmutableSet.Builder<ExecutableElement> nonUnderriddenMethods = ImmutableSet.builder(); for (ExecutableElement method : methods) { boolean isAbstract = method.getModifiers().contains(Modifier.ABSTRACT); boolean isStandardMethod = maybeStandardMethod(method).isPresent(); boolean isToBuilderMethod = builder.isPresent() && isToBuilderMethod(builder.get(), method); if (isAbstract && !isStandardMethod && !isToBuilderMethod) { nonUnderriddenMethods.add(method); } } return nonUnderriddenMethods.build(); } private static boolean isUnderride(ExecutableElement method) { return !method.getModifiers().contains(Modifier.ABSTRACT); } /** * Looks for a type called Builder, and verifies it extends the autogenerated superclass. Issues * an error if the wrong type is being subclassed—a typical copy-and-paste error when * renaming an existing @FreeBuilder type, or using one as a template. */ private Optional<TypeElement> tryFindBuilder( final QualifiedName generatedBuilder, TypeElement type) { Optional<TypeElement> userClass = tryFind(typesIn(type.getEnclosedElements()), new Predicate<Element>() { @Override public boolean apply(Element input) { return input.getSimpleName().contentEquals(USER_BUILDER_NAME); } }); if (!userClass.isPresent()) { if (type.getKind() == INTERFACE) { messager.printMessage( NOTE, "Add \"class Builder extends " + generatedBuilder.getSimpleName() + " {}\" to your interface to enable the @FreeBuilder API", type); } else { messager.printMessage( NOTE, "Add \"public static class Builder extends " + generatedBuilder.getSimpleName() + " {}\" to your class to enable the @FreeBuilder API", type); } return Optional.absent(); } boolean extendsSuperclass = new IsSubclassOfGeneratedTypeVisitor(generatedBuilder, type.getTypeParameters()) .visit(userClass.get().getSuperclass()); if (!extendsSuperclass) { messager.printMessage( ERROR, "Builder extends the wrong type (should be " + generatedBuilder.getSimpleName() + ")", userClass.get()); return Optional.absent(); } return userClass; } private Optional<BuilderFactory> builderFactory(Optional<TypeElement> builder) { if (!builder.isPresent()) { return Optional.of(NO_ARGS_CONSTRUCTOR); } if (!builder.get().getModifiers().contains(Modifier.STATIC)) { messager.printMessage(ERROR, "Builder must be static on @FreeBuilder types", builder.get()); return Optional.absent(); } return BuilderFactory.from(builder.get()); } private Map<ExecutableElement, Property> findProperties( TypeElement type, Iterable<ExecutableElement> methods) { NamingConvention namingConvention = determineNamingConvention(type, methods, messager, types); Map<ExecutableElement, Property> propertiesByMethod = newLinkedHashMap(); Optional<JacksonSupport> jacksonSupport = JacksonSupport.create(type); for (ExecutableElement method : methods) { Property.Builder propertyBuilder = namingConvention.getPropertyNames(type, method).orNull(); if (propertyBuilder != null) { addPropertyData(propertyBuilder, type, method, jacksonSupport); propertiesByMethod.put(method, propertyBuilder.build()); } } return propertiesByMethod; } private List<Property> codeGenerators( Map<ExecutableElement, Property> properties, Metadata metadata, TypeElement builder) { ImmutableList.Builder<Property> codeGenerators = ImmutableList.builder(); Set<String> methodsInvokedInBuilderConstructor = getMethodsInvokedInBuilderConstructor(builder); for (Map.Entry<ExecutableElement, Property> entry : properties.entrySet()) { Config config = new ConfigImpl( builder, metadata, entry.getValue(), entry.getKey(), methodsInvokedInBuilderConstructor); codeGenerators.add(new Property.Builder() .mergeFrom(entry.getValue()) .setCodeGenerator(createCodeGenerator(config)) .build()); } return codeGenerators.build(); } private Set<String> getMethodsInvokedInBuilderConstructor(TypeElement builder) { List<ExecutableElement> constructors = constructorsIn(builder.getEnclosedElements()); Set<Name> result = null; for (ExecutableElement constructor : constructors) { if (result == null) { result = methodIntrospector.getOwnMethodInvocations(constructor); } else { result = Sets.intersection(result, methodIntrospector.getOwnMethodInvocations(constructor)); } } return ImmutableSet.copyOf(transform(result, toStringFunction())); } /** * Introspects {@code method}, as found on {@code valueType}. */ private void addPropertyData( Property.Builder propertyBuilder, TypeElement valueType, ExecutableElement method, Optional<JacksonSupport> jacksonSupport) { TypeMirror propertyType = getReturnType(valueType, method, types); propertyBuilder .setAllCapsName(camelCaseToAllCaps(propertyBuilder.getName())) .setType(propertyType) .setFullyCheckedCast(CAST_IS_FULLY_CHECKED.visit(propertyType)); if (jacksonSupport.isPresent()) { jacksonSupport.get().addJacksonAnnotations(propertyBuilder, method); } if (propertyType.getKind().isPrimitive()) { PrimitiveType unboxedType = types.getPrimitiveType(propertyType.getKind()); TypeMirror boxedType = types.erasure(types.boxedClass(unboxedType).asType()); propertyBuilder.setBoxedType(boxedType); } } private static PropertyCodeGenerator createCodeGenerator(Config config) { for (PropertyCodeGenerator.Factory factory : PROPERTY_FACTORIES) { Optional<? extends PropertyCodeGenerator> codeGenerator = factory.create(config); if (codeGenerator.isPresent()) { return codeGenerator.get(); } } throw new AssertionError("DefaultPropertyFactory not registered"); } private class ConfigImpl implements Config { private final TypeElement builder; private final Metadata metadata; private final Property property; private final ExecutableElement getterMethod; private final Set<String> methodsInvokedInBuilderConstructor; ConfigImpl( TypeElement builder, Metadata metadata, Property property, ExecutableElement getterMethod, Set<String> methodsInvokedInBuilderConstructor) { this.builder = builder; this.metadata = metadata; this.property = property; this.getterMethod = getterMethod; this.methodsInvokedInBuilderConstructor = methodsInvokedInBuilderConstructor; } @Override public TypeElement getBuilder() { return builder; } @Override public Metadata getMetadata() { return metadata; } @Override public Property getProperty() { return property; } @Override public List<? extends AnnotationMirror> getAnnotations() { return getterMethod.getAnnotationMirrors(); } @Override public Set<String> getMethodsInvokedInBuilderConstructor() { return methodsInvokedInBuilderConstructor; } @Override public Elements getElements() { return elements; } @Override public Types getTypes() { return types; } } /** * Visitor that returns true if a cast to the visited type is guaranteed to be fully checked at * runtime. This is true for any type that is non-generic, raw, or parameterized with unbounded * wildcards, such as {@code Integer}, {@code List} or {@code Map<?, ?>}. */ private static final SimpleTypeVisitor6<Boolean, ?> CAST_IS_FULLY_CHECKED = new SimpleTypeVisitor6<Boolean, Void>() { @Override public Boolean visitArray(ArrayType t, Void p) { return visit(t.getComponentType()); } @Override public Boolean visitDeclared(DeclaredType t, Void p) { for (TypeMirror argument : t.getTypeArguments()) { if (!IS_UNBOUNDED_WILDCARD.visit(argument)) { return false; } } return true; } @Override protected Boolean defaultAction(TypeMirror e, Void p) { return true; } }; /** * Visitor that returns true if the visited type is an unbounded wildcard, i.e. {@code <?>}. */ private static final SimpleTypeVisitor6<Boolean, ?> IS_UNBOUNDED_WILDCARD = new SimpleTypeVisitor6<Boolean, Void>() { @Override public Boolean visitWildcard(WildcardType t, Void p) { return t.getExtendsBound() == null || t.getExtendsBound().toString().equals("java.lang.Object"); } @Override protected Boolean defaultAction(TypeMirror e, Void p) { return false; } }; /** * Returns the simple name of the builder class that should be generated for the given type. * * <p>This is simply the {@link #BUILDER_SIMPLE_NAME_TEMPLATE} with the original type name * substituted in. (If the original type is nested, its enclosing classes will be included, * separated with underscores, to ensure uniqueness.) */ private String generatedBuilderSimpleName(TypeElement type) { String packageName = elements.getPackageOf(type).getQualifiedName().toString(); String originalName = type.getQualifiedName().toString(); checkState(originalName.startsWith(packageName + ".")); String nameWithoutPackage = originalName.substring(packageName.length() + 1); return String.format(BUILDER_SIMPLE_NAME_TEMPLATE, nameWithoutPackage.replaceAll("\\.", "_")); } private boolean shouldBuilderBeSerializable(Optional<TypeElement> builder) { if (!builder.isPresent()) { // If there's no user-provided subclass, make the builder serializable. return true; } // If there is a user-provided subclass, only make its generated superclass serializable if // it is itself; otherwise, tools may complain about missing a serialVersionUID field. return any(builder.get().getInterfaces(), isEqualTo(Serializable.class)); } /** Returns whether a method is one of the {@link StandardMethod}s, and if so, which. */ private static Optional<StandardMethod> maybeStandardMethod(ExecutableElement method) { String methodName = method.getSimpleName().toString(); if (methodName.equals("equals")) { if (method.getParameters().size() == 1 && method.getParameters().get(0).asType().toString().equals("java.lang.Object")) { return Optional.of(StandardMethod.EQUALS); } else { return Optional.absent(); } } else if (methodName.equals("hashCode")) { if (method.getParameters().isEmpty()) { return Optional.of(StandardMethod.HASH_CODE); } else { return Optional.absent(); } } else if (methodName.equals("toString")) { if (method.getParameters().isEmpty()) { return Optional.of(StandardMethod.TO_STRING); } else { return Optional.absent(); } } else { return Optional.absent(); } } /** * Visitor that returns true if the visited type extends a generated {@code superclass} in the * same package. */ private static final class IsSubclassOfGeneratedTypeVisitor extends SimpleTypeVisitor6<Boolean, Void> { private final QualifiedName superclass; private final List<? extends TypeParameterElement> typeParameters; private IsSubclassOfGeneratedTypeVisitor( QualifiedName superclass, List<? extends TypeParameterElement> typeParameters) { super(false); this.superclass = superclass; this.typeParameters = typeParameters; } /** * Any reference to the as-yet-ungenerated builder should be an unresolved ERROR. * Similarly for many copy-and-paste errors */ @Override public Boolean visitError(ErrorType t, Void p) { if (typeParameters.isEmpty()) { // For non-generic types, the ErrorType will have the correct name. String simpleName = t.toString(); return equal(simpleName, superclass.getSimpleName()); } // For generic types, we'll just have to hope for the best. // TODO: Revalidate in a subsequent round? return true; } /** * However, with some setups (e.g. Eclipse+blaze), the builder may have already been * generated and provided via a jar, in which case the reference will be DECLARED and * qualified. We still want to generate it. */ @Override public Boolean visitDeclared(DeclaredType t, Void p) { return asElement(t).getQualifiedName().contentEquals(superclass.toString()); } } /** Converts camelCaseConvention to ALL_CAPS_CONVENTION. */ private static String camelCaseToAllCaps(String camelCase) { // The first half of the pattern spots lowercase to uppercase boundaries. // The second half spots the end of uppercase sequences, like "URL" in "myURLShortener". return camelCase.replaceAll("(?<=[^A-Z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][^A-Z])", "_") .toUpperCase(); } private Predicate<TypeMirror> isEqualTo(Class<?> cls) { final TypeMirror typeMirror = elements.getTypeElement(cls.getCanonicalName()).asType(); return new Predicate<TypeMirror>() { @Override public boolean apply(TypeMirror input) { return types.isSameType(input, typeMirror); } }; } private static Optional<ParameterizedType> parameterized( Optional<TypeElement> type, List<? extends TypeParameterElement> typeParameters) { if (!type.isPresent()) { return Optional.absent(); } return Optional.of(QualifiedName.of(type.get()).withParameters(typeParameters)); } }