/*
 * Copyright 2006 Takahiro Nakamura.
 *
 * 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 woolpack.adapter;

import java.util.Map;
import java.util.WeakHashMap;

import ognl.MethodFailedException;
import ognl.Ognl;
import ognl.OgnlException;
import ognl.TypeConverter;
import woolpack.el.AbstractEL;
import woolpack.el.ELTargetRuntimeException;
import woolpack.typeconvert.TypeConvertUtils;

/**
 * OGNL を使用した{@link woolpack.el.EL}(Object Graph Expression)です。
 * 本クラスは不変です。 本クラスは OGNL のライブラリを使用します。
 * <br/>適用しているデザインパターン：Adapter。
 * 
 * @author nakamura
 * 
 */
public class OGE extends AbstractEL {
	private static final TypeConverter DEFAULT_CONVERTER =
		new ConvertableTypeConverter(TypeConvertUtils.COLLECTION_CONVERTER);
	
	private static Map<String, Object> cache = new WeakHashMap<String, Object>();

	private final String expression;
	private final TypeConverter typeConverter;
	private final Object tree;
	
	/**
	 * キャッシュに使用する{@link Map}を設定します。
	 * 以前に設定されていたキャッシュの{@link Map}は本クラスからから参照されなくなります。
	 * 本クラスでは初期キャッシュとして{@link WeakHashMap}を使用します。
	 * 本メソッドは同期化されません。
	 * 
	 * @param newCache キャッシュに使用する{@link Map}。
	 */
	public static void changeCache(final Map<String, Object> newCache) {
		final Map<String, Object> oldCache = cache;
		newCache.putAll(oldCache);
		cache = newCache;
	}
	
	private static Object registerToMap(final String expression)
			throws OgnlException {
		Object myTree = cache.get(expression);
		if (myTree == null) {
			myTree = Ognl.parseExpression(expression);
			cache.put(expression, myTree);
		}
		return myTree;
	}

	/**
	 * @param expression OGNL の文字列表現。
	 * @param typeConverter 型の変換器。
	 * @throws IllegalArgumentException ({@link OgnlException})OGNL の解析に失敗した場合。
	 */
	public OGE(
			final String expression,
			final TypeConverter typeConverter) {
		super();
		try {
			this.tree = registerToMap(expression);
		} catch (final OgnlException exception) {
			throw new IllegalArgumentException("error at [" + expression + "]", exception);
		}
		this.expression = expression;
		this.typeConverter = typeConverter;
	}

	/**
	 * @param expression OGNL の文字列表現。
	 */
	public OGE(final String expression) {
		this(expression, DEFAULT_CONVERTER);
	}

	abstract class AbstractOGE {
		AbstractOGE() {
		}
		abstract Object execute(Map context) throws OgnlException;
	}

	private Object execute(final Object root, final AbstractOGE oge2) {
		final Map context = Ognl.createDefaultContext(root);
		final TypeConverter defaultTypeConverter = Ognl
				.getTypeConverter(context);
		Ognl.setTypeConverter(context, typeConverter);
		try {
			return oge2.execute(context);
		} catch (final MethodFailedException e) {
			if (e.getReason() instanceof NoSuchMethodException) {
				throw new UnsupportedOperationException("error at [" + expression + "]", e);
			} else {
				throw new ELTargetRuntimeException(e.getReason());
			}
		} catch (final OgnlException e) {
			throw new IllegalStateException("error at [" + expression + "]", e);
		} finally {
			Ognl.setTypeConverter(context, defaultTypeConverter);
		}
	}

	/**
	 * @throws ELTargetRuntimeException メソッドがエラーを投げた場合。
	 * @throws UnsupportedOperationException ({@link NoSuchMethodException})メソッドが存在しない場合。
	 * @throws ELTargetRuntimeException メソッドが存在しない場合以外で呼出が失敗した場合。
	 * @throws IllegalStateException ({@link OgnlException})OGNL の解析に失敗した場合。
	 */
	@Override
	public Object getValue(final Object root, final Class clazz) {
		return execute(root, new AbstractOGE() {
			@Override
			Object execute(final Map context) throws OgnlException {
				if (Object.class.equals(clazz)) {
					return Ognl.getValue(tree, context, root);
				} else {
					return Ognl.getValue(tree, context, root, clazz);
				}
			}
		});
	}

	/**
	 * @throws ELTargetRuntimeException メソッドがエラーを投げた場合。
	 * @throws UnsupportedOperationException ({@link NoSuchMethodException})メソッドが存在しない場合。
	 * @throws ELTargetRuntimeException メソッドが存在しない場合以外で呼出が失敗した場合。
	 * @throws IllegalStateException ({@link OgnlException})OGNL の解析に失敗した場合。
	 */
	@Override
	public boolean setValue(final Object root, final Object value) {
		execute(root, new AbstractOGE() {
			@Override
			Object execute(final Map context) throws OgnlException {
				Ognl.setValue(tree, context, root, value);
				return null;
			}
		});
		return true;
	}

	public String getExpression() {
		return expression;
	}
	public TypeConverter getTypeConverter() {
		return typeConverter;
	}
}
