Package com.asakusafw.compiler.flow

Source Code of com.asakusafw.compiler.flow.JobFlowDriver

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

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.text.MessageFormat;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.asakusafw.compiler.common.Precondition;
import com.asakusafw.runtime.util.TypeUtil;
import com.asakusafw.utils.collections.Lists;
import com.asakusafw.vocabulary.external.ExporterDescription;
import com.asakusafw.vocabulary.external.ImporterDescription;
import com.asakusafw.vocabulary.flow.Export;
import com.asakusafw.vocabulary.flow.FlowDescription;
import com.asakusafw.vocabulary.flow.Import;
import com.asakusafw.vocabulary.flow.In;
import com.asakusafw.vocabulary.flow.JobFlow;
import com.asakusafw.vocabulary.flow.Out;
import com.asakusafw.vocabulary.flow.graph.FlowGraph;

/**
* {@link JobFlow}が付与されている要素について、フローの構造を解析する。
*/
public final class JobFlowDriver {

    static final Logger LOG = LoggerFactory.getLogger(JobFlowDriver.class);

    private Class<? extends FlowDescription> description;

    private JobFlowClass jobFlowClass;

    private List<String> diagnostics;

    private JobFlowDriver(Class<? extends FlowDescription> description) {
        assert description != null;
        this.description = description;
        this.diagnostics = Lists.create();
    }

    /**
     * インスタンスを生成する。
     * @param description 対象のジョブフロークラス
     * @return 生成したインスタンス
     * @throws IllegalArgumentException 引数に{@code null}が指定された場合
     */
    public static JobFlowDriver analyze(Class<? extends FlowDescription> description) {
        Precondition.checkMustNotBeNull(description, "description"); //$NON-NLS-1$
        JobFlowDriver analyzer = new JobFlowDriver(description);
        analyzer.analyze();
        return analyzer;
    }

    /**
     * 解析結果のジョブフロークラス情報を返す。
     * @return 解析結果のジョブフロークラス情報、解析に失敗した場合は{@code null}
     */
    public JobFlowClass getJobFlowClass() {
        return jobFlowClass;
    }

    /**
     * 解析対象のジョブフロークラスを返す。
     * @return 解析対象のジョブフロークラス
     */
    public Class<? extends FlowDescription> getDescription() {
        return description;
    }

    /**
     * 解析結果の診断メッセージを返す。
     * @return 解析結果の診断メッセージ
     */
    public List<String> getDiagnostics() {
        return diagnostics;
    }

    private void analyze() {
        JobFlow config = findConfig();
        Constructor<? extends FlowDescription> ctor = findConstructor();
        if (ctor == null) {
            return;
        }
        FlowDescriptionDriver driver = parseParameters(ctor);
        if (hasError()) {
            return;
        }
        FlowDescription instance = newInstance(ctor, driver.getPorts());
        if (hasError()) {
            return;
        }
        try {
            FlowGraph graph = driver.createFlowGraph(instance);
            this.jobFlowClass = new JobFlowClass(config, graph);
        } catch (Exception e) {
            error(e, "{0}の解析に失敗しました ({1})", description.getName(), e.toString());
        }
    }

    private FlowDescription newInstance(
            Constructor<? extends FlowDescription> ctor,
            List<?> ports) {
        assert ctor != null;
        assert ports != null;
        try {
            return ctor.newInstance(ports.toArray());
        } catch (Exception e) {
            error(e, "{0}のインスタンス生成に失敗しました ({1})", description.getName(), e.toString());
            return null;
        }
    }

    private JobFlow findConfig() {
        if (description.getEnclosingClass() != null) {
            error(null, "ジョブフロークラスはトップレベルクラスとして宣言する必要があります");
        }
        if (Modifier.isPublic(description.getModifiers()) == false) {
            error(null, "ジョブフロークラスはpublicで宣言する必要があります");
        }
        if (Modifier.isAbstract(description.getModifiers())) {
            error(null, "ジョブフロークラスはabstractで宣言できません");
        }
        JobFlow conf = description.getAnnotation(JobFlow.class);
        if (conf == null) {
            error(null, "ジョブフロークラスには@JobFlow注釈の付与が必要です");
        }
        return conf;
    }

