/*
* Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.dsl.processor.parser;
import static com.oracle.truffle.dsl.processor.java.ElementUtils.hasError;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.ArrayType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
import com.oracle.truffle.dsl.processor.java.model.CodeVariableElement;
import com.oracle.truffle.dsl.processor.model.MethodSpec;
import com.oracle.truffle.dsl.processor.model.Parameter;
import com.oracle.truffle.dsl.processor.model.ParameterSpec;
import com.oracle.truffle.dsl.processor.model.Template;
import com.oracle.truffle.dsl.processor.model.TemplateMethod;
public final class MethodSpecParser {
private boolean emitErrors = true;
private boolean useVarArgs = false;
private final Template template;
public MethodSpecParser(Template template) {
this.template = template;
}
public Template getTemplate() {
return template;
}
public boolean isEmitErrors() {
return emitErrors;
}
public boolean isUseVarArgs() {
return useVarArgs;
}
public void setEmitErrors(boolean emitErrors) {
this.emitErrors = emitErrors;
}
public void setUseVarArgs(boolean useVarArgs) {
this.useVarArgs = useVarArgs;
}
public TemplateMethod parse(MethodSpec methodSpecification, ExecutableElement method, AnnotationMirror annotation, int naturalOrder) {
if (methodSpecification == null) {
return null;
}
methodSpecification.applyTypeDefinitions("types");
String id = method.getSimpleName().toString();
TypeMirror returnType = method.getReturnType();
return parseImpl(methodSpecification, naturalOrder, id, method, annotation, returnType, method.getParameters());
}
public TemplateMethod parseImpl(MethodSpec methodSpecification, int naturalOrder, String id, ExecutableElement method, AnnotationMirror annotation, TypeMirror returnType,
List<? extends VariableElement> parameterTypes) {
ParameterSpec returnTypeSpec = methodSpecification.getReturnType();
Parameter returnTypeMirror = matchParameter(returnTypeSpec, new CodeVariableElement(returnType, "returnType"), -1, -1);
if (returnTypeMirror == null) {
if (isEmitErrors() && method != null) {
TemplateMethod invalidMethod = new TemplateMethod(id, naturalOrder, template, methodSpecification, method, annotation, returnTypeMirror, Collections.<Parameter> emptyList());
String expectedReturnType = returnTypeSpec.toSignatureString(true);
String actualReturnType = ElementUtils.getSimpleName(returnType);
String message = String.format("The provided return type \"%s\" does not match expected return type \"%s\".\nExpected signature: \n %s", actualReturnType, expectedReturnType,
methodSpecification.toSignatureString(method.getSimpleName().toString()));
invalidMethod.addError(message);
return invalidMethod;
} else {
return null;
}
}
List<Parameter> parameters = parseParameters(methodSpecification, parameterTypes, isUseVarArgs() && method != null ? method.isVarArgs() : false);
if (parameters == null) {
if (isEmitErrors() && method != null) {
TemplateMethod invalidMethod = new TemplateMethod(id, naturalOrder, template, methodSpecification, method, annotation, returnTypeMirror, Collections.<Parameter> emptyList());
String message = String.format("Method signature %s does not match to the expected signature: \n%s", createActualSignature(method),
methodSpecification.toSignatureString(method.getSimpleName().toString()));
invalidMethod.addError(message);
return invalidMethod;
} else {
return null;
}
}
return new TemplateMethod(id, naturalOrder, template, methodSpecification, method, annotation, returnTypeMirror, parameters);
}
private static String createActualSignature(ExecutableElement method) {
StringBuilder b = new StringBuilder("(");
String sep = "";
if (method != null) {
for (VariableElement var : method.getParameters()) {
b.append(sep);
b.append(ElementUtils.getSimpleName(var.asType()));
sep = ", ";
}
}
b.append(")");
return b.toString();
}
/*
* Parameter parsing tries to parse required arguments starting from offset 0 with increasing
* offset until it finds a signature end that matches the required specification. If there is no
* end matching the required arguments, parsing fails. Parameters prior to the parsed required
* ones are cut and used to parse the optional parameters.
*/
private static List<Parameter> parseParameters(MethodSpec spec, List<? extends VariableElement> parameterTypes, boolean varArgs) {
List<Parameter> parsedRequired = null;
int offset = 0;
for (; offset <= parameterTypes.size(); offset++) {
List<VariableElement> parameters = new ArrayList<>();
parameters.addAll(parameterTypes.subList(offset, parameterTypes.size()));
parsedRequired = parseParametersRequired(spec, parameters, varArgs);
if (parsedRequired != null) {
break;
}
}
if (parsedRequired == null) {
return null;
}
if (parsedRequired.isEmpty() && offset == 0) {
offset = parameterTypes.size();
}
List<? extends VariableElement> potentialOptionals = parameterTypes.subList(0, offset);
List<Parameter> parsedOptionals = parseParametersOptional(spec, potentialOptionals);
if (parsedOptionals == null) {
return null;
}
List<Parameter> finalParameters = new ArrayList<>();
finalParameters.addAll(parsedOptionals);
finalParameters.addAll(parsedRequired);
return finalParameters;
}
private static List<Parameter> parseParametersOptional(MethodSpec spec, List<? extends VariableElement> types) {
List<Parameter> parsedParams = new ArrayList<>();
int typeStartIndex = 0;
List<ParameterSpec> specifications = spec.getOptional();
outer: for (int specIndex = 0; specIndex < specifications.size(); specIndex++) {
ParameterSpec specification = specifications.get(specIndex);
for (int typeIndex = typeStartIndex; typeIndex < types.size(); typeIndex++) {
VariableElement variable = types.get(typeIndex);
Parameter optionalParam = matchParameter(specification, variable, -1, -1);
if (optionalParam != null) {
parsedParams.add(optionalParam);
typeStartIndex = typeIndex + 1;
continue outer;
}
}
}
if (typeStartIndex < types.size()) {
// not enough types found
return null;
}
return parsedParams;
}
private static List<Parameter> parseParametersRequired(MethodSpec spec, List<VariableElement> types, boolean typeVarArgs) {
List<Parameter> parsedParams = new ArrayList<>();
List<ParameterSpec> specifications = spec.getRequired();
boolean specVarArgs = spec.isVariableRequiredParameters();
int typeIndex = 0;
int specificationIndex = 0;
ParameterSpec specification;
while ((specification = nextSpecification(specifications, specificationIndex, specVarArgs)) != null) {
VariableElement actualType = nextActualType(types, typeIndex, typeVarArgs);
if (actualType == null) {
if (spec.isIgnoreAdditionalSpecifications()) {
break;
}
return null;
}
int typeVarArgsIndex = typeVarArgs ? typeIndex - types.size() + 1 : -1;
int specVarArgsIndex = specVarArgs ? specificationIndex - specifications.size() + 1 : -1;
if (typeVarArgsIndex >= 0 && specVarArgsIndex >= 0) {
// both specifications and types have a variable number of arguments
// we would get into an endless loop if we would continue
break;
}
Parameter resolvedParameter = matchParameter(specification, actualType, specVarArgsIndex, typeVarArgsIndex);
if (resolvedParameter == null) {
return null;
}
parsedParams.add(resolvedParameter);
typeIndex++;
specificationIndex++;
}
// consume randomly ordered annotated parameters
VariableElement variable;
while ((variable = nextActualType(types, typeIndex, typeVarArgs)) != null) {
Parameter matchedParamter = matchAnnotatedParameter(spec, variable);
if (matchedParamter == null) {
break;
}
parsedParams.add(matchedParamter);
typeIndex++;
}
if (typeIndex < types.size()) {
if (spec.isIgnoreAdditionalParameters()) {
return parsedParams;
} else {
return null;
}
}
return parsedParams;
}
private static Parameter matchAnnotatedParameter(MethodSpec spec, VariableElement variable) {
for (ParameterSpec parameterSpec : spec.getAnnotations()) {
if (parameterSpec.matches(variable)) {
Parameter matchedParameter = matchParameter(parameterSpec, variable, -1, -1);
if (matchedParameter != null) {
matchedParameter.setLocalName(variable.getSimpleName().toString());
return matchedParameter;
}
}
}
return null;
}
private static ParameterSpec nextSpecification(List<ParameterSpec> specifications, int specIndex, boolean varArgs) {
if (varArgs && specIndex >= specifications.size() - 1 && !specifications.isEmpty()) {
return specifications.get(specifications.size() - 1);
} else if (specIndex < specifications.size()) {
return specifications.get(specIndex);
} else {
return null;
}
}
private static VariableElement nextActualType(List<VariableElement> types, int typeIndex, boolean varArgs) {
if (varArgs && typeIndex >= types.size() - 1 && !types.isEmpty()) {
// unpack varargs array argument
VariableElement actualType = types.get(types.size() - 1);
if (actualType.asType().getKind() == TypeKind.ARRAY) {
actualType = new CodeVariableElement(((ArrayType) actualType.asType()).getComponentType(), actualType.getSimpleName().toString());
}
return actualType;
} else if (typeIndex < types.size()) {
return types.get(typeIndex);
} else {
return null;
}
}
private static Parameter matchParameter(ParameterSpec specification, VariableElement variable, int specificationIndex, int varArgsIndex) {
TypeMirror resolvedType = variable.asType();
if (hasError(resolvedType)) {
return null;
}
if (!specification.matches(variable)) {
return null;
}
return new Parameter(specification, variable, specificationIndex, varArgsIndex);
}
}