/* * Copyright (C) 2015 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.google.callbuilder; import com.google.callbuilder.Unification.Sequence; import com.google.callbuilder.Unification.Substitution; import com.google.callbuilder.Unification.Unifiable; import com.google.callbuilder.Unification.Variable; import com.google.callbuilder.util.Preconditions; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.annotation.Nullable; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeParameterElement; import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.element.VariableElement; /** * Utility methods for inferring the types of things. */ final class TypeInference { private final AtomAndVarRegistry registry; private final Unifiable builderFieldType; TypeInference(AtomAndVarRegistry registry, Unifiable builderFieldType) { this.registry = Preconditions.checkNotNull(registry); this.builderFieldType = Preconditions.checkNotNull(builderFieldType); } private static Map<String, Variable> overridenTypeVariables(ExecutableElement method) { Map<String, Variable> variables = new HashMap<>(); for (TypeParameterElement typeParameter : method.getTypeParameters()) { variables.put(typeParameter.toString(), new Variable()); } return variables; } /** * Attempts to perform type inference for the field that has the given style. * @param fieldStyle the style of the field * @param parameter the original parameter on the annotated method for which the field is being * generated */ static @Nullable TypeInference forField(FieldStyle fieldStyle, VariableElement parameter) { List<Unifiable> lhs = new ArrayList<>(); List<Unifiable> rhs = new ArrayList<>(); AtomAndVarRegistry registry = new AtomAndVarRegistry(); Variable builderFieldType = new Variable(); Map<String, Variable> startOverridenTypeVariables = overridenTypeVariables(fieldStyle.start()); Map<String, Variable> finishOverridenTypeVariables = overridenTypeVariables(fieldStyle.finish()); // The generic type parameters of the start and finish method are actually variables in // unification, while type parameters of enclosing classes etc. are not. Override the // start/finish type parameters. // Each .add pair represents an equality constraint. // The return of start() must match the type of the builder field. lhs.add(registry.encode( fieldStyle.start().getReturnType(), startOverridenTypeVariables)); rhs.add(builderFieldType); // The parameter type of finish() must also match the type of the builder field. lhs.add(builderFieldType); List<? extends VariableElement> finishParameters = fieldStyle.finish().getParameters(); // TODO: report an error if the number of elements in finishParameters is not 1. rhs.add(registry.encode( finishParameters.get(0).asType(), finishOverridenTypeVariables)); // The return type of finish() must match the value expected by the annotated method. lhs.add(registry.encode( fieldStyle.finish().getReturnType(), finishOverridenTypeVariables)); rhs.add(registry.encode(parameter.asType(), Collections.<String, Variable>emptyMap())); Substitution result = Unification.unify(new Sequence(lhs), new Sequence(rhs)); if (result != null) { return new TypeInference(registry, result.resolve(builderFieldType)); } else { return null; } } /** * Returns the fully-qualified name of the field in the builder. This type is dictated by: * <ul> * <li>the type the annotated method expects in its parameter list * <li>the type the type returned by the {@link BuilderField} style's {@code start()} method * <li>the type accepted by the {@link BuilderField} style's {@code finish()} method * <li>the type returned by the {@link BuilderField} style's {@code finish()} method * </ul> * The {@code finish()} method can perform arbitrary changes to the builder field, so this is not * as simple as it first seems. For instance, the {@code finish()} method may convert a Guava * {@code Optional} to a nullable field, or vice-versa. */ String builderFieldType() { return registry.toType(builderFieldType); } /** * Returns the fully-qualified types of each parameter in the <em>generated</em> modifier, or * {@code null} if unification failed. */ @Nullable List<String> modifierParameterTypes(ExecutableElement modifier) { Map<String, Variable> overridenTypeVariables = overridenTypeVariables(modifier); Substitution result = Unification.unify( builderFieldType, registry.encode(modifier.getReturnType(), overridenTypeVariables)); if (result != null) { List<String> parameterTypes = new ArrayList<>(); List<? extends VariableElement> parameters = modifier.getParameters(); for (VariableElement parameter : parameters.subList(1, parameters.size())) { parameterTypes.add( registry.toType( result.resolve( registry.encode(parameter.asType(), overridenTypeVariables)))); } return parameterTypes; } return null; } }