<?php
/**
 * 
 * @author keiji
 * @package aowp.aspect.weaver.command
 */
/**
 * 
 * @author keiji
 * @package aowp.aspect.weaver.command
 */
class AOWP_MethodCallJoinPointWeaveCommand implements AOWP_IWeaveCommand {

	public function weaveBeforeAdvice(AOWP_Advice &$advice, AOWP_JoinPoint &$joinPoint) {
		$objectPropertyElement = $joinPoint->getAST();
		
		// 連結したメソッド呼び出し (フィールド参照) の場合に、それらを単純な文に変換。
		if ($objectPropertyElement->getParent()->getPropertyCount() > 1) {
			$joinPoint->setAST(AOWP_WeavingASTHelper::apartMethodCallElement($objectPropertyElement));
			$objectPropertyElement = $joinPoint->getAST();
		}
		
		// ラッピングする関数を作成。
		$wrappingFunctionElement = new AOWP_PHPFunctionElement(AOWP_WeavingASTHelper::getRandomName('function'));
		$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('object', true));
		foreach ($objectPropertyElement->arguments as $argument) {
			$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('parameter', true));
		}
		// includeの為のautoload関数の設定。
		$wrappingFunctionElement->setElement(AOWP_WeavingASTHelper::createIncludeStatemenetElement($joinPoint->getFileFullPath()));
		// アスペクトのインスタンス化。
		$aspectInstantiationElement = AOWP_WeavingASTHelper::createAspectInstantiationAST($advice->getClassNameOfAspect());
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($aspectInstantiationElement));
		// ジョインポイントのインスタンス化。
		$joinPointInstantiationElement = AOWP_WeavingASTHelper::createJoinPointInstantiationAST($joinPoint);
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInstantiationElement));
		// 呼び出されるオブジェクトを、ジョインポイントに設定。
		$joinPointInvokedObjectSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'setInvokedObject');
		$joinPointInvokedObjectSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName(0)));
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInvokedObjectSetElement));
		// メソッド呼び出しの引き数を、ジョインポイントに設定。
		for ($i = 1; $i < $wrappingFunctionElement->getParameterCount(); $i++) {
			$joinPointArgumentSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'addArgument');
			$joinPointArgumentSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName($i)));
			$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointArgumentSetElement));
		}
		// アドバイスを実行するIf文。
		$adviceExecutionElement = AOWP_WeavingASTHelper::createAdviceExecutionAST($aspectInstantiationElement->getLeftVarialeName(),
			$advice->getIndex(), $joinPointInstantiationElement->getLeftVarialeName());
		$wrappingFunctionElement->setElement($adviceExecutionElement);
		// リターン文。
		$returnStatementElement = AOWP_MethodCallJoinPointWeaveCommand::_createReturnStatementWithJoinPoint($joinPointInstantiationElement->getLeftVarialeName(), $objectPropertyElement->getPropertyName(), $wrappingFunctionElement->getParameterCount() - 1);
		$wrappingFunctionElement->setElement($returnStatementElement);

		// ラッピング関数の重複定義を防ぐためのIf文を追加。
		$ifFunctionDeclarationElement = AOWP_WeavingASTHelper::createIfForFunctionDeclaration($wrappingFunctionElement);

		// ラッピング関数を、元のコードに追加。
		AOWP_WeavingASTHelper::insertElement($objectPropertyElement, $ifFunctionDeclarationElement);
		
		// 対象のメソッド呼び出しを、作成したラッピング関数の呼び出しに変更する。
		AOWP_MethodCallJoinPointWeaveCommand::_changeIntoWrapingFunctionCall($objectPropertyElement, $wrappingFunctionElement->getFunctionName());
	}

	public function weaveAfterAdvice(AOWP_Advice &$advice, AOWP_JoinPoint &$joinPoint) {
		$objectPropertyElement = $joinPoint->getAST();
		
		// 連結したメソッド呼び出し (フィールド参照) の場合に、それらを単純な文に変換。
		if ($objectPropertyElement->getParent()->getPropertyCount() > 1) {
			$joinPoint->setAST(AOWP_WeavingASTHelper::apartMethodCallElement($objectPropertyElement));
			$objectPropertyElement = $joinPoint->getAST();
		}

		// ラッピングする関数を作成。
		$wrappingFunctionElement = new AOWP_PHPFunctionElement(AOWP_WeavingASTHelper::getRandomName('function'));
		$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('object', true));
		foreach ($objectPropertyElement->arguments as $argument) {
			$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('parameter', true));
		}
		// includeの為のautoload関数の設定。
		$wrappingFunctionElement->setElement(AOWP_WeavingASTHelper::createIncludeStatemenetElement($joinPoint->getFileFullPath()));
		// アスペクトのインスタンス化。
		$aspectInstantiationElement = AOWP_WeavingASTHelper::createAspectInstantiationAST($advice->getClassNameOfAspect());
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($aspectInstantiationElement));
		// ジョインポイントのインスタンス化。
		$joinPointInstantiationElement = AOWP_WeavingASTHelper::createJoinPointInstantiationAST($joinPoint);
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInstantiationElement));
		// 元のメソッド呼び出しの返り値を、ジョインポイントに設定。
		$joinPointReturnSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'setReturnValue');
		$originalMethodCallElement = new AOWP_PHPSimpleMethodCallElement($wrappingFunctionElement->getParameterName(0), $objectPropertyElement->getPropertyName());
		for ($i = 1; $i < $wrappingFunctionElement->getParameterCount(); $i++) {
			$originalMethodCallElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName($i)));
		}
		$joinPointReturnSetElement->addArgument(new AOWP_PHPArgumentElement(null, $originalMethodCallElement));
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointReturnSetElement));
		// 呼び出されるオブジェクトを、ジョインポイントに設定。
		$joinPointInvokedObjectSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'setInvokedObject');
		$joinPointInvokedObjectSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName(0)));
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInvokedObjectSetElement));
		// メソッド呼び出しの引き数を、ジョインポイントに設定。
		for ($i = 1; $i < $wrappingFunctionElement->getParameterCount(); $i++) {
			$joinPointArgumentSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'addArgument');
			$joinPointArgumentSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName($i)));
			$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointArgumentSetElement));
		}
		// アドバイスを実行するIf文。
		$adviceExecutionElement = AOWP_WeavingASTHelper::createAdviceExecutionAST($aspectInstantiationElement->getLeftVarialeName(),
			$advice->getIndex(), $joinPointInstantiationElement->getLeftVarialeName());
		$wrappingFunctionElement->setElement($adviceExecutionElement);
		// リターン文。
		$getReturnValueElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'getReturnValue');
		$returnStatementElement = new AOWP_PHPReturnStatementElement(null, $getReturnValueElement);
		$wrappingFunctionElement->setElement($returnStatementElement);

		// ラッピング関数の重複定義を防ぐためのIf文を追加。
		$ifFunctionDeclarationElement = AOWP_WeavingASTHelper::createIfForFunctionDeclaration($wrappingFunctionElement);

		// ラッピング関数を、元のコードに追加。
		AOWP_WeavingASTHelper::insertElement($objectPropertyElement, $ifFunctionDeclarationElement);
		
		// 対象のメソッド呼び出しを、作成したラッピング関数の呼び出しに変更する。
		AOWP_MethodCallJoinPointWeaveCommand::_changeIntoWrapingFunctionCall($objectPropertyElement, $wrappingFunctionElement->getFunctionName());
	}

	public function weaveAroundAdvice(AOWP_Advice &$advice, AOWP_JoinPoint &$joinPoint) {
		$objectPropertyElement = $joinPoint->getAST();
		
		// 連結したメソッド呼び出し (フィールド参照) の場合に、それらを単純な文に変換。
		if ($objectPropertyElement->getParent()->getPropertyCount() > 1) {
			$joinPoint->setAST(AOWP_WeavingASTHelper::apartMethodCallElement($objectPropertyElement));
			$objectPropertyElement = $joinPoint->getAST();
		}

		// proceed用の関数を作成。
		$proceedFunctionElement = new AOWP_PHPFunctionElement(AOWP_WeavingASTHelper::getRandomName('proceed'));
		$proceedFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('joinPoint', true));
		foreach ($objectPropertyElement->arguments as $argument) {
			$proceedFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('parameter', true));
		}
		// 元のメソッド呼び出し。
		$originalMethodCallElement = new AOWP_PHPSimpleMethodCallElement($proceedFunctionElement->getParameterName(0), 'getInvokedObject');
		$originalMethodCallProperty = new AOWP_PHPObjectPropertyElement();
		$originalMethodCallProperty->setTokenPropertyName($objectPropertyElement->getPropertyName());
		for ($i = 1; $i < $proceedFunctionElement->getParameterCount(); $i++) {
			$originalMethodCallProperty->addArgument(AOWP_PHPArgumentElement::createVariableArgument($proceedFunctionElement->getParameterName($i)));
		}
		$originalMethodCallElement->addObjectProperty($originalMethodCallProperty);
		// return文。
		$proceedFunctionElement->setElement(new AOWP_PHPReturnStatementElement(null, $originalMethodCallElement));
		
		// proceed関数を、元のコードに追加。
		AOWP_WeavingASTHelper::insertElement($objectPropertyElement, $proceedFunctionElement);
		
		// ラッピングする関数を作成。
		$wrappingFunctionElement = new AOWP_PHPFunctionElement(AOWP_WeavingASTHelper::getRandomName('function'));
		$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('object', true));
		foreach ($objectPropertyElement->arguments as $argument) {
			$wrappingFunctionElement->addParameter(AOWP_WeavingASTHelper::getRandomName('parameter', true));
		}
		// includeの為のautoload関数の設定。
		$wrappingFunctionElement->setElement(AOWP_WeavingASTHelper::createIncludeStatemenetElement($joinPoint->getFileFullPath()));
		// アスペクトのインスタンス化。
		$aspectInstantiationElement = AOWP_WeavingASTHelper::createAspectInstantiationAST($advice->getClassNameOfAspect());
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($aspectInstantiationElement));
		// ジョインポイントのインスタンス化。
		$joinPointInstantiationElement = AOWP_WeavingASTHelper::createJoinPointInstantiationAST($joinPoint);
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInstantiationElement));
		// proceed用の関数名を、ジョインポイントに設定。
		$joinPointProceedFunctionNameSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'setProceedFunctionName');
		$joinPointProceedFunctionNameSetElement->addArgument(AOWP_PHPArgumentElement::createStringArgument($proceedFunctionElement->getFunctionName()));
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointProceedFunctionNameSetElement));
		// 呼び出されるオブジェクトを、ジョインポイントに設定。
		$joinPointInvokedObjectSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'setInvokedObject');
		$joinPointInvokedObjectSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName(0)));
		$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointInvokedObjectSetElement));
		// メソッド呼び出しの引き数を、ジョインポイントに設定。
		for ($i = 1; $i < $wrappingFunctionElement->getParameterCount(); $i++) {
			$joinPointArgumentSetElement = new AOWP_PHPSimpleMethodCallElement($joinPointInstantiationElement->getLeftVarialeName(), 'addArgument');
			$joinPointArgumentSetElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName($i)));
			$wrappingFunctionElement->setElement(new AOWP_PHPStatementElement($joinPointArgumentSetElement));
		}
		// アドバイスを実行するIf文。
		$originalMethodCallElement = new AOWP_PHPSimpleMethodCallElement($wrappingFunctionElement->getParameterName(0), $objectPropertyElement->getPropertyName());
		for ($i = 1; $i < $wrappingFunctionElement->getParameterCount(); $i++) {
			$originalMethodCallElement->addArgument(AOWP_PHPArgumentElement::createVariableArgument($wrappingFunctionElement->getParameterName($i)));
		}
		$adviceExecutionElement = AOWP_WeavingASTHelper::createAdviceExecutionASTForAround($aspectInstantiationElement->getLeftVarialeName(),
			$advice->getIndex(), $joinPointInstantiationElement->getLeftVarialeName(), $originalMethodCallElement);
		$wrappingFunctionElement->setElement($adviceExecutionElement);

		// ラッピング関数の重複定義を防ぐためのIf文を追加。
		$ifFunctionDeclarationElement = AOWP_WeavingASTHelper::createIfForFunctionDeclaration($wrappingFunctionElement);

		// ラッピング関数を、元のコードに追加。
		AOWP_WeavingASTHelper::insertElement($objectPropertyElement, $ifFunctionDeclarationElement);
				
		// 対象のメソッド呼び出しを、作成したラッピング関数の呼び出しに変更する。
		AOWP_MethodCallJoinPointWeaveCommand::_changeIntoWrapingFunctionCall($objectPropertyElement, $wrappingFunctionElement->getFunctionName());
	}
	
	private static function _changeIntoWrapingFunctionCall(AOWP_PHPObjectPropertyElement &$targetObjectPropertyElement, $wrappingFunctionName) {
		// 対象のメソッド呼び出しを、作成したラッピング関数の呼び出しに変更する。
		$parentObjectOperatorElement = $targetObjectPropertyElement->getParent();
		$parentParentElement = $parentObjectOperatorElement->getParent();
		// ラッピング関数の呼び出しAST。
		$wrappingFunctionCallElement = new AOWP_PHPFunctionCallElement($wrappingFunctionName);
		$wrappingFunctionCallElement->addArgument(new AOWP_PHPArgumentElement(null, $parentObjectOperatorElement->getLeftExpr()));
		foreach ($targetObjectPropertyElement->arguments as $argument) {
			$wrappingFunctionCallElement->addArgument($argument);
		}
		// 元のメソッド呼び出しと、置き換え。
		$parentParentElement->{$parentObjectOperatorElement->getParentPropertyName()} = $wrappingFunctionCallElement;
	}

	/**
	 * 
	 * @param $joinPointVariableName ジョインポイントを表す変数名。
	 * @param $methodName 
	 * @param $argumentCount
	 * @return unknown_type
	 */
	private static function _createReturnStatementWithJoinPoint($joinPointVariableName, $methodName, $argumentCount) {
		$getInvokedObjectElement = new AOWP_PHPSimpleMethodCallElement($joinPointVariableName, 'getInvokedObject');
		
		$invokeMethodElement = new AOWP_PHPObjectPropertyElement();
		$invokeMethodElement->setTokenPropertyName($methodName);
		for ($i = 0; $i < $argumentCount; $i++) {
			$argumentGetMethodCallElement = new AOWP_PHPSimpleMethodCallElement($joinPointVariableName, 'getArgument');
			$argumentGetMethodCallElement->addArgument(AOWP_PHPArgumentElement::createStringArgument($i));
			$argumentElement = new AOWP_PHPArgumentElement(null, $argumentGetMethodCallElement);
			$invokeMethodElement->addArgument($argumentElement);
		}
		$getInvokedObjectElement->addObjectProperty($invokeMethodElement);
		
		return new AOWP_PHPReturnStatementElement(null, $getInvokedObjectElement);
	}
}

?>