    private Constructor<? extends FlowDescription> findConstructor() {
        @SuppressWarnings("unchecked")
        Constructor<? extends FlowDescription>[] ctors =
            (Constructor<? extends FlowDescription>[]) description.getConstructors();
        if (ctors.length == 0) {
            error(null, "ジョブフロークラスにはpublicコンストラクターの宣言が必要です");
            return null;
        } else if (ctors.length >= 2) {
            error(null, "ジョブフロークラスにはpublicコンストラクターを一つだけ宣言できます");
            return ctors[0];
        } else {
            return ctors[0];
        }
    }

    /**
     * この解析結果にエラーが含まれている場合のみ{@code true}を返す。
     * @return 解析結果にエラーが含まれている場合のみ{@code true}
     */
    public boolean hasError() {
        return diagnostics.isEmpty() == false;
    }

    private FlowDescriptionDriver parseParameters(Constructor<?> ctor) {
        assert ctor != null;
        List<Parameter> rawParams = parseRawParameters(ctor);
        FlowDescriptionDriver driver = new FlowDescriptionDriver();
        for (Parameter raw : rawParams) {
            analyzeParameter(raw, driver);
        }
        return driver;
    }

    private void analyzeParameter(Parameter parameter, FlowDescriptionDriver driver) {
        assert parameter != null;
        assert driver != null;
        if (parameter.raw == In.class) {
            analyzeInput(parameter, driver);
        } else if (parameter.raw == Out.class) {
            analyzeOutput(parameter, driver);
        } else {
            error(
                    null,
                    "ジョブフローのコンストラクターにはInかOutの入出力のみを指定できます ({0}番目の引数)",
                    parameter.getPosition());
        }
    }

