/*
* Copyright 2015 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.util;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.collect.Iterables.getOnlyElement;
import static com.google.common.collect.Iterables.transform;
import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static org.inferred.freebuilder.processor.util.ModelUtils.maybeAsTypeElement;
import static org.inferred.freebuilder.processor.util.feature.SourceLevel.diamondOperator;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableList;
import org.inferred.freebuilder.processor.util.feature.StaticFeatureSet;
import java.util.List;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.TypeParameterElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleElementVisitor6;
import javax.lang.model.util.SimpleTypeVisitor6;
public class ParameterizedType extends Excerpt {
public static ParameterizedType from(TypeElement typeElement) {
return new ParameterisedTypeForElementVisitor().visitType(typeElement, null);
}
public static ParameterizedType from(DeclaredType declaredType) {
return new ParameterisedTypeForMirrorVisitor().visitDeclared(declaredType, null);
}
public static ParameterizedType from(Class<?> cls) {
return new ParameterizedType(QualifiedName.of(cls), asList(cls.getTypeParameters()));
}
private final QualifiedName qualifiedName;
private final List<?> typeParameters;
ParameterizedType(QualifiedName qualifiedName, List<?> typeParameters) {
this.qualifiedName = checkNotNull(qualifiedName);
this.typeParameters = checkNotNull(typeParameters);
}
public String getSimpleName() {
return qualifiedName.getSimpleName();
}
public QualifiedName getQualifiedName() {
return qualifiedName;
}
public boolean isParameterized() {
return !typeParameters.isEmpty();
}
@Override
public void addTo(SourceBuilder source) {
source.add("%s%s", qualifiedName, typeParameters());
}
/**
* Returns a new {@link ParameterizedType} of the same length as this type, filled with
* {@code parameters}.
*/
public ParameterizedType withParameters(TypeMirror... parameters) {
checkArgument(typeParameters.size() == parameters.length,
"Need %s parameters for %s but got %s", typeParameters.size(), this, parameters.length);
return new ParameterizedType(qualifiedName, ImmutableList.copyOf(parameters));
}
/**
* Returns a new {@link ParameterizedType} of the same length as this type, filled with wildcards
* ("?").
*/
public ParameterizedType withWildcards() {
if (typeParameters.isEmpty()) {
return this;
}
return new ParameterizedType(qualifiedName, nCopies(typeParameters.size(), "?"));
}
/**
* Returns a source excerpt suitable for constructing an instance of this type, including "new"
* keyword but excluding brackets.
*
* <p>In Java 7+, we can use the diamond operator. Otherwise, we write out the type parameters
* in full.
*/
public Excerpt constructor() {
return Excerpts.add("new %s%s", qualifiedName, typeParametersOrDiamondOperator());
}
/**
* Returns a source excerpt suitable for declaring this type, i.e. {@code SimpleName<...>}
*/
public Excerpt declaration() {
return Excerpts.add("%s%s", qualifiedName.getSimpleName(), declarationParameters());
}
/**
* Returns a source excerpt of the type parameters of this type, including angle brackets, unless
* the diamond operator is available, in which case only the angle brackets will be emitted.
*/
public Excerpt typeParametersOrDiamondOperator() {
return isParameterized()
? diamondOperator(Excerpts.join(", ", typeParameters))
: Excerpts.empty();
}
/**
* Returns a source excerpt of the type parameters of this type, including angle brackets.
*/
public Excerpt typeParameters() {
if (typeParameters.isEmpty()) {
return Excerpts.empty();
} else {
return Excerpts.add("<%s>", Excerpts.join(", ", typeParameters));
}
}
/**
* Returns a source excerpt of the type parameters of this type, including bounds and angle
* brackets.
*/
public Excerpt declarationParameters() {
return new DeclarationParameters(typeParameters);
}
/**
* Returns a source excerpt of a JavaDoc link to this type.
*/
public Excerpt javadocLink() {
return Excerpts.add("{@link %s}", getQualifiedName());
}
/**
* Returns a source excerpt of a JavaDoc link to a no-args method on this type.
*/
public Excerpt javadocNoArgMethodLink(final String memberName) {
return Excerpts.add("{@link %s#%s()}", getQualifiedName(), memberName);
}
@Override
public String toString() {
// Only used when debugging, so an empty feature set is fine.
return new SourceStringBuilder(new TypeShortener.NeverShorten(), new StaticFeatureSet())
.add(this)
.toString();
}
@Override
protected void addFields(FieldReceiver fields) {
fields.add("qualifiedName", qualifiedName);
fields.add("typeParameters", typeParameters);
}
private final class DeclarationParameters extends Excerpt {
private final List<?> typeParameters;
DeclarationParameters(List<?> typeParameters) {
this.typeParameters = typeParameters;
}
@Override public void addTo(SourceBuilder source) {
if (!typeParameters.isEmpty()) {
String prefix = "<";
for (Object typeParameter : typeParameters) {
source.add("%s%s", prefix, typeParameter);
if (typeParameter instanceof TypeParameterElement) {
TypeParameterElement element = (TypeParameterElement) typeParameter;
if (!extendsObject(element)) {
String separator = " extends ";
for (TypeMirror bound : element.getBounds()) {
source.add("%s%s", separator, bound);
separator = " & ";
}
}
}
prefix = ", ";
}
source.add(">");
}
}
@Override
protected void addFields(FieldReceiver fields) {
fields.add("typeParameters", typeParameters);
}
}
private static boolean extendsObject(TypeParameterElement element) {
if (element.getBounds().size() != 1) {
return false;
}
TypeElement bound = maybeAsTypeElement(getOnlyElement(element.getBounds())).orNull();
if (bound == null) {
return false;
}
return bound.getQualifiedName().contentEquals(Object.class.getName());
}
private static final class ParameterisedTypeForElementVisitor
extends SimpleElementVisitor6<Object, Void> implements Function<Element, Object> {
@Override
public Object apply(Element e) {
return this.visit(e, null);
}
@Override
public ParameterizedType visitType(TypeElement e, Void p) {
return new ParameterizedType(
QualifiedName.of(e),
ImmutableList.copyOf(transform(e.getTypeParameters(), this)));
}
@Override
protected Object defaultAction(Element e, Void p) {
return e;
}
}
private static final class ParameterisedTypeForMirrorVisitor
extends SimpleTypeVisitor6<Object, Void> implements Function<TypeMirror, Object> {
@Override
public Object apply(TypeMirror t) {
return this.visit(t);
}
@Override
public ParameterizedType visitDeclared(DeclaredType t, Void p) {
return new ParameterizedType(
QualifiedName.of((TypeElement) t.asElement()),
ImmutableList.copyOf(transform(t.getTypeArguments(), this)));
}
@Override
protected Object defaultAction(TypeMirror e, Void p) {
return e;
}
}
}