/** * Copyright 2011-2017 Asakusa Framework Team. * * 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.asakusafw.operator.util; import java.text.MessageFormat; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; import javax.lang.model.element.Name; import javax.lang.model.element.TypeElement; import javax.lang.model.type.TypeMirror; import com.asakusafw.operator.CompileEnvironment; import com.asakusafw.operator.description.AnnotationDescription; import com.asakusafw.operator.description.ArrayDescription; import com.asakusafw.operator.description.ArrayTypeDescription; import com.asakusafw.operator.description.BasicTypeDescription; import com.asakusafw.operator.description.ClassDescription; import com.asakusafw.operator.description.EnumConstantDescription; import com.asakusafw.operator.description.ImmediateDescription; import com.asakusafw.operator.description.ObjectDescription; import com.asakusafw.operator.description.ReifiableTypeDescription; import com.asakusafw.operator.description.TypeDescription; import com.asakusafw.operator.description.ValueDescription; import com.asakusafw.utils.java.model.syntax.Annotation; import com.asakusafw.utils.java.model.syntax.AnnotationElement; import com.asakusafw.utils.java.model.syntax.ArrayCreationExpression; import com.asakusafw.utils.java.model.syntax.ArrayType; import com.asakusafw.utils.java.model.syntax.BasicTypeKind; import com.asakusafw.utils.java.model.syntax.ClassLiteral; import com.asakusafw.utils.java.model.syntax.Expression; import com.asakusafw.utils.java.model.syntax.ModelFactory; import com.asakusafw.utils.java.model.syntax.NamedType; import com.asakusafw.utils.java.model.syntax.Type; import com.asakusafw.utils.java.model.util.ImportBuilder; import com.asakusafw.utils.java.model.util.Models; import com.asakusafw.utils.java.model.util.TypeBuilder; /** * Common helper methods about descriptions. * @since 0.9.0 * @version 0.9.1 */ public final class DescriptionHelper { private DescriptionHelper() { return; } /** * Returns the description. * @param environment the current environment * @param type the target type * @return the corresponded description */ public static TypeDescription toDescription(CompileEnvironment environment, TypeMirror type) { switch (type.getKind()) { case DECLARED: return toDescription(environment, (javax.lang.model.type.DeclaredType) type); case ARRAY: return toDescription(environment, (javax.lang.model.type.ArrayType) type); case BOOLEAN: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.BOOLEAN); case BYTE: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.BYTE); case SHORT: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.SHORT); case INT: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.INT); case LONG: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.LONG); case FLOAT: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.FLOAT); case DOUBLE: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.DOUBLE); case CHAR: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.CHAR); case VOID: return new BasicTypeDescription(BasicTypeDescription.BasicTypeKind.VOID); default: throw new AssertionError(MessageFormat.format( "unsupported type: {0}", //$NON-NLS-1$ type)); } } /** * Returns the description. * @param environment the current environment * @param element the target element * @return the corresponded description */ public static ClassDescription toDescription(CompileEnvironment environment, TypeElement element) { Name name = environment.getProcessingEnvironment().getElementUtils().getBinaryName(element); return new ClassDescription(name.toString()); } /** * Returns the description. * @param environment the current environment * @param type the target type * @return the corresponded description */ public static ClassDescription toDescription( CompileEnvironment environment, javax.lang.model.type.DeclaredType type) { TypeElement element = (TypeElement) type.asElement(); return toDescription(environment, element); } private static TypeDescription toDescription( CompileEnvironment environment, javax.lang.model.type.ArrayType type) { TypeDescription element = toDescription(environment, type.getComponentType()); return new ArrayTypeDescription(element); } /** * Returns the resolved type. * @param importer the current import builder * @param type the target class description * @return the resolved type */ public static Type resolve(ImportBuilder importer, TypeDescription type) { switch (type.getTypeKind()) { case BASIC: return resolve((BasicTypeDescription) type); case CLASS: return resolve(importer, (ClassDescription) type); case ARRAY: return resolve(importer, (ArrayTypeDescription) type); default: throw new AssertionError(type); } } private static Type resolve(BasicTypeDescription type) { ModelFactory factory = Models.getModelFactory(); switch (type.getBasicTypeKind()) { case BOOLEAN: return factory.newBasicType(BasicTypeKind.BOOLEAN); case BYTE: return factory.newBasicType(BasicTypeKind.BYTE); case CHAR: return factory.newBasicType(BasicTypeKind.CHAR); case DOUBLE: return factory.newBasicType(BasicTypeKind.DOUBLE); case FLOAT: return factory.newBasicType(BasicTypeKind.FLOAT); case INT: return factory.newBasicType(BasicTypeKind.INT); case LONG: return factory.newBasicType(BasicTypeKind.LONG); case SHORT: return factory.newBasicType(BasicTypeKind.SHORT); case VOID: return factory.newBasicType(BasicTypeKind.VOID); default: throw new AssertionError(type); } } private static Type resolve(ImportBuilder importer, ClassDescription type) { ModelFactory factory = Models.getModelFactory(); return importer.toType(Models.toName(factory, type.getClassName())); } private static Type resolve(ImportBuilder importer, ArrayTypeDescription type) { int dim = 1; TypeDescription current = type.getComponentType(); while (current.getTypeKind() == TypeDescription.TypeKind.ARRAY) { current = ((ArrayTypeDescription) current).getComponentType(); dim++; } ModelFactory factory = Models.getModelFactory(); Type result = resolve(importer, current); for (int i = 0; i < dim; i++) { result = factory.newArrayType(result); } return result; } /** * Returns the resolved value. * @param importer the current import builder * @param value the target constant value * @return the resolved expression */ public static Expression resolveValue(ImportBuilder importer, ValueDescription value) { switch (value.getValueKind()) { case OBJECT: return resolveValue(importer, (ObjectDescription) value); case ARRAY: return resolveValue(importer, (ArrayDescription) value); default: return resolveConstant(importer, value); } } /** * Returns the resolved constant value. * @param importer the current import builder * @param constant the target constant value * @return the resolved expression */ public static Expression resolveConstant(ImportBuilder importer, ValueDescription constant) { switch (constant.getValueKind()) { case IMMEDIATE: return resolveConstant((ImmediateDescription) constant); case ENUM_CONSTANT: return resolveConstant(importer, (EnumConstantDescription) constant); case TYPE: return resolveConstant(importer, (ReifiableTypeDescription) constant); case ARRAY: return resolveConstant(importer, (ArrayDescription) constant); default: throw new IllegalArgumentException(MessageFormat.format( "not a constant value: {0}", //$NON-NLS-1$ constant)); } } private static Expression resolveConstant(ImmediateDescription constant) { ModelFactory factory = Models.getModelFactory(); Object value = constant.getValue(); if (value == null) { return Models.toNullLiteral(factory); } else { return Models.toLiteral(factory, value); } } private static Expression resolveConstant(ImportBuilder importer, EnumConstantDescription constant) { ModelFactory factory = Models.getModelFactory(); Type type = resolve(importer, constant.getDeclaringClass()); return new TypeBuilder(factory, type).field(constant.getName()).toExpression(); } private static ClassLiteral resolveConstant(ImportBuilder importer, ReifiableTypeDescription constant) { ModelFactory factory = Models.getModelFactory(); return factory.newClassLiteral(resolve(importer, constant)); } private static ArrayCreationExpression resolveConstant(ImportBuilder importer, ArrayDescription constant) { ModelFactory factory = Models.getModelFactory(); ArrayType type = (ArrayType) resolve(importer, constant.getValueType()); List<Expression> elements = new ArrayList<>(); for (ValueDescription value : constant.getElements()) { elements.add(resolveConstant(importer, value)); } return factory.newArrayCreationExpression(type, factory.newArrayInitializer(elements)); } private static Expression resolveValue(ImportBuilder importer, ObjectDescription value) { ModelFactory factory = Models.getModelFactory(); Type type = resolve(importer, value.getValueType()); List<Expression> arguments = new ArrayList<>(); for (ValueDescription argValue : value.getArguments()) { arguments.add(resolveValue(importer, argValue)); } return Optional.ofNullable(value.getMethodName()) .map(name -> new TypeBuilder(factory, type).method(name, arguments)) .orElseGet(() -> new TypeBuilder(factory, type).newObject(arguments)) .toExpression(); } private static ArrayCreationExpression resolveValue(ImportBuilder importer, ArrayDescription value) { ModelFactory factory = Models.getModelFactory(); ArrayType type = (ArrayType) resolve(importer, value.getValueType()); List<Expression> elements = new ArrayList<>(); for (ValueDescription elemValue : value.getElements()) { elements.add(resolveValue(importer, elemValue)); } return factory.newArrayCreationExpression(type, factory.newArrayInitializer(elements)); } /** * Returns the resolved annotation. * @param importer the current import builder * @param annotation the target annotation description * @return the resolved annotation */ public static Annotation resolveAnnotation(ImportBuilder importer, AnnotationDescription annotation) { ModelFactory factory = Models.getModelFactory(); NamedType type = (NamedType) resolve(importer, annotation.getDeclaringClass()); Map<String, Expression> elements = new LinkedHashMap<>(); for (Map.Entry<String, ValueDescription> entry : annotation.getElements().entrySet()) { String name = entry.getKey(); Expression value = resolveAnnotationValue(importer, entry.getValue()); elements.put(name, value); } if (elements.isEmpty()) { return factory.newMarkerAnnotation(type); } else if (elements.size() == 1 && elements.containsKey("value")) { //$NON-NLS-1$ return factory.newSingleElementAnnotation(type, elements.get("value")); //$NON-NLS-1$ } else { List<AnnotationElement> elementList = new ArrayList<>(); for (Map.Entry<String, Expression> entry : elements.entrySet()) { String name = entry.getKey(); Expression value = entry.getValue(); elementList.add(factory.newAnnotationElement(factory.newSimpleName(name), value)); } return factory.newNormalAnnotation(type, elementList); } } private static Expression resolveAnnotationValue(ImportBuilder importer, ValueDescription value) { switch (value.getValueKind()) { case IMMEDIATE: case TYPE: case ENUM_CONSTANT: return resolveConstant(importer, value); case ARRAY: { ModelFactory factory = Models.getModelFactory(); List<Expression> elements = new ArrayList<>(); for (ValueDescription element : ((ArrayDescription) value).getElements()) { elements.add(resolveAnnotationValue(importer, element)); } if (elements.size() == 1) { return elements.get(0); } return factory.newArrayInitializer(elements); } case ANNOTATION: return resolveAnnotation(importer, (AnnotationDescription) value); default: throw new IllegalArgumentException(MessageFormat.format( "not an annotation value: {0}", //$NON-NLS-1$ value)); } } }