/* *******************************************************************
 * Copyright (c) 2005 Contributors.
 * 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
 *
 * Contributors:
 *   Adrian Colyer			Initial implementation
 * ******************************************************************/
package org.aspectj.internal.lang.reflect;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.lang.reflect.TypeVariable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;

import org.aspectj.internal.lang.annotation.ajcDeclareAnnotation;
import org.aspectj.internal.lang.annotation.ajcDeclareEoW;
import org.aspectj.internal.lang.annotation.ajcDeclareParents;
import org.aspectj.internal.lang.annotation.ajcDeclarePrecedence;
import org.aspectj.internal.lang.annotation.ajcDeclareSoft;
import org.aspectj.internal.lang.annotation.ajcITD;
import org.aspectj.internal.lang.annotation.ajcPrivileged;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.DeclareError;
import org.aspectj.lang.annotation.DeclareWarning;
import org.aspectj.lang.reflect.Advice;
import org.aspectj.lang.reflect.AdviceKind;
import org.aspectj.lang.reflect.AjType;
import org.aspectj.lang.reflect.AjTypeSystem;
import org.aspectj.lang.reflect.DeclareAnnotation;
import org.aspectj.lang.reflect.DeclareErrorOrWarning;
import org.aspectj.lang.reflect.DeclareParents;
import org.aspectj.lang.reflect.DeclarePrecedence;
import org.aspectj.lang.reflect.DeclareSoft;
import org.aspectj.lang.reflect.InterTypeConstructorDeclaration;
import org.aspectj.lang.reflect.InterTypeFieldDeclaration;
import org.aspectj.lang.reflect.InterTypeMethodDeclaration;
import org.aspectj.lang.reflect.NoSuchAdviceException;
import org.aspectj.lang.reflect.NoSuchPointcutException;
import org.aspectj.lang.reflect.PerClause;
import org.aspectj.lang.reflect.PerClauseKind;
import org.aspectj.lang.reflect.Pointcut;


/**
 * @author colyer
 *
 */
public class AjTypeImpl<T> implements AjType<T> {

	private static final String ajcMagic = "ajc$";

	private Class<T> clazz;
	private Pointcut[] declaredPointcuts = null;
	private Pointcut[] pointcuts = null;
	private Advice[] declaredAdvice = null;
	private Advice[] advice = null;
	private InterTypeMethodDeclaration[] declaredITDMethods = null;
	private InterTypeMethodDeclaration[] itdMethods = null;
	private InterTypeFieldDeclaration[] declaredITDFields = null;
	private InterTypeFieldDeclaration[] itdFields = null;
	private InterTypeConstructorDeclaration[] itdCons = null;
	private InterTypeConstructorDeclaration[] declaredITDCons = null;

