Package com.asakusafw.modelgen.util

Source Code of com.asakusafw.modelgen.util.JoinedModelBuilder$Side

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

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import com.asakusafw.modelgen.model.JoinedModelDescription;
import com.asakusafw.modelgen.model.ModelDescription;
import com.asakusafw.modelgen.model.ModelProperty;
import com.asakusafw.modelgen.model.Source;



/**
* {@link JoinedModelDescription}を構築するビルダー。
*/
public class JoinedModelBuilder extends ModelBuilder<JoinedModelBuilder> {

    private List<String> columns;

    private Side left;

    private Side right;

    /**
     * インスタンスを生成する。
     * @param name 生成するモデルの名前
     * @param left 結合されるモデル
     * @param leftAlias 結合されるモデルのエイリアス (省略可)
     * @param right 結合するモデル
     * @param rightAlias 結合するモデルのエイリアス (省略可)
     */
    public JoinedModelBuilder(
            String name,
            ModelDescription left,
            String leftAlias,
            ModelDescription right,
            String rightAlias) {
        super(name);
        if (left == null) {
            throw new IllegalArgumentException("left must not be null"); //$NON-NLS-1$
        }
        if (right == null) {
            throw new IllegalArgumentException("right must not be null"); //$NON-NLS-1$
        }
        this.columns = new ArrayList<String>();
        this.left = new Side(left);
        this.right = new Side(right);
        if (leftAlias != null) {
            this.left.alias = leftAlias;
        }
        if (rightAlias != null) {
            this.right.alias = rightAlias;
        }
        if (this.left.alias.equals(this.right.alias)) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "エイリアス名が衝突しています ({0})",
                    this.left.alias));
        }
    }

    /**
     * 結合条件を追加する。
     * @param aProperty 等価結合条件の1つ目の項
     * @param bProperty 等価結合条件の2つ目の項
     * @return このオブジェクト (メソッドチェイン用)
     */
    public JoinedModelBuilder on(String aProperty, String bProperty) {
        if (aProperty == null) {
            throw new IllegalArgumentException("aProperty must not be null"); //$NON-NLS-1$
        }
        if (bProperty == null) {
            throw new IllegalArgumentException("bProperty must not be null"); //$NON-NLS-1$
        }
        Ref a = resolve(aProperty);
        Ref b = resolve(bProperty);
        if (a.side == b.side) {
            throw new IllegalArgumentException(MessageFormat.format(
                    "結合条件は左右で異なるモデルを参照して下さい ({0} = {1})",
                    aProperty,
                    bProperty));
        }
        a.side.condition.add(a.side.find(a.name));
        b.side.condition.add(b.side.find(b.name));
        return this;
    }

    /**
     * カラムの情報を追加する。
     * @param columnName 追加するカラムの名前
     * @param sourceProperty マッピング元のプロパティの指定
     * @return このオブジェクト (メソッドチェイン用)
     */
    public JoinedModelBuilder add(
            String columnName,
            String sourceProperty) {
        if (columnName == null) {
            throw new IllegalArgumentException("columnName must not be null"); //$NON-NLS-1$
        }
        if (sourceProperty == null) {
            throw new IllegalArgumentException("sourceProperty must not be null"); //$NON-NLS-1$
        }
        Ref source = resolve(sourceProperty);
        columns.add(columnName);
        source.side.mapping.put(source.name, columnName);
        return this;
    }

    /**
     * ここまでの情報を元に、{@link JoinedModelDescription}を構築して返す。
     * @return 構築したモデル
     */
    @Override
    public JoinedModelDescription toDescription() {
        if (left.condition.isEmpty()) {
            throw new IllegalStateException(MessageFormat.format(
                    "結合条件が指定されていません ({0})",
                    getReference()));
        }
        if (columns.isEmpty()) {
            throw new IllegalStateException(MessageFormat.format(
                    "プロパティが追加されていません ({0})",
                    getReference()));
        }

        pairingTrivialSource(left, right);
        pairingTrivialSource(right, left);
        pairingSourceWithSameNamed(left, right);
        pairingSourceWithSameNamed(right, left);

        return new JoinedModelDescription(
                getReference(),
                buildProperties(),
                left.condition,
                right.condition);
    }

    private void pairingTrivialSource(Side a, Side b) {
        assert a != null;
        assert b != null;
        // 結合条件になったフィールドが結果に含まれる場合、それもソースとして登録する
        for (int i = 0, n = a.condition.size(); i < n; i++) {
            Source as = a.condition.get(i);
            Source bs = b.condition.get(i);
            if (a.mapping.containsKey(as.getName())
                    && b.mapping.containsKey(bs.getName()) == false) {
                String column = a.mapping.get(as.getName());
                b.mapping.put(bs.getName(), column);
            }
        }
    }

    private void pairingSourceWithSameNamed(Side a, Side b) {
        assert a != null;
        assert b != null;
        // 同名で同型のフィールドが結果に含まれる場合、それもソースとして登録する
        for (Map.Entry<String, String> entry : a.mapping.entrySet()) {
            // マッピング済のものは何もしない
            if (entry.getValue() != null) {
                continue;
            }

            // マッピングされていないもので、逆側でマッピングされている同名のプロパティを探す
            String unmapped = entry.getKey();
            String opposite = b.mapping.get(unmapped);
            if (opposite == null) {
                continue; // 逆側でもマッピングされていない
            }

            // 見つかった場合、型の互換性を確認
            Source as = a.sources.get(unmapped);
            Source bs = b.sources.get(unmapped);
            assert as != null;
            assert bs != null;
            if (as.getType().equals(bs.getType()) == false) {
                continue; // 型に互換性が無い
            }

            // 合致したら、逆側のマッピングに相乗りする
            entry.setValue(opposite);
        }
    }

    private List<ModelProperty> buildProperties() {
        // ペア用のコンテナを作成
        Map<String, SourcePair> pairs = new TreeMap<String, SourcePair>();
        for (String mapTo : columns) {
            pairs.put(mapTo, new SourcePair());
        }

        // 左の情報をペアのコンテナに格納
        for (Map.Entry<String, String> entry : left.mapping.entrySet()) {
            String mapTo = entry.getValue();
            assert mapTo == null || pairs.containsKey(mapTo);
            if (mapTo == null) {
                continue;
            }
            pairs.get(mapTo).left = left.sources.get(entry.getKey());
        }

        // 右の情報をペアのコンテナに格納
        for (Map.Entry<String, String> entry : right.mapping.entrySet()) {
            String mapTo = entry.getValue();
            assert mapTo == null || pairs.containsKey(mapTo);
            if (mapTo == null) {
                continue;
            }
            pairs.get(mapTo).right = right.sources.get(entry.getKey());
        }

        // ペアの情報を元にプロパティを構築
        List<ModelProperty> properties = new ArrayList<ModelProperty>();
        for (String mapTo : columns) {
            SourcePair sources = pairs.get(mapTo);
            assert sources != null;
            assert sources.left != null || sources.right != null;
            ModelProperty property = new ModelProperty(
                    mapTo,
                    sources.left,
                    sources.right);
            properties.add(property);
        }
        return properties;
    }

    private Ref resolve(String source) {
        assert source != null;
        int qualified = source.indexOf('.');
        if (qualified < 0) {
            boolean leftHit = left.mapping.containsKey(source);
            boolean rightHit = right.mapping.containsKey(source);
            if (leftHit && rightHit) {
                throw new IllegalArgumentException(MessageFormat.format(
                        "{0}はあいまいです ({1})",
                        source,
                        getReference()));
            }
            if (leftHit == false && rightHit == false) {
                throw new IllegalArgumentException(MessageFormat.format(
                        "{0}が見つかりません ({1})",
                        source,
                        getReference()));
            }
            if (leftHit) {
                return new Ref(left, source);
            } else {
                return new Ref(right, source);
            }
        } else {
            String qualifier = source.substring(0, qualified);
            String column = source.substring(qualified + 1);
            if (left.alias.equals(qualifier)) {
                return new Ref(left, column);
            } else if (right.alias.equals(qualifier)) {
                return new Ref(right, column);
            } else {
                throw new IllegalArgumentException(MessageFormat.format(
                        "{0}が見つかりません ({1})",
                        qualifier,
                        source));
            }
        }
    }

    private static class Side {

        ModelDescription model;

        Map<String, Source> sources;

        String alias;

        List<Source> condition;

        Map<String, String> mapping;

        Side(ModelDescription model) {
            assert model != null;
            this.model = model;
            this.sources = new TreeMap<String, Source>();
            // FIXME 標準のエイリアス
            this.alias = model.getReference().getSimpleName();
            this.condition = new ArrayList<Source>();
            this.mapping = new TreeMap<String, String>();
            for (Source s : model.getPropertiesAsSources()) {
                sources.put(s.getName(), s);
                mapping.put(s.getName(), null);
            }
        }

        Source find(String columnName) {
            assert columnName != null;
            if (sources.containsKey(columnName)) {
                return sources.get(columnName);
            }
            throw new IllegalArgumentException(MessageFormat.format(
                    "結合条件のカラムが見つかりません({0}.{1})",
                    model.getReference(),
                    columnName));
        }
    }

    private static class Ref {

        Side side;

        String name;

        public Ref(Side side, String name) {
            assert side != null;
            assert name != null;
            this.side = side;
            this.name = name;
        }
    }

    private static class SourcePair {

        Source left;

        Source right;

        SourcePair() {
            return;
        }
    }
}
TOP

Related Classes of com.asakusafw.modelgen.util.JoinedModelBuilder$Side

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.