Package com.asakusafw.compiler.operator.flow

Source Code of com.asakusafw.compiler.operator.flow.FlowPartClassCollector

/**
* Copyright 2011-2014 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.flow;

import java.text.MessageFormat;
import java.util.List;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;

import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.compiler.operator.ExecutableAnalyzer;
import com.asakusafw.compiler.operator.ExecutableAnalyzer.TypeConstraint;
import com.asakusafw.compiler.operator.OperatorCompilerException;
import com.asakusafw.compiler.operator.OperatorCompilingEnvironment;
import com.asakusafw.compiler.operator.OperatorPortDeclaration;
import com.asakusafw.compiler.operator.PortTypeDescription;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.utils.java.model.syntax.DocElement;
import com.asakusafw.vocabulary.flow.FlowDescription;

/**
* フロー部品からなる演算子を集める。
*/
public class FlowPartClassCollector {

    private final OperatorCompilingEnvironment environment;

    private final List<FlowPartClass> collected;

    private boolean sawError;

    /**
     * インスタンスを生成する。
     * @param environment 環境オブジェクト
     * @throws IllegalArgumentException 引数に{@code null}が含まれる場合
     */
    public FlowPartClassCollector(OperatorCompilingEnvironment environment) {
        Precondition.checkMustNotBeNull(environment, "environment"); //$NON-NLS-1$
        this.environment = environment;
        this.collected = Lists.create();
        this.sawError = false;
    }

    /**
     * フロー部品としての注釈が付与された要素を追加する。
     * @param element 追加する要素
     * @throws IllegalArgumentException 引数に{@code null}が含まれる場合
     */
    public void add(Element element) {
        Precondition.checkMustNotBeNull(element, "element"); //$NON-NLS-1$
        if (element.getKind() != ElementKind.CLASS) {
            error(element, "フロー部品はクラスとして宣言される必要があります");
            return;
        }
        TypeElement typeDecl = (TypeElement) element;
        FlowPartClass result = toFlowPartClass(typeDecl);
        if (result != null) {
            collected.add(result);
        }
    }

    private FlowPartClass toFlowPartClass(Element element) {
        assert element != null;
        if (validateClassModifiers(element) == false) {
            return null;
        }
        TypeElement type = (TypeElement) element;
        ExecutableElement ctor = findConstructor(type);
        if (ctor == null) {
            return null;
        }
        validateConstructorModifiers(ctor);
        FlowPartClass aClass = analyze(type, ctor);
        return aClass;
    }

    private FlowPartClass analyze(TypeElement aClass, ExecutableElement ctor) {
        assert aClass != null;
        assert ctor != null;
        ExecutableAnalyzer analyzer = new ExecutableAnalyzer(environment, ctor);
        List<? extends DocElement> documentation = analyzer.getDocument(aClass);
        List<OperatorPortDeclaration> inputPorts = Lists.create();
        List<OperatorPortDeclaration> outputPorts = Lists.create();
        List<OperatorPortDeclaration> parameters = Lists.create();
        for (int i = 0, n = analyzer.countParameters(); i < n; i++) {
            OperatorPortDeclaration port = analyzePort(analyzer, i);
            if (port == null) {
                continue;
            } else if (port.getKind() == OperatorPortDeclaration.Kind.INPUT) {
                inputPorts.add(port);
            } else if (port.getKind() == OperatorPortDeclaration.Kind.OUTPUT) {
                outputPorts.add(port);
            } else {
                parameters.add(port);
            }
        }
        if (inputPorts.isEmpty() && outputPorts.isEmpty()) {
            analyzer.error("フロー部品の入出力が指定されていません。コンストラクターの引数にIn<...>, Out<...>型で指定して下さい");
        }
        if (analyzer.hasError()) {
            sawError = true;
            return null;
        }
        outputPorts = inferTypeVariables(analyzer, inputPorts, outputPorts, parameters);
        if (analyzer.hasError()) {
            sawError = true;
            return null;
        }
        return new FlowPartClass(
                aClass,
                documentation,
                inputPorts,
                outputPorts,
                parameters);
    }

