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

/**
 * 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){
		if(map.containsKey(o)){
			accept(o, o);
			return;
		}
		if(o == null){
			return;
		}
		final Class clazz = o.getClass();
		for(final Object key:map.keySet()){
			if(!(key instanceof Class)){
				continue;
			}
			if(!((Class)key).isAssignableFrom(clazz)){
				continue;
			}
			accept(key, o);
			return;
		}
	}
	
	private void accept(final Object key, final Object o){
		map.get(key).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);
	}
}
