/* *******************************************************************
 * Copyright (c) 2004 IBM Corporation.
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * ******************************************************************/
package org.aspectj.weaver.internal.tools;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;

import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.World;
import org.aspectj.weaver.ast.Literal;
import org.aspectj.weaver.ast.Test;
import org.aspectj.weaver.patterns.AbstractPatternNodeVisitor;
import org.aspectj.weaver.patterns.AnnotationPointcut;
import org.aspectj.weaver.patterns.ArgsAnnotationPointcut;
import org.aspectj.weaver.patterns.ArgsPointcut;
import org.aspectj.weaver.patterns.CflowPointcut;
import org.aspectj.weaver.patterns.ExposedState;
import org.aspectj.weaver.patterns.IfPointcut;
import org.aspectj.weaver.patterns.NotAnnotationTypePattern;
import org.aspectj.weaver.patterns.NotPointcut;
import org.aspectj.weaver.patterns.Pointcut;
import org.aspectj.weaver.patterns.ThisOrTargetAnnotationPointcut;
import org.aspectj.weaver.patterns.ThisOrTargetPointcut;
import org.aspectj.weaver.patterns.WithinAnnotationPointcut;
import org.aspectj.weaver.patterns.WithinCodeAnnotationPointcut;
import org.aspectj.weaver.reflect.ReflectionFastMatchInfo;
import org.aspectj.weaver.reflect.ReflectionShadow;
import org.aspectj.weaver.reflect.ReflectionWorld;
import org.aspectj.weaver.reflect.ShadowMatchImpl;
import org.aspectj.weaver.tools.DefaultMatchingContext;
import org.aspectj.weaver.tools.MatchingContext;
import org.aspectj.weaver.tools.PointcutExpression;
import org.aspectj.weaver.tools.PointcutParameter;
import org.aspectj.weaver.tools.ShadowMatch;

/**
 * Map from weaver.tools interface to internal Pointcut implementation...
 */
public class PointcutExpressionImpl implements PointcutExpression {

	private final static boolean MATCH_INFO = false;

	private World world;
	private Pointcut pointcut;
	private String expression;
	private PointcutParameter[] parameters;
	private MatchingContext matchContext = new DefaultMatchingContext();

	public PointcutExpressionImpl(Pointcut pointcut, String expression, PointcutParameter[] params, World inWorld) {
		this.pointcut = pointcut;
		this.expression = expression;
		this.world = inWorld;
		this.parameters = params;
		if (this.parameters == null) {
			this.parameters = new PointcutParameter[0];
		}
	}

