Package com.asakusafw.modelgen.view

Source Code of com.asakusafw.modelgen.view.ViewAnalyzer$View

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

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

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

import com.asakusafw.modelgen.model.Aggregator;
import com.asakusafw.modelgen.model.JoinedModelDescription;
import com.asakusafw.modelgen.model.ModelDescription;
import com.asakusafw.modelgen.model.ModelRepository;
import com.asakusafw.modelgen.util.JoinedModelBuilder;
import com.asakusafw.modelgen.util.SummarizedModelBuilder;
import com.asakusafw.modelgen.view.model.CreateView;
import com.asakusafw.modelgen.view.model.CreateView.Kind;
import com.asakusafw.modelgen.view.model.Name;
import com.asakusafw.modelgen.view.model.On;
import com.asakusafw.modelgen.view.model.Select;
import com.asakusafw.utils.graph.Graph;
import com.asakusafw.utils.graph.Graphs;

/**
* ビューの構造を解析する。
*/
public class ViewAnalyzer {

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

    private List<View> added = new ArrayList<View>();

    /**
     * この解析器に指定のビューを追加する。
     * @param namespace ビューの名前空間
     * @param view 追加するビューの構造
     * @throws IllegalArgumentException 引数に{@code null}が指定された場合
     */
    public void add(List<String> namespace, CreateView view) {
        if (namespace == null) {
            throw new IllegalArgumentException("namespace must not be null"); //$NON-NLS-1$
        }
        if (view == null) {
            throw new IllegalArgumentException("view must not be null"); //$NON-NLS-1$
        }
        added.add(new View(namespace, view));
    }

    /**
     * これまでに追加されたビューの情報を解析して、{@link ModelDescription}の形式に変換する。
     * @param repository 利用するリポジトリ
     * @return 変換後のモデル
     * @throws IllegalArgumentException 引数に{@code null}が指定された場合
     */
    public List<ModelDescription> analyze(ModelRepository repository) {
        if (repository == null) {
            throw new IllegalArgumentException("repository must not be null"); //$NON-NLS-1$
        }

        LOG.info("ビューの関係を解析しています ({}個のビュー)", added.size());
        List<View> sorted = sort();

        Map<Name, ModelDescription> context =
            new HashMap<Name, ModelDescription>();
        List<ModelDescription> analyzed = new ArrayList<ModelDescription>();
        for (View view : sorted) {
            LOG.info("ビューの構造を解析しています: {}", view.ast.name);
            ModelDescription model = transform(view, repository, context);
            context.put(view.ast.name, model);
            if (model != null) {
                analyzed.add(model);
            }
        }

        if (analyzed.size() != added.size()) {
            throw new IllegalStateException(MessageFormat.format(
                    "解析中にエラーが発生したため、ビューの処理を中断します (処理したビューの個数: {0}/{1})",
                    analyzed.size(),
                    added.size()));
        }
        return analyzed;
    }

    private ModelDescription transform(
            View target,
            ModelRepository repository,
            Map<Name, ModelDescription> context) {
        assert target != null;
        assert repository != null;
        assert context != null;

        CreateView ast = target.ast;

        // 先に外部依存関係を解決してcontextに登録する
        if (resolveDependency(ast, repository, context) == false) {
            return null;
        }

        Kind kind = ast.getKind();
        if (kind == Kind.JOINED) {
            return transformJoined(target, context);
        } else if (kind == Kind.SUMMARIZED) {
            return transformSummarized(target, context);
        } else {
            LOG.error("{}は処理できない種類のビューです: {}",
                    ast.name,
                    ast);
            return null;
        }
    }

    private JoinedModelDescription transformJoined(
            View target,
            Map<Name, ModelDescription> context) {
        assert target != null;
        assert target.ast.getKind() == CreateView.Kind.JOINED;
        assert context != null;
        CreateView ast = target.ast;
        assert context.get(ast.from.table) != null;
        assert context.get(ast.from.join.table) != null;

        JoinedModelBuilder builder = new JoinedModelBuilder(
                ast.name.token,
                context.get(ast.from.table),
                ast.from.alias,
                context.get(ast.from.join.table),
                ast.from.join.alias);
        builder.namespace(target.namespace);

        // 結合条件
        for (On on : ast.from.join.condition) {
            builder.on(on.left.token, on.right.token);
        }

        // カラムの追加
        for (Select select : ast.selectList) {
            assert select.aggregator == Aggregator.IDENT;
            builder.add(select.alias.token, select.name.token);
        }

        return builder.toDescription();
    }

    private ModelDescription transformSummarized(
            View target,
            Map<Name, ModelDescription> context) {
        assert target != null;
        assert target.ast.getKind() == CreateView.Kind.SUMMARIZED;

        CreateView ast = target.ast;
        assert context.get(ast.from.table) != null;

        SummarizedModelBuilder builder = new SummarizedModelBuilder(
                ast.name.token,
                context.get(ast.from.table),
                ast.from.alias);
        builder.namespace(target.namespace);

        // グループ化
        for (Name name : ast.groupBy) {
            builder.groupBy(name.token);
        }

        // カラムの追加
        for (Select select : ast.selectList) {
            builder.add(
                    select.alias.token,
                    select.aggregator,
                    select.name.token);
        }

        return builder.toDescription();
    }

    private boolean resolveDependency(
            CreateView target,
            ModelRepository repository,
            Map<Name, ModelDescription> context) {
        boolean success = true;
        for (Name dependency : target.getDependencies()) {
            LOG.debug("{}の依存先として{}を解決しています",
                    target.name,
                    dependency);
            if (context.containsKey(dependency)) {
                if (context.get(dependency) == null) {
                    LOG.warn("{}は依存先の{}が失敗しているため、変換をスキップします",
                            target.name,
                            dependency);
                    return false;
                }
            } else {
                ModelDescription resolved = repository.find(dependency.token);
                if (resolved == null) {
                    LOG.error("{}は依存先の{}が見つからないため、変換に失敗しました",
                            target.name,
                            dependency);
                    success = false;
                } else {
                    context.put(dependency, resolved);
                }
            }
        }
        return success;
    }

    private List<View> sort() {
        Map<Name, View> map = new HashMap<Name, View>();
        Graph<Name> dependencies = Graphs.newInstance();
        for (View view : added) {
            Name name = view.ast.name;
            map.put(name, view);
            for (Name dependTo : view.ast.getDependencies()) {
                dependencies.addEdge(name, dependTo);
            }
        }

        // 依存関係の循環を指摘
        Set<Set<Name>> circuit = Graphs.findCircuit(dependencies);
        if (circuit.isEmpty() == false) {
            throw new IllegalStateException(MessageFormat.format(
                    "ビューの参照関係に循環が存在します: {0}",
                    circuit));
        }

        // 依存関係の逆順に整列
        List<Name> sorted = Graphs.sortPostOrder(dependencies);

        List<View> results = new ArrayList<View>();
        for (Name name : sorted) {
            // 外部参照でないものについてのみ結果に残す
            View view = map.get(name);
            if (view != null) {
                results.add(view);
            }
        }
        return results;
    }

    private static class View {

        String[] namespace;

        CreateView ast;

        View(List<String> namespace, CreateView ast) {
            assert namespace != null;
            assert ast != null;
            this.namespace = namespace.toArray(new String[namespace.size()]);
            this.ast = ast;
        }
    }
}
TOP

Related Classes of com.asakusafw.modelgen.view.ViewAnalyzer$View

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.