/** * 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.compiler.operator.processor; import java.text.MessageFormat; import java.util.List; import java.util.Map; import javax.lang.model.element.AnnotationValue; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.DeclaredType; import javax.lang.model.type.TypeMirror; import javax.lang.model.util.Types; import com.asakusafw.compiler.common.Precondition; import com.asakusafw.compiler.operator.DataModelMirror; import com.asakusafw.compiler.operator.DataModelMirror.Kind; import com.asakusafw.compiler.operator.OperatorCompilingEnvironment; import com.asakusafw.compiler.operator.OperatorProcessor; import com.asakusafw.vocabulary.operator.MasterSelection; /** * Analyzes MasterJoin-like operators. */ public final class MasterKindOperatorAnalyzer { /** * Returns the selector method for the target operator method. * @param context the current context * @return the selector method, or {@code null} if the operator method does not use one * @throws ResolveException if error occurred while resolving language elements * @throws IllegalArgumentException if the parameter is {@code null} */ public static ExecutableElement findSelector(OperatorProcessor.Context context) throws ResolveException { Precondition.checkMustNotBeNull(context, "context"); //$NON-NLS-1$ String selectorName = getSelectorName(context); if (selectorName == null) { return null; } ExecutableElement selectorMethod = getSelectorMethod(context, selectorName); checkParameters(context.environment, context.element, selectorMethod); return selectorMethod; } private static void checkParameters( OperatorCompilingEnvironment environment, ExecutableElement operatorMethod, ExecutableElement selectorMethod) throws ResolveException { assert environment != null; assert operatorMethod != null; assert selectorMethod != null; assert operatorMethod.getParameters().isEmpty() == false; List<? extends VariableElement> operatorParams = operatorMethod.getParameters(); List<? extends VariableElement> selectorParams = selectorMethod.getParameters(); checkParameterCount(operatorMethod, selectorMethod); DataModelMirror operatorMaster = environment.loadDataModel(operatorParams.get(0).asType()); DataModelMirror selectorMaster = extractSelectorMaster( environment, selectorMethod, selectorParams.get(0).asType()); if (isValidMaster(operatorMaster, selectorMaster) == false) { throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorInvalidSelectorMaster"), //$NON-NLS-1$ selectorMethod.getSimpleName(), operatorMaster)); } if (selectorParams.size() == 1) { return; } DataModelMirror operatorTx = environment.loadDataModel(operatorParams.get(1).asType()); DataModelMirror selectorTx = environment.loadDataModel(selectorParams.get(1).asType()); if (isValidTx(operatorTx, selectorTx) == false) { throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorInvalidSelectorTransaction"), //$NON-NLS-1$ selectorMethod.getSimpleName(), operatorTx)); } DataModelMirror selectorResult = environment.loadDataModel(selectorMethod.getReturnType()); if (isValidResult(operatorMaster, selectorMaster, selectorResult) == false) { throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorInvalidSelectorResult"), //$NON-NLS-1$ selectorMethod.getSimpleName(), operatorMaster)); } for (int i = 2, n = selectorParams.size(); i < n; i++) { TypeMirror expected = operatorParams.get(i).asType(); TypeMirror actual = selectorParams.get(i).asType(); if (environment.getTypeUtils().isSubtype(expected, actual) == false) { throw new ResolveException(MessageFormat.format( Messages.getString( "MasterKindOperatorAnalyzer.errorInconsistentSelectorOptionParameter"), //$NON-NLS-1$ selectorMethod.getSimpleName(), expected, String.valueOf(i + 1))); } } } private static boolean isValidMaster(DataModelMirror operatorMaster, DataModelMirror selectorMaster) { if (operatorMaster == null || selectorMaster == null) { return false; } return operatorMaster.canContain(selectorMaster); } private static boolean isValidTx(DataModelMirror operatorTx, DataModelMirror selectorTx) { if (operatorTx == null || selectorTx == null) { return false; } return operatorTx.canInvoke(selectorTx); } private static boolean isValidResult( DataModelMirror operatorMaster, DataModelMirror selectorMaster, DataModelMirror selectorResult) { if (operatorMaster == null || selectorMaster == null || selectorResult == null) { return false; } if (selectorResult.canInvoke(operatorMaster)) { return true; } // FIXME restrict if (selectorMaster.getKind() == Kind.PARTIAL && selectorMaster.isSame(selectorResult)) { return true; } return false; } private static void checkParameterCount( ExecutableElement operatorMethod, ExecutableElement selectorMethod) throws ResolveException { assert operatorMethod != null; assert selectorMethod != null; List<? extends VariableElement> operatorParams = operatorMethod.getParameters(); List<? extends VariableElement> selectorParams = selectorMethod.getParameters(); if (operatorParams.size() < selectorParams.size()) { throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorExtraSelectorParameter"), //$NON-NLS-1$ selectorMethod.getSimpleName())); } if (selectorParams.size() == 0) { throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorInconsistentSelectorMasterType"), //$NON-NLS-1$ selectorMethod.getSimpleName(), operatorParams.get(0).asType())); } } private static DataModelMirror extractSelectorMaster( OperatorCompilingEnvironment environment, ExecutableElement selectorMethod, TypeMirror firstParameter) throws ResolveException { assert environment != null; assert selectorMethod != null; assert firstParameter != null; TypeMirror erasedSelector = environment.getErasure(firstParameter); Types types = environment.getTypeUtils(); if (types.isSameType(erasedSelector, environment.getDeclaredType(List.class)) == false) { throw new ResolveException(MessageFormat.format( Messages.getString( "MasterKindOperatorAnalyzer.errorInvalidSelectorMasterContainer"), //$NON-NLS-1$ selectorMethod.getSimpleName())); } DeclaredType list = (DeclaredType) firstParameter; if (list.getTypeArguments().size() != 1) { throw new ResolveException(MessageFormat.format( Messages.getString( "MasterKindOperatorAnalyzer.errorInvalidSelectorMasterTypeArgument"), //$NON-NLS-1$ selectorMethod.getSimpleName())); } TypeMirror selectorElement = list.getTypeArguments().get(0); return environment.loadDataModel(selectorElement); } private static ExecutableElement getSelectorMethod( OperatorProcessor.Context context, String selectorName) throws ResolveException { assert context != null; assert selectorName != null; for (Element member : context.element.getEnclosingElement().getEnclosedElements()) { if (member.getKind() != ElementKind.METHOD) { continue; } if (member.getSimpleName().contentEquals(selectorName)) { if (member.getAnnotation(MasterSelection.class) == null) { throw new ResolveException(MessageFormat.format( Messages.getString( "MasterKindOperatorAnalyzer.errorMissingSelectorAnnotation"), //$NON-NLS-1$ selectorName, MasterSelection.class.getSimpleName())); } return (ExecutableElement) member; } } throw new ResolveException(MessageFormat.format( Messages.getString("MasterKindOperatorAnalyzer.errorMissingSelectorMethod"), //$NON-NLS-1$ selectorName)); } private static String getSelectorName(OperatorProcessor.Context context) { assert context != null; for (Map.Entry<? extends ExecutableElement, ? extends AnnotationValue> entry : context.annotation.getElementValues().entrySet()) { if (entry.getKey().getSimpleName().contentEquals(MasterSelection.ELEMENT_NAME)) { Object value = entry.getValue().getValue(); if (value instanceof String) { return (String) value; } } } return null; } private MasterKindOperatorAnalyzer() { return; } /** * An exception for tell that resolving elements are failed. */ public static class ResolveException extends Exception { private static final long serialVersionUID = 1L; /** * Creates a new instance. * @param message the exception message (nullable) */ public ResolveException(String message) { super(message); } /** * Creates a new instance. * @param message the exception message (nullable) * @param cause the original cause (nullable) */ public ResolveException(String message, Throwable cause) { super(message, cause); } } }