	public Pointcut getUnderlyingPointcut() {
		return this.pointcut;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.tools.PointcutExpression#setMatchingContext(org.aspectj.weaver.tools.MatchingContext)
	 */
	public void setMatchingContext(MatchingContext aMatchContext) {
		this.matchContext = aMatchContext;
	}

	public boolean couldMatchJoinPointsInType(Class aClass) {
		ResolvedType matchType = world.resolve(aClass.getName());
		if (matchType.isMissing() && (world instanceof ReflectionWorld)) {
			// Class is a generated class that cannot be 'looked up' via getResource.
			// For example a proxy or lambda.
			// Use the class itself in this case
			matchType = ((ReflectionWorld)world).resolveUsingClass(aClass);
		}
		ReflectionFastMatchInfo info = new ReflectionFastMatchInfo(matchType, null, this.matchContext, world);
		boolean couldMatch = pointcut.fastMatch(info).maybeTrue();
		if (MATCH_INFO) {
			System.out.println("MATCHINFO: fast match for '" + this.expression + "' against '" + aClass.getName() + "': "
					+ couldMatch);
		}
		return couldMatch;
	}

	public boolean mayNeedDynamicTest() {
		HasPossibleDynamicContentVisitor visitor = new HasPossibleDynamicContentVisitor();
		pointcut.traverse(visitor, null);
		return visitor.hasDynamicContent();
	}

	private ExposedState getExposedState() {
		return new ExposedState(parameters.length);
	}

	public ShadowMatch matchesMethodExecution(Method aMethod) {
		ShadowMatch match = matchesExecution(aMethod);
		if (MATCH_INFO && match.maybeMatches()) {
			System.out.println("MATCHINFO: method execution match on '" + aMethod + "' for '" + this.expression + "': "
					+ (match.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return match;
	}

	public ShadowMatch matchesConstructorExecution(Constructor aConstructor) {
		ShadowMatch match = matchesExecution(aConstructor);
		if (MATCH_INFO && match.maybeMatches()) {
			System.out.println("MATCHINFO: constructor execution match on '" + aConstructor + "' for '" + this.expression + "': "
					+ (match.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return match;
	}

	private ShadowMatch matchesExecution(Member aMember) {
		Shadow s = ReflectionShadow.makeExecutionShadow(world, aMember, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aMember);
		sm.setWithinCode(null);
		sm.setWithinType(aMember.getDeclaringClass());
		return sm;
	}

	public ShadowMatch matchesStaticInitialization(Class aClass) {
		Shadow s = ReflectionShadow.makeStaticInitializationShadow(world, aClass, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(null);
		sm.setWithinCode(null);
		sm.setWithinType(aClass);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: static initialization match on '" + aClass.getName() + "' for '" + this.expression
					+ "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesAdviceExecution(Method aMethod) {
		Shadow s = ReflectionShadow.makeAdviceExecutionShadow(world, aMethod, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aMethod);
		sm.setWithinCode(null);
		sm.setWithinType(aMethod.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: advice execution match on '" + aMethod + "' for '" + this.expression + "': "
					+ (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesInitialization(Constructor aConstructor) {
		Shadow s = ReflectionShadow.makeInitializationShadow(world, aConstructor, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aConstructor);
		sm.setWithinCode(null);
		sm.setWithinType(aConstructor.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: initialization match on '" + aConstructor + "' for '" + this.expression + "': "
					+ (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesPreInitialization(Constructor aConstructor) {
		Shadow s = ReflectionShadow.makePreInitializationShadow(world, aConstructor, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aConstructor);
		sm.setWithinCode(null);
		sm.setWithinType(aConstructor.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: preinitialization match on '" + aConstructor + "' for '" + this.expression + "': "
					+ (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesMethodCall(Method aMethod, Member withinCode) {
		Shadow s = ReflectionShadow.makeCallShadow(world, aMethod, withinCode, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aMethod);
		sm.setWithinCode(withinCode);
		sm.setWithinType(withinCode.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: method call match on '" + aMethod + "' withinCode='" + withinCode + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesMethodCall(Method aMethod, Class callerType) {
		Shadow s = ReflectionShadow.makeCallShadow(world, aMethod, callerType, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aMethod);
		sm.setWithinCode(null);
		sm.setWithinType(callerType);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: method call match on '" + aMethod + "' callerType='" + callerType.getName() + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesConstructorCall(Constructor aConstructor, Class callerType) {
		Shadow s = ReflectionShadow.makeCallShadow(world, aConstructor, callerType, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aConstructor);
		sm.setWithinCode(null);
		sm.setWithinType(callerType);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: constructor call match on '" + aConstructor + "' callerType='" + callerType.getName()
					+ "' for '" + this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesConstructorCall(Constructor aConstructor, Member withinCode) {
		Shadow s = ReflectionShadow.makeCallShadow(world, aConstructor, withinCode, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aConstructor);
		sm.setWithinCode(withinCode);
		sm.setWithinType(withinCode.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: constructor call match on '" + aConstructor + "' withinCode='" + withinCode + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesHandler(Class exceptionType, Class handlingType) {
		Shadow s = ReflectionShadow.makeHandlerShadow(world, exceptionType, handlingType, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(null);
		sm.setWithinCode(null);
		sm.setWithinType(handlingType);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: handler match on '" + exceptionType.getName() + "' handlingType='" + handlingType
					+ "' for '" + this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesHandler(Class exceptionType, Member withinCode) {
		Shadow s = ReflectionShadow.makeHandlerShadow(world, exceptionType, withinCode, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(null);
		sm.setWithinCode(withinCode);
		sm.setWithinType(withinCode.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: handler match on '" + exceptionType.getName() + "' withinCode='" + withinCode
					+ "' for '" + this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesFieldGet(Field aField, Class withinType) {
		Shadow s = ReflectionShadow.makeFieldGetShadow(world, aField, withinType, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aField);
		sm.setWithinCode(null);
		sm.setWithinType(withinType);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: field get match on '" + aField + "' withinType='" + withinType.getName() + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesFieldGet(Field aField, Member withinCode) {
		Shadow s = ReflectionShadow.makeFieldGetShadow(world, aField, withinCode, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aField);
		sm.setWithinCode(withinCode);
		sm.setWithinType(withinCode.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: field get match on '" + aField + "' withinCode='" + withinCode + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesFieldSet(Field aField, Class withinType) {
		Shadow s = ReflectionShadow.makeFieldSetShadow(world, aField, withinType, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aField);
		sm.setWithinCode(null);
		sm.setWithinType(withinType);
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: field set match on '" + aField + "' withinType='" + withinType.getName() + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	public ShadowMatch matchesFieldSet(Field aField, Member withinCode) {
		Shadow s = ReflectionShadow.makeFieldSetShadow(world, aField, withinCode, this.matchContext);
		ShadowMatchImpl sm = getShadowMatch(s);
		sm.setSubject(aField);
		sm.setWithinCode(withinCode);
		sm.setWithinType(withinCode.getDeclaringClass());
		if (MATCH_INFO && sm.maybeMatches()) {
			System.out.println("MATCHINFO: field set match on '" + aField + "' withinCode='" + withinCode + "' for '"
					+ this.expression + "': " + (sm.alwaysMatches() ? "YES" : "MAYBE"));
		}
		return sm;
	}

	private ShadowMatchImpl getShadowMatch(Shadow forShadow) {
		org.aspectj.util.FuzzyBoolean match = pointcut.match(forShadow);
		Test residueTest = Literal.TRUE;
		ExposedState state = getExposedState();
		if (match.maybeTrue()) {
			residueTest = pointcut.findResidue(forShadow, state);
		}
		ShadowMatchImpl sm = new ShadowMatchImpl(match, residueTest, state, parameters);
		sm.setMatchingContext(this.matchContext);
		return sm;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.tools.PointcutExpression#getPointcutExpression()
	 */
	public String getPointcutExpression() {
		return expression;
	}

	private static class HasPossibleDynamicContentVisitor extends AbstractPatternNodeVisitor {
		private boolean hasDynamicContent = false;

		public boolean hasDynamicContent() {
			return hasDynamicContent;
		}

		@Override
		public Object visit(WithinAnnotationPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(WithinCodeAnnotationPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(AnnotationPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(ArgsAnnotationPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(ArgsPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(CflowPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(IfPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(NotAnnotationTypePattern node, Object data) {
			return node.getNegatedPattern().accept(this, data);
		}

		@Override
		public Object visit(NotPointcut node, Object data) {
			return node.getNegatedPointcut().accept(this, data);
		}

		@Override
		public Object visit(ThisOrTargetAnnotationPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

		@Override
		public Object visit(ThisOrTargetPointcut node, Object data) {
			hasDynamicContent = true;
			return null;
		}

	}

	public static class Handler implements Member {

		private Class<?> decClass;
		private Class<?> exType;

		public Handler(Class decClass, Class exType) {
			this.decClass = decClass;
			this.exType = exType;
		}

		public int getModifiers() {
			return 0;
		}

		public Class getDeclaringClass() {
			return decClass;
		}

		public String getName() {
			return null;
		}

		public Class getHandledExceptionType() {
			return exType;
		}

		public boolean isSynthetic() {
			return false;
		}
	}
}
