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

import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentMap;

import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.Node;

import woolpack.utils.AppendableWriter;
import woolpack.utils.MapIterableMap;
import woolpack.utils.UtilsConstants;

/**
 * 単一リクエストまたは単一スレッドごとの状態を保持するコンテキスト。
 * 本クラスはリクエストごとに生成され単一のスレッドのみからアクセスされる必要がある。
 * 適用しているパターン：Interpreter(Context 役)。
 * @author nakamura
 * 
 */
public class DomContext{
	private String id;
	private Map<String,Object> config;
	private Object container;
	private Map input;
	private Map<String,Object> request;
	private ConcurrentMap<String,Object> session;
	private ConcurrentMap<String,Object> application;
	private Map<String,Object> beforeLocal;
	private Map<String,Object> local;
	private Node node;
	
	/**
	 * コンストラクタ。
	 *
	 */
	public DomContext(){
	}
	
	/**
	 * 浅いコピーコンストラクタ。
	 * サブクラスだけでなく{@link DomExpression}からも呼び出されることを想定しているため public にしている。
	 * @param base コピー元。
	 */
	public DomContext(final DomContext base){
		this.id = base.id;
		this.config = base.config;
		this.container = base.container;
		this.input = base.input;
		this.request = base.request;
		this.session = base.session;
		this.application = base.application;
		this.beforeLocal = (base.local != null)?base.local:base.beforeLocal;
		this.node = base.node;
	}

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

	/**
	 * 変更がコピー元{@link DomContext#getLocal()}には反映されない{@link Map}を返す。
	 * @return テンポラリの{@link Map}。
	 */
	public Map<String,Object> getLocal() {
		if(local == null){
			if(beforeLocal == null){
				local = new HashMap<String,Object>();
			}else{
				local = new MapIterableMap<String,Object>(Arrays.asList(new HashMap<String,Object>(), beforeLocal));
			}
		} 
		return local;
	}
	
	/**
	 * このインスタンスの内容を出力する。テスト/デバッグ用。
	 * @param sb 出力先。
	 * @throws IOException {@link Appendable}が例外を投げた場合。
	 * @throws NullPointerException 引数が null の場合。
	 */
	public void appendTo(final Appendable sb) throws IOException{
		sb.append("DomContext dump information:");
		appendTo(sb, "id", id);
		appendTo(sb, "config", config);
		appendTo(sb, "container", container);
		appendTo(sb, "input", input);
		appendTo(sb, "request", request);
		appendTo(sb, "session", session);
		appendTo(sb, "application", application);
		appendTo(sb, "local", local);
		sb.append("\n,node:");
		if(node != null){
			UtilsConstants.TRANSFORMER_FACTORY.newInstance().transform(
					new DOMSource(node), 
					new StreamResult(new AppendableWriter(sb))
			);
		}
	}
	
	private static void appendTo(final Appendable sb, final String label, final Object o) throws IOException{
		sb.append('\n');
		sb.append(',');
		sb.append(label);
		sb.append(':');
		if(o != null){
			sb.append(o.toString());
		}
	}

	public Node getNode() {
		return node;
	}

	public void setNode(final Node node) {
		this.node = node;
	}

	public Map<String,Object> getConfig() {
		return config;
	}

	public void setConfig(final Map<String,Object> config) {
		this.config = config;
	}

	public Object getContainer() {
		return container;
	}

	public void setContainer(final Object container) {
		this.container = container;
	}

	public Map<String,Object> getRequest() {
		return request;
	}

	public void setRequest(final Map<String,Object> request) {
		this.request = request;
	}

	/**
	 * セッションスコープを返す。
	 * セッションスコープは複数のスレッドからアクセスされる可能性があるので{@link Map}ではなく
	 * {@link ConcurrentMap}として定義することによりセッション利用側での並行性の制御を容易にする。
	 * @return セッションスコープ。
	 */
	public ConcurrentMap<String,Object> getSession() {
		return session;
	}

	/**
	 * セッションスコープを設定する。
	 * セッションスコープは複数のスレッドからアクセスされる可能性があるので{@link Map}ではなく
	 * {@link ConcurrentMap}として定義することによりセッション利用側での並行性の制御を容易にする。
	 * @param session セッションスコープ。
	 */
	public void setSession(final ConcurrentMap<String,Object> session) {
		this.session = session;
	}

	/**
	 * idを返す。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @return id。
	 */
	public String getId() {
		return id;
	}

	/**
	 * id を設定する。
	 * コピー先で設定した値はコピー元には反映されない。
	 * @param id id。
	 */
	public void setId(final String id) {
		this.id = id;
	}

	public Map getInput() {
		return input;
	}

	public void setInput(final Map input) {
		this.input = input;
	}

	/**
	 * アプリケーションスコープを返す。
	 * アプリケーションスコープは複数のスレッドからアクセスされる可能性があるので{@link Map}ではなく
	 * {@link ConcurrentMap}として定義することにより{@link DomExpression}実装クラスにおける並行性の制御を容易にする。
	 * @return アプリケーションスコープ。
	 */
	public ConcurrentMap<String, Object> getApplication() {
		return application;
	}

	/**
	 * アプリケーションスコープを設定する。
	 * アプリケーションスコープは複数のスレッドからアクセスされる可能性があるので{@link Map}ではなく
	 * {@link ConcurrentMap}として定義することにより{@link DomExpression}実装クラスにおける並行性の制御を容易にする。
	 * @param application アプリケーションスコープ。
	 */
	public void setApplication(final ConcurrentMap<String, Object> application) {
		this.application = application;
	}
}
