/*
 * Copyright (c) 2005- Shinji Kashihara.
 * All rights reserved. This program are made available under
 * the terms of the Eclipse Public License v1.0 which accompanies
 * this distribution, and is available at epl-v10.html.
 */
package jp.sourceforge.mergedoc.pleiades.aspect.resource;

import java.util.List;
import java.util.Set;

import jp.sourceforge.mergedoc.pleiades.aspect.advice.JointPoint;
import jp.sourceforge.mergedoc.pleiades.aspect.advice.PleiadesConfig;
import jp.sourceforge.mergedoc.pleiades.aspect.advice.PointCut;
import jp.sourceforge.mergedoc.pleiades.log.Logger;

/**
 * 呼び出し階層エクスプローラーです。
 * <p>
 * @author cypher256
 */
public class CallHierarchyExplorer {

	/** ロガー */
	@SuppressWarnings("unused")
	private static final Logger log = Logger.getLogger(CallHierarchyExplorer.class);

	/** 呼び出しトレースを検査する最大階層 */
	private static final int TRACE_MAX = 30;

	/** このクラスのシングルトン・インスタンス */
	private static final CallHierarchyExplorer singleton = new CallHierarchyExplorer();

	/**
	 * 呼び出し階層探査オブジェクトを取得します。
	 * <p>
	 * @return 呼び出し階層探査オブジェクト
	 */
	public static CallHierarchyExplorer getInstance() {
		return singleton;
	}

	//-------------------------------------------------------------------------

	/** 除外パッケージ・プロパティー */
	private final ExcludePackageProperties excludePackageProperties = ExcludePackageProperties.getInstance();

	/**
	 * 翻訳対象外か判定します。
	 * 呼び出し元のパッケージやクラスを元に判定されます。
	 * <p>
	 * @param en 英語リソース文字列（ニーモニック無し）
	 * @param jointPoint ジョイント・ポイント
	 * @return 翻訳対象外の場合は true
	 */
	public boolean isExcludeTranslation(String en, JointPoint jointPoint) {

		StackTraceElement[] stes = null;

		// properties [%EXCULUDE%] 呼び出し元による除外（訳語とパッケージ単位）
		// xml 定義のアドバイスに ?{JOINT_POINT} 指定は不要。
		Set<String> noTransPathEntries = excludePackageProperties.getPathEntries(en);

		if (noTransPathEntries != null) {
			stes = getStackTrace();
			for (int i = 0; i < TRACE_MAX && i < stes.length; i++) {

				StackTraceElement ste = stes[i];
				String className = ste.getClassName();

				for (String noTransPath : noTransPathEntries) {
					if (className.startsWith(noTransPath)) {
						return true;
					}
				}
			}
		}

		// xml [*clludeTrace] 呼び出し元トレースによる除外と限定（再帰的に遡る）。
		// xml 定義のアドバイスに ?{JOINT_POINT} 指定が必要。
		if (jointPoint != null) {

			PointCut pointCut = PleiadesConfig.getInstance().getPointCut(jointPoint);

			if (pointCut != null) {

				List<JointPoint> excludeTrace = pointCut.getExcludeTrace();
				List<JointPoint> includeTrace = pointCut.getIncludeTrace();

				// 除外指定に一致する場合は除外する
				if (excludeTrace.size() > 0) {
					if (containsTrace(excludeTrace, stes)) {
						return true;
					}
				}

				// 限定指定に一致する場合は除外しない
				if (includeTrace.size() > 0) {
					if (containsTrace(includeTrace, stes)) {
						return false;
					}
					// 一致しない場合は除外する
					return true;
				}
			}
		}

		return false;
	}

	/**
	 * スタックトレースにトレース・リストのジョイント・ポイントが含まれるか判定します。
	 * <p>
	 * @param trace トレース・リスト
	 * @param stes スタックトレース
	 * @return 含まれる場合は true
	 */
	protected boolean containsTrace(List<JointPoint> trace, StackTraceElement[] stes) {

		if (stes == null) {
			stes = getStackTrace();
		}

		for (JointPoint jp : trace) {

			for (int i = 0; i < TRACE_MAX && i < stes.length; i++) {

				StackTraceElement ste = stes[i];

				// クラス名は前方一致
				if (ste.getClassName().startsWith(jp.getClassName())) {

					// メソッド名は完全一致
					String methodName = jp.getMethodName();
					if (methodName == null || ste.getMethodName().equals(methodName)) {

						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * 現在のスレッドのスタックトレースを取得します。
	 * @return スタックトレース
	 */
	public StackTraceElement[] getStackTrace() {
		return Thread.currentThread().getStackTrace();
	}
}