    private void analyzeInput(Parameter parameter, FlowDescriptionDriver driver) {
        assert parameter != null;
        assert driver != null;
        Type dataType = invoke(In.class, parameter.type);
        if (dataType == null) {
            error(
                    null,
                    "ジョブフローの入力には型引数としてデータの種類を指定する必要があります ({0}番目の引数)",
                    parameter.getPosition());
            return;
        }
        if (parameter.exporter != null) {
            error(
                    null,
                    "ジョブフローの入力には@Exportを指定できません ({0}番目の引数)",
                    parameter.getPosition());
        }
        if (parameter.importer == null) {
            error(
                    null,
                    "ジョブフローの入力には@Importを指定する必要があります ({0}番目の引数)",
                    parameter.getPosition());
            return;
        } else {
            String name = parameter.importer.name();
            if (driver.isValidName(name) == false) {
                error(
                        null,
                        "ジョブフローの入力の名前 \"{1}\" が正しくありません ({0}番目の引数)",
                        parameter.getPosition(),
                        name);
                return;
            }
            Class<? extends ImporterDescription> aClass = parameter.importer.description();
            ImporterDescription importer;
            try {
                importer = aClass.newInstance();
            } catch (Exception e) {
                error(
                        e,
                        "インポーター記述{0} ({1}番目の引数) の解析に失敗しました (インスタンス化に失敗しました)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }

            if (importer.getModelType() == null) {
                error(
                        null,
                        "インポーター記述{0} ({1}番目の引数) の解析に失敗しました (インポーター記述にデータの種類が指定されていません)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }
            if (dataType.equals(importer.getModelType()) == false) {
                error(
                        null,
                        "インポーター記述{0} ({1}番目の引数) の解析に失敗しました (入力の型とインポーター記述の型が一致しません)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }
            driver.createIn(name, importer);
        }
    }

    private void analyzeOutput(Parameter parameter, FlowDescriptionDriver driver) {
        assert parameter != null;
        assert driver != null;
        Type dataType = invoke(Out.class, parameter.type);
        if (dataType == null) {
            error(
                    null,
                    "ジョブフローの出力には型引数としてデータの種類を指定する必要があります ({0}番目の引数)",
                    parameter.getPosition());
            return;
        }
        if (parameter.importer != null) {
            error(
                    null,
                    "ジョブフローの出力には@Importを指定できません ({0}番目の引数)",
                    parameter.getPosition());
        }
        if (parameter.exporter == null) {
            error(
                    null,
                    "ジョブフローの出力には@Exportを指定する必要があります ({0}番目の引数)",
                    parameter.getPosition());
            return;
        } else {
            String name = parameter.exporter.name();
            if (driver.isValidName(name) == false) {
                error(
                        null,
                        "ジョブフローの出力の名前 \"{1}\" が正しくありません ({0}番目の引数)",
                        parameter.getPosition(),
                        name);
                return;
            }
            Class<? extends ExporterDescription> aClass = parameter.exporter.description();

            ExporterDescription exporter;
            try {
                exporter = aClass.newInstance();
            } catch (Exception e) {
                error(
                        e,
                        "エクスポーター記述{0} ({1}番目の引数) の解析に失敗しました (インスタンス化に失敗しました)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }

            if (exporter.getModelType() == null) {
                error(
                        null,
                        "エクスポーター記述{0} ({1}番目の引数) の解析に失敗しました (エクスポーター記述にデータの種類が指定されていません)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }
            if (dataType.equals(exporter.getModelType()) == false) {
                error(
                        null,
                        "エクスポーター記述{0} ({1}番目の引数) の解析に失敗しました (出力の型とエクスポーター記述の型が一致しません)",
                        aClass.getName(),
                        parameter.getPosition());
                return;
            }
            driver.createOut(name, exporter);
        }
    }

    private void error(
            Throwable reason,
            String message,
            Object... args) {
        StringBuilder buf = new StringBuilder();
        buf.append(format(message, args));
        buf.append(" - ");
        buf.append(description.getName());
        String text = buf.toString();
        diagnostics.add(text);
        if (reason == null) {
            LOG.error(text);
        } else {
            LOG.error(text, reason);
        }
    }

    private String format(String message, Object... args) {
        assert message != null;
        assert args != null;
        if (args.length == 0) {
            return message;
        } else {
            return MessageFormat.format(message, args);
        }
    }

    private Type invoke(Class<?> target, Type subtype) {
        assert target != null;
        assert subtype != null;
        List<Type> invoked = TypeUtil.invoke(target, subtype);
        if (invoked == null || invoked.size() != 1) {
            return null;
        }
        return invoked.get(0);
    }

    private List<Parameter> parseRawParameters(Constructor<?> ctor) {
        assert ctor != null;
        Class<?>[] rawTypes = ctor.getParameterTypes();
        Type[] types = ctor.getGenericParameterTypes();
        Annotation[][] annotations = ctor.getParameterAnnotations();
        List<Parameter> results = Lists.create();
        for (int i = 0; i < types.length; i++) {
            Import importer = null;
            Export expoter = null;
            for (Annotation a : annotations[i]) {
                if (a.annotationType() == Import.class) {
                    importer = (Import) a;
                } else if (a.annotationType() == Export.class) {
                    expoter = (Export) a;
                }
            }
            results.add(new Parameter(
                    i,
                    rawTypes[i],
                    types[i],
                    importer,
                    expoter));
        }
        return results;
    }

    private static class Parameter {

        final int index;

        final Class<?> raw;

        final Type type;

        final Import importer;

        final Export exporter;

        Parameter(
                int index,
                Class<?> raw,
                Type type,
                Import importer,
                Export exporter) {
            assert raw != null;
            assert type != null;
            this.index = index;
            this.raw = raw;
            this.type = type;
            this.importer = importer;
            this.exporter = exporter;
        }

        int getPosition() {
            return index + 1;
        }
    }
}
TOP

Related Classes of com.asakusafw.compiler.flow.JobFlowDriver

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.