/*
 * Copyright 2004-2015 the Seasar Foundation and the Others.
 *
 * 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 org.seasar.extension.dxo.util;

import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;

import org.seasar.framework.util.MethodUtil;
import org.seasar.framework.util.StringUtil;
import org.seasar.framework.util.Tokenizer;

/**
 * Dxoのユーティリティクラスです。
 * 
 * @author koichik
 * @author higa
 */
public class DxoUtil {

    /** genericな{@link Map}から値の型を取得するためのメソッドです。 */
    protected static final Method GET_VALUE_TYPE_OF_TARGET_MAP_METHOD = getValueTypeOfTargetMapMethod();

    /**
     * S2-Tigerが利用可能な場合、Dxoのメソッドからgenericな{@link List}である変換先の要素型を返します。
     * 
     * @param method
     *            Dxoのメソッド
     * @return Dxoのメソッドからgenericな{@link List}である変換先の要素型
     */
    public static Class getElementTypeOfList(final Method method) {
        final Class[] parameterTypes = method.getParameterTypes();
        return parameterTypes.length == 1 ? MethodUtil
                .getElementTypeOfListFromReturnType(method) : MethodUtil
                .getElementTypeOfListFromParameterType(method, 1);
    }

    /**
     * S2-Tigerが利用可能な場合、Dxoのメソッドからgenericな{@link Map}である変換先の値の型を返します。
     * 
     * @param method
     *            Dxoのメソッド
     * @return Dxoのメソッドからgenericな{@link Map}である変換先の値の型
     */
    public static Class getValueTypeOfTargetMap(final Method method) {
        if (GET_VALUE_TYPE_OF_TARGET_MAP_METHOD == null) {
            return null;
        }
        return (Class) MethodUtil.invoke(GET_VALUE_TYPE_OF_TARGET_MAP_METHOD,
                null, new Object[] { method });
    }

    /**
     * S2-Tigerが利用可能な場合、Dxoのメソッドからgenericな{@link Map}である変換先の値の型を返すメソッドを返します。
     * 
     * @return Dxoのメソッドからgenericな{@link Map}である変換先の値の型を返すメソッド
     */
    protected static Method getValueTypeOfTargetMapMethod() {
        try {
            final Class clazz = Class
                    .forName("org.seasar.extension.dxo.util.DxoTigerUtil");
            return clazz.getMethod("getValueTypeOfTargetMap",
                    new Class[] { Method.class });
        } catch (final Throwable ignore) {
        }
        return null;
    }

    /**
     * 変換ルールを解析した結果の式オブジェクトを返します。
     * 
     * @param rule
     *            変換ルール
     * @return 変換ルールを解析した結果の式オブジェクト
     */
    public static Expression parseRule(final String rule) {
        if (StringUtil.isEmpty(rule)) {
            return null;
        }
        Expression exp = SimpleExpressionParser.parse(rule);
        if (exp == null) {
            exp = new OgnlExpression(rule);
        }
        return exp;
    }

    /**
     * <code>CONVERSION_RULE</code>アノテーションで指定される簡素な{@link Map}表現を OGNLの{@link Map}リテラルに変換して返します。
     * <p>
     * 簡素な{@link Map}表現は次の形式の文字列です。
     * </p>
     * 
     * <pre>
     * key1 : value1, key2 : value2 ...
     * </pre>
     * 
     * <p>
     * これをOGNLの{@link Map}リテラルにするため，キーをシングルクオートで囲んだ文字列を返します。
     * 値に複雑な式が指定されると適切に変換できない場合があります。 そのような場合は簡素な{@link Map}表現ではなく、 OGNLの{@link Map}リテラルを使用してください。
     * </p>
     * 
     * @param expr
     *            <code>CONVERSION_RULE</code>アノテーションで指定される簡素な{@link Map}表現
     * @return OGNLの{@link Map}リテラル
     */
    protected static String addQuote(final String expr) {
        final MyTokenizer tokenizer = new MyTokenizer(expr);
        final StringBuffer buf = new StringBuffer(expr.length() + 10);
        for (int token = tokenizer.nextToken(); token != Tokenizer.TT_EOF;) {
            if (token == Tokenizer.TT_QUOTE || token == Tokenizer.TT_WORD) {
                buf.append('\'').append(tokenizer.getStringValue())
                        .append('\'');
            } else {
                throw new IllegalArgumentException(expr + "(" + token + ")");
            }
            token = tokenizer.nextToken();
            if (token == MyTokenizer.TT_COLON) {
                buf.append((char) token);
            } else {
                throw new IllegalArgumentException(expr + "(" + token + ")");
            }
            token = tokenizer.nextToken();
            if (token == Tokenizer.TT_QUOTE) {
                buf.append('\'').append(tokenizer.getStringValue())
                        .append('\'');
            } else if (token == Tokenizer.TT_WORD) {
                buf.append(tokenizer.getStringValue());
            } else {
                throw new IllegalArgumentException(expr + "(" + token + ")");
            }
            token = tokenizer.nextToken();
            if (token == MyTokenizer.TT_COMMA) {
                buf.append((char) token);
                token = tokenizer.nextToken();
            } else if (token == Tokenizer.TT_EOF) {
                break;
            } else {
                throw new IllegalArgumentException(expr + "(" + token + ")");
            }
        }
        return buf.toString();
    }

    /**
     * <code>CONVERSION_RULE</code>アノテーションで指定される簡素な{@link Map}表現からトークンを取り出すクラスです。
     */
    protected static class MyTokenizer extends Tokenizer {

        /** コロン */
        public static final int TT_COLON = ':';

        /** カンマ */
        public static final int TT_COMMA = ',';

        /** 単語の構成要素ではない文字の配列 */
        private static byte[] defaultCtype = new byte[256];

        static {
            setup(defaultCtype);
        }

        /**
         * インスタンスを構築します。
         * 
         * @param str
         *            <code>CONVERSION_RULE</code>アノテーションで指定される簡素な{@link Map}表現
         */
        public MyTokenizer(final String str) {
            this(str, defaultCtype);
        }

        /**
         * インスタンスを構築します。
         * 
         * @param str
         *            <code>CONVERSION_RULE</code>アノテーションで指定される簡素な{@link Map}表現
         * @param ctype
         */
        public MyTokenizer(final String str, final byte[] ctype) {
            super(str, ctype);
        }

        protected static void setup(final byte[] ctype2) {
            Tokenizer.setup(ctype2);
            ordinaryChar(ctype2, TT_COLON);
            ordinaryChar(ctype2, TT_COMMA);
        }

    }

}