    private OperatorPortDeclaration analyzePort(
            ExecutableAnalyzer analyzer, int index) {
        TypeConstraint type = analyzer.getParameterType(index);
        if (type.isIn()) {
            return toPort(OperatorPortDeclaration.Kind.INPUT, analyzer, type, index);
        } else if (type.isOut()) {
            return toPort(OperatorPortDeclaration.Kind.OUTPUT, analyzer, type, index);
        } else {
            return toParameter(analyzer, index, type);
        }
    }

    private List<OperatorPortDeclaration> inferTypeVariables(
            ExecutableAnalyzer analyzer,
            List<OperatorPortDeclaration> inputPorts,
            List<OperatorPortDeclaration> outputPorts,
            List<OperatorPortDeclaration> parameters) {
        assert analyzer != null;
        assert inputPorts != null;
        assert outputPorts != null;
        assert parameters != null;
        List<OperatorPortDeclaration> inferred = Lists.create();
        for (OperatorPortDeclaration output : outputPorts) {
            if (output.getType().getRepresentation().getKind() != TypeKind.TYPEVAR) {
                inferred.add(new OperatorPortDeclaration(
                        output.getKind(),
                        output.getDocumentation(),
                        output.getName(),
                        PortTypeDescription.direct(output.getType().getRepresentation()),
                        output.getParameterPosition(),
                        null));
            } else {
                OperatorPortDeclaration outputType = inferOutputType(output, inputPorts, parameters);
                if (outputType != null) {
                    inferred.add(outputType);
                } else {
                    analyzer.error(
                            output.getParameterPosition(),
                            "{0}の型{1}は入力と関係のない型です",
                            output.getName(),
                            output.getType().getRepresentation());
                }
            }
        }
        return inferred;
    }

    private OperatorPortDeclaration inferOutputType(
            OperatorPortDeclaration output,
            List<OperatorPortDeclaration> inputPorts,
            List<OperatorPortDeclaration> parameters) {
        assert output != null;
        assert inputPorts != null;
        assert parameters != null;
        Types types = environment.getTypeUtils();
        TypeMirror outputType = output.getType().getRepresentation();
        for (OperatorPortDeclaration input : inputPorts) {
            if (types.isSameType(outputType, input.getType().getRepresentation())) {
                return new OperatorPortDeclaration(
                        output.getKind(),
                        output.getDocumentation(),
                        output.getName(),
                        PortTypeDescription.reference(outputType, input.getName()),
                        output.getParameterPosition(),
                        null);
            }
        }
        DeclaredType classType = environment.getDeclaredType(Class.class);
        for (OperatorPortDeclaration param : parameters) {
            // check is form of M<T>
            TypeMirror paramType = param.getType().getRepresentation();
            if (paramType.getKind() != TypeKind.DECLARED) {
                continue;
            }
            DeclaredType declParamType = (DeclaredType) paramType;
            if (declParamType.getTypeArguments().size() != 1) {
                continue;
            }
            // check is <: Class<?>
            if (types.isSameType(environment.getErasure(paramType), classType) == false) {
                continue;
            }
            if (types.isSameType(declParamType.getTypeArguments().get(0), outputType)) {
                return new OperatorPortDeclaration(
                        output.getKind(),
                        output.getDocumentation(),
                        output.getName(),
                        PortTypeDescription.reference(outputType, param.getName()),
                        output.getParameterPosition(),
                        null);
            }
        }
        return null;
    }

    private OperatorPortDeclaration toPort(
            OperatorPortDeclaration.Kind kind,
            ExecutableAnalyzer analyzer,
            TypeConstraint type,
            int index) {
        assert analyzer != null;
        assert type != null;
        TypeConstraint dataType = type.getTypeArgument();
        if (dataType.isModel() == false) {
            analyzer.error(index, "モデルオブジェクト型以外は入出力に指定できません");
            return null;
        }
        return new OperatorPortDeclaration(
                kind,
                analyzer.getParameterDocument(index),
                analyzer.getParameterName(index),
                PortTypeDescription.reference(dataType.getType(), analyzer.getParameterName(index)),
                index,
                null);
    }

