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

import java.util.Map;
import java.util.Map.Entry;

/**
 * Visitor デザインパターンの Visitor 役。 オリジナルの Visitor デザインパターンでは Element 役実装毎に Visitor
 * 役インタフェースの visit メソッドを用意する(オーバロードする)ことを前提にしているが、 本設計では visit メソッドの内部で accept
 * メソッドの役割({@link Acceptable})を分岐する。 適用しているパターン：Visitor。
 * 
 * @author nakamura
 * 
 */
public class Visitor {
	private Map<Object, Acceptable> map;

	private Object context;

	/**
	 * コンストラクタ。
	 * 
	 */
	public Visitor() {
	}

	/**
	 * 浅いコピーコンストラクタ。
	 * 
	 * @param base
	 *            コピー元。
	 */
	protected Visitor(final Visitor base) {
		this.map = base.map;
		this.context = base.context;
	}

	/**
	 * 利用クラスが直接呼び出すか、または
	 * {@link Acceptable#accept(Visitor, Object)}
	 * から呼び出される(called)。
	 * このメソッドはまず引数をキーとして{@link #getMap()}を検索する。
	 * 最初にマッチした場合は対応する{@link Acceptable}に処理を委譲し処理を終了する。
	 * 次に引数のクラス名の代入可能性を{@link #getMap()}からキーを順に取り出して検証する。
	 * 最初にマッチした場合は対応する{@link Acceptable}に処理を委譲し処理を終了する。
	 * いずれもマッチしない場合はなにもしないで処理を終了する。
	 * 
	 * @param o
	 *            Element 役。
	 * @throws NullPointerException
	 *             {@link #map}が null の場合。
	 */
	public void visit(final Object o) {
		{
			final Acceptable a = map.get(o);
			if (a != null) {
				accept(a, o);
				return;
			}
		}
		if (o == null) {
			return;
		}
		final Class clazz = o.getClass();
		for (final Entry<Object, Acceptable> entry : map.entrySet()) {
			if ((entry.getKey() instanceof Class)
					&& ((Class) entry.getKey()).isAssignableFrom(clazz)) {
				accept(entry.getValue(), o);
				break;
			}
		}
	}

	private void accept(final Acceptable a, final Object o) {
		a.accept(this, o);
	}

	/**
	 * コンテキストを返す。 {@link Acceptable#accept(Visitor, Object)}から呼び出されることを想定している。
	 * 
	 * @return コンテキスト。
	 */
	public Object getContext() {
		return context;
	}

	/**
	 * コンテキストを設定する。 このメソッドで設定したコンテキストを
	 * {@link Acceptable#accept(Visitor, Object)}で利用することを想定している。
	 * 
	 * @param context
	 *            コンテキストを設定する。
	 */
	public void setContext(final Object context) {
		this.context = context;
	}

	/**
	 * 委譲先{@link Acceptable}を分岐する定義としての、
	 * Element 役実装またはそのクラスオブジェクトと
	 * {@link Acceptable}の{@link Map}を返す。
	 * 
	 * @return Element 役実装(またはそのクラスオブジェクト)と{@link Acceptable}の{@link Map}。
	 */
	public Map<Object, Acceptable> getMap() {
		return map;
	}

	/**
	 * 委譲先{@link Acceptable}を分岐する定義としての、
	 * Element 役実装(またはそのクラスオブジェクト)と
	 * {@link Acceptable}の{@link Map}を設定する。
	 * 
	 * @param map
	 *            Element 役実装と{@link Acceptable}の{@link Map}。
	 */
	public void setMap(final Map<Object, Acceptable> map) {
		this.map = map;
	}

	/**
	 * 浅いコピーを行う。{@link java.lang.Cloneable}は実装していない。
	 * 
	 * @return コピーされた{@link Visitor}。
	 */
	public Visitor copy() {
		return new Visitor(this);
	}
}