	public AjTypeImpl(Class<T> fromClass) {
		this.clazz = fromClass;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getName()
	 */
	public String getName() {
		return clazz.getName();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getPackage()
	 */
	public Package getPackage() {
		return clazz.getPackage();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getInterfaces()
	 */
	public AjType<?>[] getInterfaces() {
		Class<?>[] baseInterfaces = clazz.getInterfaces();
		return toAjTypeArray(baseInterfaces);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getModifiers()
	 */
	public int getModifiers() {
		return clazz.getModifiers();
	}

	public Class<T> getJavaClass() {
		return clazz;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getSupertype()
	 */
	public AjType<? super T> getSupertype() {
		Class<? super T> superclass = clazz.getSuperclass();
		return superclass==null ? null : new AjTypeImpl<>(superclass);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getGenericSupertype()
	 */
	public Type getGenericSupertype() {
		return clazz.getGenericSuperclass();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getEnclosingMethod()
	 */
	public Method getEnclosingMethod() {
		return clazz.getEnclosingMethod();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getEnclosingConstructor()
	 */
	public Constructor getEnclosingConstructor() {
		return clazz.getEnclosingConstructor();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getEnclosingType()
	 */
	public AjType<?> getEnclosingType() {
		Class<?> enc = clazz.getEnclosingClass();
		return enc != null ? new AjTypeImpl<>(enc) : null;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaringType()
	 */
	public AjType<?> getDeclaringType() {
		Class<?> dec = clazz.getDeclaringClass();
		return dec != null ? new AjTypeImpl<>(dec) : null;
	}

	public PerClause getPerClause() {
		if (isAspect()) {
			Aspect aspectAnn = clazz.getAnnotation(Aspect.class);
			String perClause = aspectAnn.value();
			if (perClause.equals("")) {
				if (getSupertype().isAspect()) {
					return getSupertype().getPerClause();
				}
				return new PerClauseImpl(PerClauseKind.SINGLETON);
			} else if (perClause.startsWith("perthis(")) {
				return new PointcutBasedPerClauseImpl(PerClauseKind.PERTHIS,perClause.substring("perthis(".length(),perClause.length() - 1));
			} else if (perClause.startsWith("pertarget(")) {
				return new PointcutBasedPerClauseImpl(PerClauseKind.PERTARGET,perClause.substring("pertarget(".length(),perClause.length() - 1));
			} else if (perClause.startsWith("percflow(")) {
				return new PointcutBasedPerClauseImpl(PerClauseKind.PERCFLOW,perClause.substring("percflow(".length(),perClause.length() - 1));
			} else if (perClause.startsWith("percflowbelow(")) {
				return new PointcutBasedPerClauseImpl(PerClauseKind.PERCFLOWBELOW,perClause.substring("percflowbelow(".length(),perClause.length() - 1));
			} else if (perClause.startsWith("pertypewithin")) {
				return new TypePatternBasedPerClauseImpl(PerClauseKind.PERTYPEWITHIN,perClause.substring("pertypewithin(".length(),perClause.length() - 1));
			} else {
				throw new IllegalStateException("Per-clause not recognized: " + perClause);
			}
		} else {
			return null;
		}
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#isAnnotationPresent(java.lang.Class)
	 */
	public boolean isAnnotationPresent(Class<? extends Annotation> annotationType) {
		return clazz.isAnnotationPresent(annotationType);
	}

	public <A extends Annotation> A getAnnotation(Class<A> annotationType) {
		return clazz.getAnnotation(annotationType);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getAnnotations()
	 */
	public Annotation[] getAnnotations() {
		return clazz.getAnnotations();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredAnnotations()
	 */
	public Annotation[] getDeclaredAnnotations() {
		return clazz.getDeclaredAnnotations();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getAspects()
	 */
	public AjType<?>[] getAjTypes() {
		Class[] classes = clazz.getClasses();
		return toAjTypeArray(classes);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredAspects()
	 */
	public AjType<?>[] getDeclaredAjTypes() {
		Class[] classes = clazz.getDeclaredClasses();
		return toAjTypeArray(classes);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getConstructor(java.lang.Class...)
	 */
	public Constructor getConstructor(AjType<?>... parameterTypes) throws NoSuchMethodException {
		return clazz.getConstructor(toClassArray(parameterTypes));
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getConstructors()
	 */
	public Constructor[] getConstructors() {
		return clazz.getConstructors();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredConstructor(java.lang.Class...)
	 */
	public Constructor getDeclaredConstructor(AjType<?>... parameterTypes) throws NoSuchMethodException {
		return clazz.getDeclaredConstructor(toClassArray(parameterTypes));
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredConstructors()
	 */
	public Constructor[] getDeclaredConstructors() {
		return clazz.getDeclaredConstructors();
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredField(java.lang.String)
	 */
	public Field getDeclaredField(String name) throws NoSuchFieldException {
		Field f =  clazz.getDeclaredField(name);
		if (f.getName().startsWith(ajcMagic)) throw new NoSuchFieldException(name);
		return f;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredFields()
	 */
	public Field[] getDeclaredFields() {
		Field[] fields = clazz.getDeclaredFields();
		List<Field> filteredFields = new ArrayList<>();
		for (Field field : fields)
			if (!field.getName().startsWith(ajcMagic)
				&& !field.isAnnotationPresent(DeclareWarning.class)
				&& !field.isAnnotationPresent(DeclareError.class)) {
				filteredFields.add(field);
			}
		Field[] ret = new Field[filteredFields.size()];
		filteredFields.toArray(ret);
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getField(java.lang.String)
	 */
	public Field getField(String name)  throws NoSuchFieldException {
		Field f =  clazz.getField(name);
		if (f.getName().startsWith(ajcMagic)) throw new NoSuchFieldException(name);
		return f;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getFields()
	 */
	public Field[] getFields() {
		Field[] fields = clazz.getFields();
		List<Field> filteredFields = new ArrayList<>();
		for (Field field : fields)
			if (!field.getName().startsWith(ajcMagic)
					&& !field.isAnnotationPresent(DeclareWarning.class)
					&& !field.isAnnotationPresent(DeclareError.class)) {
					filteredFields.add(field);
				}
		Field[] ret = new Field[filteredFields.size()];
		filteredFields.toArray(ret);
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredMethod(java.lang.String, java.lang.Class...)
	 */
	public Method getDeclaredMethod(String name, AjType<?>... parameterTypes) throws NoSuchMethodException {
		Method m =  clazz.getDeclaredMethod(name,toClassArray(parameterTypes));
		if (!isReallyAMethod(m)) throw new NoSuchMethodException(name);
		return m;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getMethod(java.lang.String, java.lang.Class...)
	 */
	public Method getMethod(String name, AjType<?>... parameterTypes) throws NoSuchMethodException {
		Method m =  clazz.getMethod(name,toClassArray(parameterTypes));
		if (!isReallyAMethod(m)) throw new NoSuchMethodException(name);
		return m;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredMethods()
	 */
	public Method[] getDeclaredMethods() {
		Method[] methods = clazz.getDeclaredMethods();
		List<Method> filteredMethods = new ArrayList<>();
		for (Method method : methods) {
			if (isReallyAMethod(method)) filteredMethods.add(method);
		}
		Method[] ret = new Method[filteredMethods.size()];
		filteredMethods.toArray(ret);
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getMethods()
	 */
	public Method[] getMethods() {
		Method[] methods = clazz.getMethods();
		List<Method> filteredMethods = new ArrayList<>();
		for (Method method : methods) {
			if (isReallyAMethod(method)) filteredMethods.add(method);
		}
		Method[] ret = new Method[filteredMethods.size()];
		filteredMethods.toArray(ret);
		return ret;
	}

	private boolean isReallyAMethod(Method method) {
		if (method.getName().startsWith(ajcMagic)) return false;
		if (method.getAnnotations().length==0) return true;
		if (method.isAnnotationPresent(org.aspectj.lang.annotation.Pointcut.class)) return false;
		if (method.isAnnotationPresent(Before.class)) return false;
		if (method.isAnnotationPresent(After.class)) return false;
		if (method.isAnnotationPresent(AfterReturning.class)) return false;
		if (method.isAnnotationPresent(AfterThrowing.class)) return false;
		if (method.isAnnotationPresent(Around.class)) return false;
		return true;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredPointcut(java.lang.String)
	 */
	public Pointcut getDeclaredPointcut(String name) throws NoSuchPointcutException {
		Pointcut[] pcs = getDeclaredPointcuts();
		for (Pointcut pc : pcs)
			if (pc.getName().equals(name)) return pc;
		throw new NoSuchPointcutException(name);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getPointcut(java.lang.String)
	 */
	public Pointcut getPointcut(String name) throws NoSuchPointcutException {
		Pointcut[] pcs = getPointcuts();
		for (Pointcut pc : pcs)
			if (pc.getName().equals(name)) return pc;
		throw new NoSuchPointcutException(name);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredPointcuts()
	 */
	public Pointcut[] getDeclaredPointcuts() {
		if (declaredPointcuts != null) return declaredPointcuts;
		List<Pointcut> pointcuts = new ArrayList<>();
		Method[] methods = clazz.getDeclaredMethods();
		for (Method method : methods) {
			Pointcut pc = asPointcut(method);
			if (pc != null) pointcuts.add(pc);
		}
		Pointcut[] ret = new Pointcut[pointcuts.size()];
		pointcuts.toArray(ret);
		declaredPointcuts = ret;
		return ret;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getPointcuts()
	 */
	public Pointcut[] getPointcuts() {
		if (pointcuts != null) return pointcuts;
		List<Pointcut> pcuts = new ArrayList<>();
		Method[] methods = clazz.getMethods();
		for (Method method : methods) {
			Pointcut pc = asPointcut(method);
			if (pc != null) pcuts.add(pc);
		}
		Pointcut[] ret = new Pointcut[pcuts.size()];
		pcuts.toArray(ret);
		pointcuts  = ret;
		return ret;
	}

	private Pointcut asPointcut(Method method) {
		org.aspectj.lang.annotation.Pointcut pcAnn = method.getAnnotation(org.aspectj.lang.annotation.Pointcut.class);
		if (pcAnn != null) {
			String name = method.getName();
			if (name.startsWith(ajcMagic)) {
				// extract real name
				int nameStart = name.indexOf("$$");
				name = name.substring(nameStart +2,name.length());
				int nextDollar = name.indexOf("$");
				if (nextDollar != -1) name = name.substring(0,nextDollar);
			}
			return new PointcutImpl(name,pcAnn.value(),method,AjTypeSystem.getAjType(method.getDeclaringClass()),pcAnn.argNames());
		} else {
			return null;
		}
	}


	public Advice[] getDeclaredAdvice(AdviceKind... ofType) {
		Set<AdviceKind> types;
		if (ofType.length == 0) {
			types = EnumSet.allOf(AdviceKind.class);
		} else {
			types = EnumSet.noneOf(AdviceKind.class);
			types.addAll(Arrays.asList(ofType));
		}
		return getDeclaredAdvice(types);
	}

	public Advice[] getAdvice(AdviceKind... ofType) {
		Set<AdviceKind> types;
		if (ofType.length == 0) {
			types = EnumSet.allOf(AdviceKind.class);
		} else {
			types = EnumSet.noneOf(AdviceKind.class);
			types.addAll(Arrays.asList(ofType));
		}
		return getAdvice(types);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredAdvice(org.aspectj.lang.reflect.AdviceType)
	 */
	private Advice[] getDeclaredAdvice(Set<AdviceKind> ofAdviceTypes) {
		if (declaredAdvice == null) initDeclaredAdvice();
		List<Advice> adviceList = new ArrayList<>();
		for (Advice a : declaredAdvice) {
			if (ofAdviceTypes.contains(a.getKind())) adviceList.add(a);
		}
		Advice[] ret = new Advice[adviceList.size()];
		adviceList.toArray(ret);
		return ret;
	}

	private void initDeclaredAdvice() {
		Method[] methods = clazz.getDeclaredMethods();
		List<Advice> adviceList = new ArrayList<>();
		for (Method method : methods) {
			Advice advice = asAdvice(method);
			if (advice != null) adviceList.add(advice);
		}
		declaredAdvice = new Advice[adviceList.size()];
		adviceList.toArray(declaredAdvice);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredAdvice(org.aspectj.lang.reflect.AdviceType)
	 */
	private Advice[] getAdvice(Set<AdviceKind> ofAdviceTypes) {
		if (advice == null) initAdvice();
		List<Advice> adviceList = new ArrayList<>();
		for (Advice a : advice) {
			if (ofAdviceTypes.contains(a.getKind())) adviceList.add(a);
		}
		Advice[] ret = new Advice[adviceList.size()];
		adviceList.toArray(ret);
		return ret;
	}

	private void initAdvice() {
		Method[] methods = clazz.getMethods();
		List<Advice> adviceList = new ArrayList<>();
		for (Method method : methods) {
			Advice advice = asAdvice(method);
			if (advice != null) adviceList.add(advice);
		}
		advice = new Advice[adviceList.size()];
		adviceList.toArray(advice);
	}


	public Advice getAdvice(String name) throws NoSuchAdviceException {
		if (name.equals("")) throw new IllegalArgumentException("use getAdvice(AdviceType...) instead for un-named advice");
		if (advice == null) initAdvice();
		for (Advice a : advice) {
			if (a.getName().equals(name)) return a;
		}
		throw new NoSuchAdviceException(name);
	}

	public Advice getDeclaredAdvice(String name) throws NoSuchAdviceException {
		if (name.equals("")) throw new IllegalArgumentException("use getAdvice(AdviceType...) instead for un-named advice");
		if (declaredAdvice == null) initDeclaredAdvice();
		for (Advice a : declaredAdvice) {
			if (a.getName().equals(name)) return a;
		}
		throw new NoSuchAdviceException(name);
	}

	private Advice asAdvice(Method method) {
		if (method.getAnnotations().length == 0) return null;
		Before beforeAnn = method.getAnnotation(Before.class);
		if (beforeAnn != null) return new AdviceImpl(method,beforeAnn.value(),AdviceKind.BEFORE);
		After afterAnn = method.getAnnotation(After.class);
		if (afterAnn != null) return new AdviceImpl(method,afterAnn.value(),AdviceKind.AFTER);
		AfterReturning afterReturningAnn = method.getAnnotation(AfterReturning.class);
		if (afterReturningAnn != null) {
			String pcExpr = afterReturningAnn.pointcut();
			if (pcExpr.equals("")) pcExpr = afterReturningAnn.value();
			return new AdviceImpl(method,pcExpr,AdviceKind.AFTER_RETURNING,afterReturningAnn.returning());
		}
		AfterThrowing afterThrowingAnn = method.getAnnotation(AfterThrowing.class);
		if (afterThrowingAnn != null) {
			String pcExpr = afterThrowingAnn.pointcut();
			if (pcExpr == null) pcExpr = afterThrowingAnn.value();
			return new AdviceImpl(method,pcExpr,AdviceKind.AFTER_THROWING,afterThrowingAnn.throwing());
		}
		Around aroundAnn = method.getAnnotation(Around.class);
		if (aroundAnn != null) return new AdviceImpl(method,aroundAnn.value(),AdviceKind.AROUND);
		return null;
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredITDMethod(java.lang.String, java.lang.Class, java.lang.Class...)
	 */
	public InterTypeMethodDeclaration getDeclaredITDMethod(String name,
			AjType<?> target, AjType<?>... parameterTypes) throws NoSuchMethodException {
		InterTypeMethodDeclaration[] itdms = getDeclaredITDMethods();
		outer: for (InterTypeMethodDeclaration itdm : itdms) {
			try {
				if (!itdm.getName().equals(name)) continue;
				AjType<?> itdTarget = itdm.getTargetType();
				if (itdTarget.equals(target)) {
					AjType<?>[] ptypes = itdm.getParameterTypes();
					if (ptypes.length == parameterTypes.length) {
						for (int i = 0; i < ptypes.length; i++) {
							if (!ptypes[i].equals(parameterTypes[i]))
								continue outer;
						}
						return itdm;
					}
				}
			} catch (ClassNotFoundException cnf) {
				// just move on to the next one
			}
		}
		throw new NoSuchMethodException(name);
	}

	/* (non-Javadoc)
	 * @see org.aspectj.lang.reflect.AjType#getDeclaredITDMethods()
	 */
	public InterTypeMethodDeclaration[] getDeclaredITDMethods() {
		if (this.declaredITDMethods == null) {
			List<InterTypeMethodDeclaration> itdms = new ArrayList<>();
			Method[] baseMethods = clazz.getDeclaredMethods();
			for (Method m : baseMethods) {
				if (!m.getName().contains("ajc$interMethodDispatch1$")) continue;
				if (m.isAnnotationPresent(ajcITD.class)) {
					ajcITD ann = m.getAnnotation(ajcITD.class);
					InterTypeMethodDeclaration itdm =
						new InterTypeMethodDeclarationImpl(
								this,ann.targetType(),ann.modifiers(),
								ann.name(),m);
					itdms.add(itdm);
				}
			}
			addAnnotationStyleITDMethods(itdms,false);
			this.declaredITDMethods = new InterTypeMethodDeclaration[itdms.size()];
			itdms.toArray(this.declaredITDMethods);
		}
		return this.declaredITDMethods;
	}

	public InterTypeMethodDeclaration getITDMethod(String name, AjType<?> target,
			AjType<?>... parameterTypes)
	throws NoSuchMethodException {
		InterTypeMethodDeclaration[] itdms = getITDMethods();
		outer: for (InterTypeMethodDeclaration itdm : itdms) {
			try {
				if (!itdm.getName().equals(name)) continue;
				AjType<?> itdTarget = itdm.getTargetType();
				if (itdTarget.equals(target)) {
					AjType<?>[] ptypes = itdm.getParameterTypes();
					if (ptypes.length == parameterTypes.length) {
						for (int i = 0; i < ptypes.length; i++) {
							if (!ptypes[i].equals(parameterTypes[i]))
								continue outer;
						}
						return itdm;
					}
				}
			} catch (ClassNotFoundException cnf) {
				// just move on to the next one
			}
		}
		throw new NoSuchMethodException(name);
	}

	public InterTypeMethodDeclaration[] getITDMethods() {
		if (this.itdMethods == null) {
			List<InterTypeMethodDeclaration> itdms = new ArrayList<>();
			Method[] baseMethods = clazz.getDeclaredMethods();
			for (Method m : baseMethods) {
				if (!m.getName().contains("ajc$interMethod$")) continue;
				if (m.isAnnotationPresent(ajcITD.class)) {
					ajcITD ann = m.getAnnotation(ajcITD.class);
					if (!Modifier.isPublic(ann.modifiers())) continue;
					InterTypeMethodDeclaration itdm =
						new InterTypeMethodDeclarationImpl(
								this,ann.targetType(),ann.modifiers(),
								ann.name(),m);
					itdms.add(itdm);
				}
			}
			addAnnotationStyleITDMethods(itdms,true);
			this.itdMethods = new InterTypeMethodDeclaration[itdms.size()];
			itdms.toArray(this.itdMethods);
		}
		return this.itdMethods;
	}

	private void addAnnotationStyleITDMethods(List<InterTypeMethodDeclaration> toList, boolean publicOnly) {
		if (isAspect()) {
            for (Field f : clazz.getDeclaredFields()) {
                if (!f.getType().isInterface()) continue;
                if (f.isAnnotationPresent(org.aspectj.lang.annotation.DeclareParents.class)) {
                	Class<org.aspectj.lang.annotation.DeclareParents> decPAnnClass = org.aspectj.lang.annotation.DeclareParents.class;
                	org.aspectj.lang.annotation.DeclareParents decPAnn = f.getAnnotation(decPAnnClass);
               	    if (decPAnn.defaultImpl() == decPAnnClass) continue; // doesn't contribute members...
                    for (Method itdM : f.getType().getDeclaredMethods()) {
                        if (!Modifier.isPublic(itdM.getModifiers()) && publicOnly) continue;
                        InterTypeMethodDeclaration itdm = new InterTypeMethodDeclarationImpl(
                                    this, AjTypeSystem.getAjType(f.getType()), itdM,
                                    Modifier.PUBLIC
                        );
                        toList.add(itdm);
                    }
                }
            }
		}
	}

	private void addAnnotationStyleITDFields(List<InterTypeFieldDeclaration> toList, boolean publicOnly) {
        //AV: I think it is meaningless
        //@AJ decp is interface driven ie no field
		return;
	}

	public InterTypeConstructorDeclaration getDeclaredITDConstructor(
			AjType<?> target, AjType<?>... parameterTypes) throws NoSuchMethodException {
		InterTypeConstructorDeclaration[] itdcs = getDeclaredITDConstructors();
		outer: for (InterTypeConstructorDeclaration itdc : itdcs) {
			try {
				AjType<?> itdTarget = itdc.getTargetType();
				if (itdTarget.equals(target)) {
					AjType<?>[] ptypes = itdc.getParameterTypes();
					if (ptypes.length == parameterTypes.length) {
						for (int i = 0; i < ptypes.length; i++) {
							if (!ptypes[i].equals(parameterTypes[i]))
								continue outer;
						}
						return itdc;
					}
				}
			} catch (ClassNotFoundException cnf) {
				// just move on to the next one
			}
		}
		throw new NoSuchMethodException();
	}

	public InterTypeConstructorDeclaration[] getDeclaredITDConstructors() {
		if (this.declaredITDCons == null) {
			List<InterTypeConstructorDeclaration> itdcs = new ArrayList<>();
			Method[] baseMethods = clazz.getDeclaredMethods();
			for (Method m : baseMethods) {
				if (!m.getName().contains("ajc$postInterConstructor")) continue;
				if (m.isAnnotationPresent(ajcITD.class)) {
					ajcITD ann = m.getAnnotation(ajcITD.class);
					InterTypeConstructorDeclaration itdc =
						new InterTypeConstructorDeclarationImpl(this,ann.targetType(),ann.modifiers(),m);
					itdcs.add(itdc);
				}
			}
			this.declaredITDCons = new InterTypeConstructorDeclaration[itdcs.size()];
			itdcs.toArray(this.declaredITDCons);
		}
		return this.declaredITDCons;
	}

	public InterTypeConstructorDeclaration getITDConstructor(AjType<?> target,
			AjType<?>... parameterTypes) throws NoSuchMethodException {
		InterTypeConstructorDeclaration[] itdcs = getITDConstructors();
		outer: for (InterTypeConstructorDeclaration itdc : itdcs) {
			try {
				AjType<?> itdTarget = itdc.getTargetType();
				if (itdTarget.equals(target)) {
					AjType<?>[] ptypes = itdc.getParameterTypes();
					if (ptypes.length == parameterTypes.length) {
						for (int i = 0; i < ptypes.length; i++) {
							if (!ptypes[i].equals(parameterTypes[i]))
								continue outer;
						}
						return itdc;
					}
				}
			} catch (ClassNotFoundException cnf) {
				// just move on to the next one
			}
		}
		throw new NoSuchMethodException();
	}

	public InterTypeConstructorDeclaration[] getITDConstructors() {
		if (this.itdCons == null) {
			List<InterTypeConstructorDeclaration> itdcs = new ArrayList<>();
			Method[] baseMethods = clazz.getMethods();
			for (Method m : baseMethods) {
				if (!m.getName().contains("ajc$postInterConstructor")) continue;
				if (m.isAnnotationPresent(ajcITD.class)) {
					ajcITD ann = m.getAnnotation(ajcITD.class);
					if (!Modifier.isPublic(ann.modifiers())) continue;
					InterTypeConstructorDeclaration itdc =
						new InterTypeConstructorDeclarationImpl(this,ann.targetType(),ann.modifiers(),m);
					itdcs.add(itdc);
				}
			}
			this.itdCons = new InterTypeConstructorDeclaration[itdcs.size()];
			itdcs.toArray(this.itdCons);
		}
		return this.itdCons;	}

	public InterTypeFieldDeclaration getDeclaredITDField(String name,
			AjType<?> target) throws NoSuchFieldException {
		InterTypeFieldDeclaration[] itdfs = getDeclaredITDFields();
		for (InterTypeFieldDeclaration itdf : itdfs) {
			if (itdf.getName().equals(name)) {
				try {
					AjType<?> itdTarget = itdf.getTargetType();
					if (itdTarget.equals(target)) return itdf;
				} catch (ClassNotFoundException cnfEx) {
					// move on to next field
				}
			}
		}
		throw new NoSuchFieldException(name);
	}

	public InterTypeFieldDeclaration[] getDeclaredITDFields() {
		List<InterTypeFieldDeclaration> itdfs = new ArrayList<>();
		if (this.declaredITDFields == null) {
			Method[] baseMethods = clazz.getDeclaredMethods();
			for(Method m : baseMethods) {
				if (m.isAnnotationPresent(ajcITD.class)) {
					if (!m.getName().contains("ajc$interFieldInit")) continue;
					ajcITD ann = m.getAnnotation(ajcITD.class);
					String interFieldInitMethodName = m.getName();
					String interFieldGetDispatchMethodName =
						interFieldInitMethodName.replace("FieldInit","FieldGetDispatch");
					try {
						Method dispatch = clazz.getDeclaredMethod(interFieldGetDispatchMethodName, m.getParameterTypes());
						InterTypeFieldDeclaration itdf = new InterTypeFieldDeclarationImpl(
								this,ann.targetType(),ann.modifiers(),ann.name(),
								AjTypeSystem.getAjType(dispatch.getReturnType()),
								dispatch.getGenericReturnType());
						itdfs.add(itdf);
					} catch (NoSuchMethodException nsmEx) {
						throw new IllegalStateException("Can't find field get dispatch method for " + m.getName());
					}
				}
			}
			addAnnotationStyleITDFields(itdfs, false);
			this.declaredITDFields = new InterTypeFieldDeclaration[itdfs.size()];
			itdfs.toArray(this.declaredITDFields);
		}
		return this.declaredITDFields;
	}

	public InterTypeFieldDeclaration getITDField(String name, AjType<?> target)
	throws NoSuchFieldException {
		InterTypeFieldDeclaration[] itdfs = getITDFields();
		for (InterTypeFieldDeclaration itdf : itdfs) {
			if (itdf.getName().equals(name)) {
				try {
					AjType<?> itdTarget = itdf.getTargetType();
					if (itdTarget.equals(target)) return itdf;
				} catch (ClassNotFoundException cnfEx) {
					// move on to next field
				}
			}
		}
		throw new NoSuchFieldException(name);
	}

	public InterTypeFieldDeclaration[] getITDFields() {
		List<InterTypeFieldDeclaration> itdfs = new ArrayList<>();
		if (this.itdFields == null) {
			Method[] baseMethods = clazz.getMethods();
			for(Method m : baseMethods) {
				if (m.isAnnotationPresent(ajcITD.class)) {
					ajcITD ann = m.getAnnotation(ajcITD.class);
					if (!m.getName().contains("ajc$interFieldInit")) continue;
					if (!Modifier.isPublic(ann.modifiers())) continue;
					String interFieldInitMethodName = m.getName();
					String interFieldGetDispatchMethodName =
						interFieldInitMethodName.replace("FieldInit","FieldGetDispatch");
					try {
						Method dispatch = m.getDeclaringClass().getDeclaredMethod(interFieldGetDispatchMethodName, m.getParameterTypes());
						InterTypeFieldDeclaration itdf = new InterTypeFieldDeclarationImpl(
								this,ann.targetType(),ann.modifiers(),ann.name(),
								AjTypeSystem.getAjType(dispatch.getReturnType()),
								dispatch.getGenericReturnType());
						itdfs.add(itdf);
					} catch (NoSuchMethodException nsmEx) {
						throw new IllegalStateException("Can't find field get dispatch method for " + m.getName());
					}
				}
			}
			addAnnotationStyleITDFields(itdfs, true);
			this.itdFields = new InterTypeFieldDeclaration[itdfs.size()];
			itdfs.toArray(this.itdFields);
		}
		return this.itdFields;
	}

	public DeclareErrorOrWarning[] getDeclareErrorOrWarnings() {
		List<DeclareErrorOrWarning> deows = new ArrayList<>();
		for (Field field : clazz.getDeclaredFields()) {
			try {
				if (field.isAnnotationPresent(DeclareWarning.class)) {
					 DeclareWarning dw = field.getAnnotation(DeclareWarning.class);
					 if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) {
						 String message = (String) field.get(null);
						 DeclareErrorOrWarningImpl deow = new DeclareErrorOrWarningImpl(dw.value(),message,false,this);
						 deows.add(deow);
					 }
				} else if (field.isAnnotationPresent(DeclareError.class)) {
					 DeclareError de = field.getAnnotation(DeclareError.class);
					 if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers())) {
						 String message = (String) field.get(null);
						 DeclareErrorOrWarningImpl deow = new DeclareErrorOrWarningImpl(de.value(),message,true,this);
						 deows.add(deow);
					 }
				}
			} catch (IllegalArgumentException e) {
				// just move on to the next field
			} catch (IllegalAccessException e) {
				// just move on to the next field
			}
		}
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(ajcDeclareEoW.class)) {
				ajcDeclareEoW deowAnn = method.getAnnotation(ajcDeclareEoW.class);
				DeclareErrorOrWarning deow = new DeclareErrorOrWarningImpl(deowAnn.pointcut(),deowAnn.message(),deowAnn.isError(),this);
				deows.add(deow);
			}
		}
		DeclareErrorOrWarning[] ret = new DeclareErrorOrWarning[deows.size()];
		deows.toArray(ret);
		return ret;
	}

	public DeclareParents[] getDeclareParents() {
		List<DeclareParents> decps = new ArrayList<>();
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(ajcDeclareParents.class)) {
				ajcDeclareParents decPAnn = method.getAnnotation(ajcDeclareParents.class);
				DeclareParentsImpl decp = new DeclareParentsImpl(
						decPAnn.targetTypePattern(),
						decPAnn.parentTypes(),
						decPAnn.isExtends(),
						this
						);
				decps.add(decp);
			}
		}
		addAnnotationStyleDeclareParents(decps);
		if (getSupertype().isAspect()) {
			decps.addAll(Arrays.asList(getSupertype().getDeclareParents()));
		}
		DeclareParents[] ret = new DeclareParents[decps.size()];
		decps.toArray(ret);
		return ret;
	}

	private void addAnnotationStyleDeclareParents(List<DeclareParents> toList) {
        for (Field f : clazz.getDeclaredFields()) {
            if (f.isAnnotationPresent(org.aspectj.lang.annotation.DeclareParents.class)) {
                if (!f.getType().isInterface()) continue;
                org.aspectj.lang.annotation.DeclareParents ann = f.getAnnotation(org.aspectj.lang.annotation.DeclareParents.class);
                String parentType = f.getType().getName();
                DeclareParentsImpl decp = new DeclareParentsImpl(
                        ann.value(),
                        parentType,
                        false,
                        this
                );
                toList.add(decp);
            }
        }
	}

	public DeclareSoft[] getDeclareSofts() {
		List<DeclareSoft> decs = new ArrayList<>();
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(ajcDeclareSoft.class)) {
				ajcDeclareSoft decSAnn = method.getAnnotation(ajcDeclareSoft.class);
				DeclareSoftImpl ds = new DeclareSoftImpl(
						this,
						decSAnn.pointcut(),
						decSAnn.exceptionType()
						);
				decs.add(ds);
			}
		}
		if (getSupertype().isAspect()) {
			decs.addAll(Arrays.asList(getSupertype().getDeclareSofts()));
		}
		DeclareSoft[] ret = new DeclareSoft[decs.size()];
		decs.toArray(ret);
		return ret;
	}

	public DeclareAnnotation[] getDeclareAnnotations() {
		List<DeclareAnnotation> decAs = new ArrayList<>();
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(ajcDeclareAnnotation.class)) {
				ajcDeclareAnnotation decAnn = method.getAnnotation(ajcDeclareAnnotation.class);
				// the target annotation is on this method...
				Annotation targetAnnotation = null;
				Annotation[] anns = method.getAnnotations();
				for (Annotation ann: anns) {
					if (ann.annotationType() != ajcDeclareAnnotation.class) {
						// this must be the one...
						targetAnnotation = ann;
						break;
					}
				}
				DeclareAnnotationImpl da = new DeclareAnnotationImpl(
						this,
						decAnn.kind(),
						decAnn.pattern(),
						targetAnnotation,
						decAnn.annotation()
						);
				decAs.add(da);
			}
		}
		if (getSupertype().isAspect()) {
			decAs.addAll(Arrays.asList(getSupertype().getDeclareAnnotations()));
		}
		DeclareAnnotation[] ret = new DeclareAnnotation[decAs.size()];
		decAs.toArray(ret);
		return ret;
	}

	public DeclarePrecedence[] getDeclarePrecedence() {
		List<DeclarePrecedence> decps = new ArrayList<>();

		// @AspectJ Style
		if (clazz.isAnnotationPresent(org.aspectj.lang.annotation.DeclarePrecedence.class)) {
			org.aspectj.lang.annotation.DeclarePrecedence ann =
				clazz.getAnnotation(org.aspectj.lang.annotation.DeclarePrecedence.class);
			DeclarePrecedenceImpl decp = new DeclarePrecedenceImpl(
					ann.value(),
					this
					);
			decps.add(decp);
		}

		// annotated code-style
		for (Method method : clazz.getDeclaredMethods()) {
			if (method.isAnnotationPresent(ajcDeclarePrecedence.class)) {
				ajcDeclarePrecedence decPAnn = method.getAnnotation(ajcDeclarePrecedence.class);
				DeclarePrecedenceImpl decp = new DeclarePrecedenceImpl(
						decPAnn.value(),
						this
						);
				decps.add(decp);
			}
		}
		if (getSupertype().isAspect()) {
			decps.addAll(Arrays.asList(getSupertype().getDeclarePrecedence()));
		}
		DeclarePrecedence[] ret = new DeclarePrecedence[decps.size()];
		decps.toArray(ret);
		return ret;
	}

	public T[] getEnumConstants() {
		return clazz.getEnumConstants();
	}

	public TypeVariable<Class<T>>[] getTypeParameters() {
		return clazz.getTypeParameters();
	}

	public boolean isEnum() {
		return clazz.isEnum();
	}

	public boolean isInstance(Object o) {
		return clazz.isInstance(o);
	}

	public boolean isInterface() {
		return clazz.isInterface();
	}

	public boolean isLocalClass() {
		return clazz.isLocalClass() && !isAspect();
	}

	public boolean isMemberClass() {
		return clazz.isMemberClass() && !isAspect();
	}

	public boolean isArray() {
		return clazz.isArray();
	}

	public boolean isPrimitive() {
		return clazz.isPrimitive();
	}

	public boolean isAspect() {
		return clazz.getAnnotation(Aspect.class) != null;
	}

	public boolean isMemberAspect() {
		return clazz.isMemberClass() && isAspect();
	}

	public boolean isPrivileged() {
		return isAspect() && clazz.isAnnotationPresent(ajcPrivileged.class);
	}

	@Override
	public boolean equals(Object obj) {
		if (!(obj instanceof AjTypeImpl)) return false;
		AjTypeImpl<?> other = (AjTypeImpl<?>) obj;
		return other.clazz.equals(clazz);
	}

	@Override
	public int hashCode() {
		return clazz.hashCode();
	}

	private AjType<?>[] toAjTypeArray(Class<?>[] classes) {
		AjType<?>[] ajtypes = new AjType<?>[classes.length];
		for (int i = 0; i < ajtypes.length; i++) {
			ajtypes[i] = AjTypeSystem.getAjType(classes[i]);
		}
		return ajtypes;
	}

	private Class<?>[] toClassArray(AjType<?>[] ajTypes) {
		Class<?>[] classes = new Class<?>[ajTypes.length];
		for (int i = 0; i < classes.length; i++) {
			classes[i] = ajTypes[i].getJavaClass();
		}
		return classes;
	}

	public String toString() { return getName(); }

}