    private OperatorPortDeclaration toParameter(
            ExecutableAnalyzer analyzer,
            int index,
            TypeConstraint type) {
        assert analyzer != null;
        assert type != null;
        if (type.isOperator()) {
            analyzer.error(index, "演算子オブジェクトは引数に直接指定できません");
            return null;
        }
        return new OperatorPortDeclaration(
                OperatorPortDeclaration.Kind.CONSTANT,
                analyzer.getParameterDocument(index),
                analyzer.getParameterName(index),
                PortTypeDescription.direct(type.getType()),
                index,
                null);
    }

    private ExecutableElement findConstructor(TypeElement type) {
        assert type != null;
        List<ExecutableElement> elements = Lists.create();
        for (Element element : type.getEnclosedElements()) {
            if (element.getKind() == ElementKind.CONSTRUCTOR
                    && element.getModifiers().contains(Modifier.PUBLIC)) {
                elements.add((ExecutableElement) element);
            }
        }
        if (elements.isEmpty()) {
            error(type, "フロー部品クラスには明示的なpublicコンストラクターが必要です");
            return null;
        }
        if (elements.size() >= 2) {
            for (ExecutableElement odd : elements) {
                error(odd, "フロー部品クラスに定義可能なpublicコンストラクタは一つまでです");
            }
            return null;
        }
        return elements.get(0);
    }

    /**
     * ここまでに{@link #add(Element)}に指定された要素を元に構築したフロー部品クラスの一覧を返す。
     * @return 構築したフロー部品クラスの一覧
     * @throws OperatorCompilerException 構築に失敗した場合
     */
    public List<FlowPartClass> collect() {
        if (sawError) {
            throw new OperatorCompilerException(null, "フロー部品クラスの分析に失敗したため、処理を中止します");
        }
        return collected;
    }

    private boolean validateClassModifiers(Element element) {
        assert element != null;
        TypeElement type = (TypeElement) element;
        DeclaredType superType = environment.getDeclaredType(FlowDescription.class);
        if (environment.getTypeUtils().isSubtype(type.asType(), superType) == false) {
            raiseInvalidClass(type, MessageFormat.format(
                    "フロー部品クラス{0}は{1}のサブクラスとして宣言する必要があります",
                    "{0}",
                    FlowDescription.class.getName()));
        }
        if (type.getEnclosingElement().getKind() != ElementKind.PACKAGE) {
            raiseInvalidClass(type, "フロー部品クラス{0}はパッケージ直下のトップレベルクラスとして宣言する必要があります");
        }
        if (type.getModifiers().contains(Modifier.PUBLIC) == false) {
            raiseInvalidClass(type, "フロー部品クラス{0}はpublicとして宣言する必要があります");
        }
        if (type.getModifiers().contains(Modifier.ABSTRACT)) {
            raiseInvalidClass(type, "フロー部品クラス{0}はabstractとして宣言できません");
        }
        return true;
    }

    private void validateConstructorModifiers(ExecutableElement ctor) {
        assert ctor != null;
        if (ctor.getThrownTypes().isEmpty() == false) {
            error(ctor, "フロー部品クラスのコンストラクターには例外型を指定できません");
        }
        if (ctor.getTypeParameters().isEmpty() == false) {
            error(ctor, "フロー部品クラスのコンストラクターには型引数を指定できません");
        }
    }

    private void raiseInvalidClass(TypeElement element, String message) {
        error(element, MessageFormat.format(
                message,
                element.getQualifiedName()));
    }

    private void error(Element element, String message) {
        assert element != null;
        assert message != null;
        this.environment.getMessager().printMessage(Diagnostic.Kind.ERROR,
                message,
                element);
        this.sawError = true;
    }
}
TOP

Related Classes of com.asakusafw.compiler.operator.flow.FlowPartClassCollector

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.