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

import java.io.IOException;

import javassist.CannotCompileException;
import javassist.CtBehavior;
import javassist.CtClass;
import javassist.CtConstructor;
import javassist.NotFoundException;
import jp.sourceforge.mergedoc.pleiades.log.Logger;
import jp.sourceforge.mergedoc.pleiades.runtime.advice.PleiadesConfig;
import jp.sourceforge.mergedoc.pleiades.runtime.resource.ExcludeClassNameCache;
import jp.sourceforge.mergedoc.pleiades.runtime.resource.TransformedClassCache;

/**
 * 翻訳処理を埋め込むためのバイトコード変換を行うトランスフォーマーです。
 * <p>
 * @author cypher256
 */
public class TranslationTransformer extends AbstractTransformer {

	/** ロガー */
	private static final Logger log = Logger.getLogger(TranslationTransformer.class);

	/**
	 * クラスパス追加対象か判定します。
	 * <br>
	 * LauncherEclipseTransformer#init でコア・プラグインは追加済みですが、外部配置および ADT などで
	 * configuration/org.eclipse.osgi/bundles/8/1/.cp/libs/ などに動的配置された場合の対応です。
	 * <br>
	 * この処理は Eclipse に依存しているため、
	 * サブクラス TranslationEclipseTransformer を作成し実装することが望まれます。
	 * @throws IOException 入出力例外が発生した場合
	 * @throws NotFoundException クラスが見つからない場合
	 */
	@Override
	protected boolean isClassPathTarget(String fullPath, String jarFileName) throws IOException, NotFoundException {

		if (jarFileName.startsWith("org.eclipse.")) {
			if (
				jarFileName.startsWith("org.eclipse.core.resources_") ||
				jarFileName.startsWith("org.eclipse.core.runtime_") ||
				jarFileName.startsWith("org.eclipse.debug.core_") ||
				jarFileName.startsWith("org.eclipse.dltk.core_") ||
				jarFileName.startsWith("org.eclipse.equinox.common_") ||
				jarFileName.startsWith("org.eclipse.jdt.core_") ||
				jarFileName.startsWith("org.eclipse.jdt.ui_") ||
				jarFileName.startsWith("org.eclipse.jface_") ||
				jarFileName.startsWith("org.eclipse.osgi_") ||
				jarFileName.startsWith("org.eclipse.swt.") ||
				jarFileName.startsWith("org.eclipse.ui.workbench_")
			) {
				return true;
			}
		} else if (
				jarFileName.startsWith("mergedoc.jstyle.swt") ||

				// ADT CannotCompileException 回避。多くなれば XML 定義。2013.02.10
				// lint-api.jar などはロードが遅れるため追加しない (pleiades-config.xml で対応)
				jarFileName.startsWith("com.android.ide.eclipse.adt_") ||
				jarFileName.equals("assetstudio.jar") ||
				jarFileName.equals("sdklib.jar")
		) {
			log.debug("クラスパス追加 %s", fullPath);
			return true;
		}
		return false;
	}

	/**
	 * バイトコードを変換します。
	 * <p>
	 * {@link TranslationEditor 翻訳エディター} を使用し、バイトコードを変換します。
	 * ただし、{@link ExcludeClassNameCache 変換除外クラス名キャッシュ}
	 * に処理するクラス名が含まれる場合、何も行いません。
	 */
	@Override
	protected byte[] transformBytecode(
			String classId,
			String className,
			byte[] bytecode)
		throws CannotCompileException, NotFoundException, IOException {

		// 変換除外クラスの場合は何もしない
		ExcludeClassNameCache excludeClassCache = ExcludeClassNameCache.getInstance();
		if (excludeClassCache.contains(classId)) {
			return null;
		}

		// 変換済みクラス・キャッシュがある場合はそれを返す
		TransformedClassCache classCache = TransformedClassCache.getInstance();
		byte[] cachedBytecode = classCache.get(classId);
		if (cachedBytecode != null) {
			return cachedBytecode;
		}

		// 定義済みの変換除外クラス
		PleiadesConfig config = PleiadesConfig.getInstance();
		if (config.isExcludePackage(className)) {
			excludeClassCache.addNextLaunch(classId);
			return null;
		}

		// バイトコードに翻訳アスペクトを埋め込み
		CtClass ctClass = createCtClass(bytecode);

		try {
			byte[] transformedBytecode = transformClass(ctClass);

			// 次回起動用の情報を作成
			if (classCache.isActive()) {

				if (transformedBytecode == null) {
					if (!classCache.contains(classId)) {

						// 変換対象外の場合は、変換除外リストに追加
						excludeClassCache.addNextLaunch(classId);
					}
				} else {
					// 変換した場合は、変換済みクラス・キャッシュに追加
					classCache.putNextLaunch(classId, transformedBytecode);
				}
			}
			return transformedBytecode;

		} catch (CannotCompileException e) {

			// 関連クラス未ロードでコンパイル不可、次回変換除外
			excludeClassCache.addNextLaunch(classId);
			throw e;
		}
	}

	/**
	 * クラス・オブジェクトに翻訳アスペクトを埋め込みます。
	 * <p>
	 * @param ctClass 変換対象クラス・オブジェクト
	 * @return 埋め込み後のバイトコード。埋め込み対象でない場合は null。
	 */
	protected byte[] transformClass(CtClass ctClass)
		throws CannotCompileException, NotFoundException, IOException {

		long start = System.nanoTime();

		// コンストラクター、メソッドの変換
		TranslationEditor editor = new TranslationEditor(ctClass);
		for (CtBehavior ctBehavior : ctClass.getDeclaredBehaviors()) {

			// コードを検査し、呼び出し部分を編集
			ctBehavior.instrument(editor);

			// メソッドを編集
			editor.editBehavior(ctBehavior);
		}

		// スタティック・イニシャライザーの変換
		CtConstructor ctInitializer = ctClass.getClassInitializer();
		if (ctInitializer != null) {
			ctInitializer.instrument(editor);
		}

		Analyses.end(TranslationTransformer.class, "transformClass", start);
		return editor.toBytecode();
	}

	/**
	 * クラス記述子をログへ出力するデバッグ用のトランスフォーマーです。
	 * 対象クラス名は pleiades-config.xml に定義しておきます。
	 */
	protected static class LoggingDescTranslationTransformer extends TranslationTransformer {

		private final String logDescClassName;

		public LoggingDescTranslationTransformer(String logDescClassName) {
			this.logDescClassName = logDescClassName;
		}

		@Override
		protected CtClass createCtClass(byte[] bytecode) throws IOException, NotFoundException {

			CtClass ctClass = super.createCtClass(bytecode);

			if (ctClass.getName().startsWith(logDescClassName)) {
				logDescriptor(ctClass);
			}
			return ctClass;
		}
	}
}
