/* * Copyright (c) 2015, 2016, 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.object.dsl.processor; import com.oracle.truffle.api.object.DynamicObject; import com.oracle.truffle.api.object.DynamicObjectFactory; import com.oracle.truffle.api.object.Layout.ImplicitCast; import com.oracle.truffle.api.object.ObjectType; import com.oracle.truffle.api.object.dsl.Layout; import com.oracle.truffle.api.object.dsl.Nullable; import com.oracle.truffle.api.object.dsl.Volatile; import com.oracle.truffle.dsl.processor.java.ElementUtils; import com.oracle.truffle.object.dsl.processor.model.LayoutModel; import com.oracle.truffle.object.dsl.processor.model.NameUtils; import com.oracle.truffle.object.dsl.processor.model.PropertyBuilder; import com.oracle.truffle.object.dsl.processor.model.PropertyModel; import javax.lang.model.element.AnnotationMirror; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.TypeElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; public class LayoutParser { private final LayoutProcessor processor; private TypeMirror objectTypeSuperclass; private LayoutModel superLayout; private String name; private String packageName; private String interfaceFullName; private boolean hasObjectTypeGuard; private boolean hasObjectGuard; private boolean hasDynamicObjectGuard; private boolean hasShapeProperties; private boolean hasCreate; private boolean hasBuilder; private final List<String> constructorProperties = new ArrayList<>(); private final Map<String, PropertyBuilder> properties = new HashMap<>(); private List<ImplicitCast> implicitCasts = new ArrayList<>(); public LayoutParser(LayoutProcessor processor) { this.processor = processor; } public void parse(TypeElement layoutElement) { if (layoutElement.getKind() != ElementKind.INTERFACE) { processor.reportError(layoutElement, "@Layout should only be applied to interfaces"); } parseName(layoutElement); if (!layoutElement.getInterfaces().isEmpty()) { if (layoutElement.getInterfaces().size() > 1) { processor.reportError(layoutElement, "@Layout interfaces can have at most one super-interface"); } final DeclaredType superInterface = (DeclaredType) layoutElement.getInterfaces().get(0); parseSuperLayout((TypeElement) superInterface.asElement()); } for (AnnotationMirror annotationMirror : layoutElement.getAnnotationMirrors()) { if (isSameType(annotationMirror.getAnnotationType(), Layout.class)) { objectTypeSuperclass = ElementUtils.getAnnotationValue(TypeMirror.class, annotationMirror, "objectTypeSuperclass"); if (ElementUtils.getAnnotationValue(Boolean.class, annotationMirror, "implicitCastIntToLong")) { implicitCasts.add(ImplicitCast.IntToLong); } if (ElementUtils.getAnnotationValue(Boolean.class, annotationMirror, "implicitCastIntToDouble")) { implicitCasts.add(ImplicitCast.IntToDouble); } } } if (superLayout != null && !implicitCasts.isEmpty()) { processor.reportError(layoutElement, "@Layout implicit casts need to be specified in the base layout"); } for (Element element : layoutElement.getEnclosedElements()) { if (element.getKind() == ElementKind.FIELD) { final String simpleName = element.getSimpleName().toString(); if (simpleName.endsWith("_IDENTIFIER")) { parseIdentifier((VariableElement) element); } else { processor.reportError(element, "@Layout interface fields should only be identifier fields, ending with _IDENTIFIER"); } } } for (Element element : layoutElement.getEnclosedElements()) { if (element.getKind() == ElementKind.METHOD) { final String simpleName = element.getSimpleName().toString(); if (simpleName.equals("create" + name + "Shape")) { parseShapeConstructor((ExecutableElement) element); } } } for (Element element : layoutElement.getEnclosedElements()) { if (element.getKind() == ElementKind.METHOD) { final String simpleName = element.getSimpleName().toString(); if (simpleName.equals("create" + name + "Shape")) { // Handled above } else if (simpleName.equals("create" + name)) { parseConstructor((ExecutableElement) element); } else if (simpleName.equals("build")) { parseBuilder((ExecutableElement) element); } else if (simpleName.equals("is" + name)) { parseGuard((ExecutableElement) element); } else if (simpleName.startsWith("getAndSet")) { parseGetAndSet((ExecutableElement) element); } else if (simpleName.startsWith("compareAndSet")) { parseCompareAndSet((ExecutableElement) element); } else if (simpleName.startsWith("get")) { parseGetter((ExecutableElement) element); } else if (simpleName.startsWith("set")) { parseSetter((ExecutableElement) element); } else { processor.reportError(element, "Unknown method prefix in @Layout interface - wouldn't know how to implement this method"); } } } for (Element element : layoutElement.getEnclosedElements()) { if (element.getKind() != ElementKind.FIELD && element.getKind() != ElementKind.METHOD) { processor.reportError(element, "@Layout interfaces can only contain fields and methods"); } } } private void parseName(TypeElement layoutElement) { parsePackageName(layoutElement); interfaceFullName = ElementUtils.getQualifiedName(layoutElement); final String nameString = layoutElement.getSimpleName().toString(); if (!nameString.endsWith("Layout")) { processor.reportError(layoutElement, "@Layout interfaces should have a name ending with -Layout"); } name = nameString.substring(0, nameString.length() - "Layout".length()); } private void parseSuperLayout(TypeElement superTypeElement) { final LayoutParser superParser = new LayoutParser(processor); superParser.parse(superTypeElement); superLayout = superParser.build(); } private void parseIdentifier(VariableElement fieldElement) { final String identifierName = fieldElement.getSimpleName().toString(); final String propertyName = NameUtils.constantToIdentifier(identifierName.substring(0, identifierName.length() - "_IDENTIFIER".length())); getProperty(propertyName).setHasIdentifier(true); } private void parseShapeConstructor(ExecutableElement methodElement) { List<? extends VariableElement> parameters = methodElement.getParameters(); if (!parameters.isEmpty()) { hasShapeProperties = true; } if (superLayout != null) { final List<PropertyModel> superShapeProperties = superLayout.getAllShapeProperties(); checkSharedParameters(methodElement, parameters, superShapeProperties); parameters = parameters.subList(superShapeProperties.size(), parameters.size()); } for (VariableElement element : parameters) { final String parameterName = element.getSimpleName().toString(); constructorProperties.add(parameterName); final PropertyBuilder property = getProperty(parameterName); setPropertyType(element, property, element.asType()); parseConstructorParameterAnnotations(property, element); property.setIsShapeProperty(true); } } private void parseConstructor(ExecutableElement methodElement) { hasCreate = true; checkCreateAndBuilder(methodElement); List<? extends VariableElement> parameters = methodElement.getParameters(); if (hasShapeProperties) { if (parameters.isEmpty()) { processor.reportError(methodElement, "If an @Layout has shape properties the constructor must have parameters"); } final VariableElement firstParameter = parameters.get(0); final String firstParameterName = firstParameter.getSimpleName().toString(); if (!matches(firstParameterName, "factory")) { processor.reportError(firstParameter, "If an @Layout has shape properties, the first parameter of the constructor must be called factory (was %s)", firstParameter.getSimpleName()); } if (!isSameType(firstParameter.asType(), DynamicObjectFactory.class)) { processor.reportError(firstParameter, "If an @Layout has shape properties, the first parameter of the constructor must be of type DynamicObjectFactory (was %s)", firstParameter.asType()); } parameters = parameters.subList(1, parameters.size()); } addConstructorProperties(methodElement, parameters); } private void parseBuilder(ExecutableElement methodElement) { hasBuilder = true; checkCreateAndBuilder(methodElement); List<? extends VariableElement> parameters = methodElement.getParameters(); if (!isSameType(methodElement.getReturnType(), Object[].class)) { processor.reportError(methodElement, "build() must have Object[] for return type"); } addConstructorProperties(methodElement, parameters); } private void checkCreateAndBuilder(ExecutableElement methodElement) { if (hasCreate && hasBuilder) { processor.reportError(methodElement, "Only one of create<Layout>() or build() may be specified."); return; } } private void addConstructorProperties(ExecutableElement methodElement, List<? extends VariableElement> parameters) { List<? extends VariableElement> ownParameters = parameters; if (superLayout != null) { final List<PropertyModel> superProperties = superLayout.getAllInstanceProperties(); checkSharedParameters(methodElement, parameters, superProperties); ownParameters = parameters.subList(superProperties.size(), parameters.size()); } for (VariableElement element : ownParameters) { final String parameterName = element.getSimpleName().toString(); if (parameterName.equals("factory")) { processor.reportError(methodElement, "Factory is a confusing name for a property"); } if (constructorProperties.contains(parameterName)) { processor.reportError(methodElement, "The property %s is duplicated"); } else { constructorProperties.add(parameterName); final PropertyBuilder property = getProperty(parameterName); setPropertyType(element, property, element.asType()); parseConstructorParameterAnnotations(property, element); } } } private void checkSharedParameters(Element element, List<? extends VariableElement> parameters, List<PropertyModel> sharedProperties) { if (parameters.size() < sharedProperties.size()) { processor.reportError(element, "@Layout constructor cannot have less parameters than the super layout constructor " + parameters + " " + sharedProperties); } for (int n = 0; n < sharedProperties.size(); n++) { final VariableElement parameter = parameters.get(n); final String parameterName = parameter.getSimpleName().toString(); final PropertyModel superLayoutProperty = sharedProperties.get(n); if (superLayoutProperty.hasGeneratedName()) { // Assume the name is right if we cannot check it during incremental compilation superLayoutProperty.fixName(parameterName); } if (!parameterName.equals(superLayoutProperty.getName())) { processor.reportError(element, "@Layout constructor parameter %d needs to have the same name as the super layout constructor (is %s, should be %s)", n, parameter.getSimpleName(), superLayoutProperty.getName()); } if (!isSameType(parameter.asType(), superLayoutProperty.getType())) { processor.reportError(element, "@Layout constructor parameter %d needs to have the same type as the super layout constructor (is %s, should be %s)", n, parameter.asType(), superLayoutProperty.getType()); } } } private void parsePackageName(TypeElement layoutElement) { final String[] packageComponents = ElementUtils.getQualifiedName(layoutElement).split("\\."); final StringBuilder packageBuilder = new StringBuilder(); for (int n = 0; n < packageComponents.length; n++) { if (Character.isUpperCase(packageComponents[n].charAt(0))) { break; } if (n > 0) { packageBuilder.append('.'); } packageBuilder.append(packageComponents[n]); } packageName = packageBuilder.toString(); } private void parseGuard(ExecutableElement methodElement) { if (methodElement.getParameters().size() != 1) { processor.reportError(methodElement, "@Layout guard methods must have just one parameter"); } final VariableElement parameter = methodElement.getParameters().get(0); final TypeMirror type = parameter.asType(); final String parameterName = parameter.getSimpleName().toString(); final String expectedParameterName; if (isSameType(type, DynamicObject.class)) { hasDynamicObjectGuard = true; expectedParameterName = "object"; } else if (isSameType(type, ObjectType.class)) { hasObjectTypeGuard = true; expectedParameterName = "objectType"; } else if (isSameType(type, Object.class)) { hasObjectGuard = true; expectedParameterName = "object"; } else { processor.reportError(methodElement, "@Layout guard method with unknown parameter type %s - don't know how to guard on this", type); expectedParameterName = null; } if (expectedParameterName != null && !matches(parameterName, expectedParameterName)) { processor.reportError(methodElement, "@Layout guard method should have a parameter named %s", expectedParameterName); } } private void parseGetter(ExecutableElement methodElement) { if (methodElement.getParameters().size() != 1) { processor.reportError(methodElement, "@Layout getter methods must have just one parameter"); } final VariableElement parameter = methodElement.getParameters().get(0); final TypeMirror parameterType = parameter.asType(); final String parameterName = parameter.getSimpleName().toString(); final boolean isShapeGetter; final boolean isObjectTypeGetter; final String expectedParameterName; if (isSameType(parameterType, DynamicObject.class)) { isShapeGetter = false; isObjectTypeGetter = false; expectedParameterName = "object"; } else if (isSameType(parameterType, DynamicObjectFactory.class)) { isShapeGetter = true; isObjectTypeGetter = false; expectedParameterName = "factory"; } else if (isSameType(parameterType, ObjectType.class)) { isShapeGetter = false; isObjectTypeGetter = true; expectedParameterName = "objectType"; } else { isShapeGetter = false; isObjectTypeGetter = false; expectedParameterName = null; processor.reportError(methodElement, "@Layout getter methods must have a parameter of type DynamicObject or, for shape properties, DynamicObjectFactory or ObjectType"); } if (expectedParameterName != null && !matches(parameterName, expectedParameterName)) { processor.reportError(methodElement, "@Layout getter method should have a parameter named %s", expectedParameterName); } final String propertyName = NameUtils.titleToCamel(methodElement.getSimpleName().toString().substring("get".length())); final PropertyBuilder property = getProperty(propertyName); if (isShapeGetter) { property.setHasShapeGetter(true); } else if (isObjectTypeGetter) { property.setHasObjectTypeGetter(true); } else { property.setHasGetter(true); } setPropertyType(methodElement, property, methodElement.getReturnType()); } private void parseSetter(ExecutableElement methodElement) { if (methodElement.getParameters().size() != 2) { processor.reportError(methodElement, "@Layout guard methods must have two parameters"); } final VariableElement parameter = methodElement.getParameters().get(0); final TypeMirror parameterType = parameter.asType(); final String parameterName = parameter.getSimpleName().toString(); final boolean isShapeSetter; final String expectedParameterName; if (isSameType(parameterType, DynamicObject.class)) { isShapeSetter = false; expectedParameterName = "object"; } else if (isSameType(parameterType, DynamicObjectFactory.class)) { isShapeSetter = true; expectedParameterName = "factory"; } else { isShapeSetter = false; expectedParameterName = null; processor.reportError(methodElement, "@Layout setter methods must have a first parameter of type DynamicObject or, for shape properties, DynamicObjectFactory"); } if (expectedParameterName != null && !matches(parameterName, expectedParameterName)) { processor.reportError(methodElement, "@Layout getter method should have a first parameter named %s", expectedParameterName); } final VariableElement secondParameter = methodElement.getParameters().get(1); final String secondParameterName = secondParameter.getSimpleName().toString(); if (!matches(secondParameterName, "value")) { processor.reportError(methodElement, "@Layout getter method should have a second parameter named value"); } final boolean isUnsafeSetter = methodElement.getSimpleName().toString().endsWith("Unsafe"); String propertyName = NameUtils.titleToCamel(methodElement.getSimpleName().toString().substring("set".length())); if (isUnsafeSetter) { propertyName = propertyName.substring(0, propertyName.length() - "Unsafe".length()); } final PropertyBuilder property = getProperty(propertyName); if (isShapeSetter) { property.setHasShapeSetter(true); } else { if (isUnsafeSetter) { property.setHasUnsafeSetter(isUnsafeSetter); } else { property.setHasSetter(true); } } setPropertyType(methodElement, property, methodElement.getParameters().get(1).asType()); } private void parseCompareAndSet(ExecutableElement methodElement) { if (methodElement.getParameters().size() != 3) { processor.reportError(methodElement, "@Layout compare and set methods must have three parameters"); } final VariableElement objectParameter = methodElement.getParameters().get(0); final VariableElement currentValueParameter = methodElement.getParameters().get(1); final VariableElement newValueParameter = methodElement.getParameters().get(2); if (!isSameType(objectParameter.asType(), DynamicObject.class)) { processor.reportError(methodElement, "@Layout compare and set method should have a first parameter of type DynamicObject"); } if (!objectParameter.getSimpleName().toString().equals("object")) { processor.reportError(methodElement, "@Layout compare and set method should have a first parameter named object"); } if (!currentValueParameter.getSimpleName().toString().equals("expectedValue")) { processor.reportError(methodElement, "@Layout compare and set method should have a second parameter named expectedValue"); } if (!newValueParameter.getSimpleName().toString().equals("value")) { processor.reportError(methodElement, "@Layout compare and set method should have a third parameter named value"); } String propertyName = NameUtils.titleToCamel(methodElement.getSimpleName().toString().substring("compareAndSet".length())); final PropertyBuilder property = getProperty(propertyName); property.setHasCompareAndSet(true); setPropertyType(methodElement, property, methodElement.getParameters().get(1).asType()); setPropertyType(methodElement, property, methodElement.getParameters().get(2).asType()); } private void parseGetAndSet(ExecutableElement methodElement) { if (methodElement.getParameters().size() != 2) { processor.reportError(methodElement, "@Layout get and set methods must have two parameters"); } final VariableElement objectParameter = methodElement.getParameters().get(0); final VariableElement newValueParameter = methodElement.getParameters().get(1); if (!isSameType(objectParameter.asType(), DynamicObject.class)) { processor.reportError(methodElement, "@Layout get and set method should have a first parameter of type DynamicObject"); } if (!objectParameter.getSimpleName().toString().equals("object")) { processor.reportError(methodElement, "@Layout get and set method should have a first parameter named object"); } if (!newValueParameter.getSimpleName().toString().equals("value")) { processor.reportError(methodElement, "@Layout get and set method should have a second parameter named value"); } String propertyName = NameUtils.titleToCamel(methodElement.getSimpleName().toString().substring("getAndSet".length())); final PropertyBuilder property = getProperty(propertyName); property.setHasGetAndSet(true); setPropertyType(methodElement, property, methodElement.getParameters().get(1).asType()); setPropertyType(methodElement, property, methodElement.getReturnType()); } private static void parseConstructorParameterAnnotations(PropertyBuilder property, Element element) { if (element.getAnnotation(Nullable.class) != null) { property.setNullable(true); } if (element.getAnnotation(Volatile.class) != null) { property.setVolatile(true); } } private TypeMirror getType(Class<?> klass) { return ElementUtils.getType(processor.getProcessingEnv(), klass); } private boolean isSameType(TypeMirror a, TypeMirror b) { return processor.getProcessingEnv().getTypeUtils().isSameType(a, b); } private boolean isSameType(TypeMirror a, Class<?> klass) { return processor.getProcessingEnv().getTypeUtils().isSameType(a, getType(klass)); } // Whether the parameter name is a fake generated one (argX). // Happens only during superLayout parsing. public static boolean isGeneratedName(String name) { return name.length() > 3 && name.startsWith("arg") && Character.isDigit(name.charAt(3)); } private static boolean matches(String parameterName, String expected) { if (isGeneratedName(parameterName)) { return true; } return parameterName.equals(expected); } private void setPropertyType(Element element, PropertyBuilder builder, TypeMirror type) { if (builder.getType() == null) { builder.setType(type); } else if (!isSameType(type, builder.getType())) { processor.reportError(element, "@Layout property types are inconsistent - was previously %s but now %s", builder.getType(), type); } } private PropertyBuilder getProperty(String propertyName) { PropertyBuilder builder = properties.get(propertyName); if (builder == null) { builder = new PropertyBuilder(propertyName); properties.put(propertyName, builder); } return builder; } public LayoutModel build() { return new LayoutModel(objectTypeSuperclass, superLayout, name, packageName, hasObjectTypeGuard, hasObjectGuard, hasDynamicObjectGuard, hasBuilder, buildProperties(), interfaceFullName, implicitCasts); } private List<PropertyModel> buildProperties() { final List<PropertyModel> models = new ArrayList<>(); for (String propertyName : constructorProperties) { models.add(getProperty(propertyName).build()); } return models; } }