/* *******************************************************************
 * Copyright (c) 2002-2010 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
 * ******************************************************************/
package org.aspectj.weaver;

import java.io.IOException;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.aspectj.bridge.ISourceLocation;

/**
 * Represent a resolved member. Components of it are expected to exist. This member will correspond to a real member *unless* it is
 * being used to represent the effect of an ITD.
 *
 * @author PARC
 * @author Andy Clement
 */
public class ResolvedMemberImpl extends MemberImpl implements IHasPosition, ResolvedMember {

	private String[] parameterNames = null;
	private boolean isResolved = false;
	protected UnresolvedType[] checkedExceptions = UnresolvedType.NONE;

	/**
	 * if this member is a parameterized version of a member in a generic type, then this field holds a reference to the member we
	 * parameterize.
	 */
	protected ResolvedMember backingGenericMember = null;

	protected AnnotationAJ[] annotations = null;
	protected ResolvedType[] annotationTypes = null;
	protected AnnotationAJ[][] parameterAnnotations = null;
	protected ResolvedType[][] parameterAnnotationTypes = null;

	// Some members are 'created' to represent other things (for example ITDs).
	// These members have their annotations stored elsewhere, and this flag indicates
	// that is the case. It is up to the caller to work out where that is!
	// Once determined the caller may choose to stash the annotations in this
	// member...
	private boolean isAnnotatedElsewhere = false;
	private boolean isAjSynthetic = false;

	// generic methods have type variables
	protected TypeVariable[] typeVariables;

	// these three fields hold the source location of this member
	protected int start, end;
	protected ISourceContext sourceContext = null;

	// XXX deprecate this in favor of the constructor below
	public ResolvedMemberImpl(MemberKind kind, UnresolvedType declaringType, int modifiers, UnresolvedType returnType, String name,
			UnresolvedType[] parameterTypes) {
		super(kind, declaringType, modifiers, returnType, name, parameterTypes);
	}

	public ResolvedMemberImpl(MemberKind kind, UnresolvedType declaringType, int modifiers, UnresolvedType returnType, String name,
			UnresolvedType[] parameterTypes, UnresolvedType[] checkedExceptions) {
		super(kind, declaringType, modifiers, returnType, name, parameterTypes);
		this.checkedExceptions = checkedExceptions;
	}

	public ResolvedMemberImpl(MemberKind kind, UnresolvedType declaringType, int modifiers, UnresolvedType returnType, String name,
			UnresolvedType[] parameterTypes, UnresolvedType[] checkedExceptions, ResolvedMember backingGenericMember) {
		this(kind, declaringType, modifiers, returnType, name, parameterTypes, checkedExceptions);
		this.backingGenericMember = backingGenericMember;
		this.isAjSynthetic = backingGenericMember.isAjSynthetic();
	}

	public ResolvedMemberImpl(MemberKind kind, UnresolvedType declaringType, int modifiers, String name, String signature) {
		super(kind, declaringType, modifiers, name, signature);
	}

	/**
	 * Compute the full set of signatures for a member. This walks up the hierarchy giving the ResolvedMember in each defining type
	 * in the hierarchy. A shadowMember can be created with a target type (declaring type) that does not actually define the member.
	 * This is ok as long as the member is inherited in the declaring type. Each declaring type in the line to the actual declaring
	 * type is added as an additional signature. For example:
	 *
	 * class A { void foo(); } class B extends A {}
	 *
	 * shadowMember : void B.foo()
	 *
	 * gives { void B.foo(), void A.foo() }
	 *
	 * @param joinPointSignature
	 * @param inAWorld
	 */
	public static JoinPointSignature[] getJoinPointSignatures(Member joinPointSignature, World inAWorld) {

		// Walk up hierarchy creating one member for each type up to and
		// including the
		// first defining type
		ResolvedType originalDeclaringType = joinPointSignature.getDeclaringType().resolve(inAWorld);
		ResolvedMemberImpl firstDefiningMember = (ResolvedMemberImpl) joinPointSignature.resolve(inAWorld);
		if (firstDefiningMember == null) {
			return JoinPointSignature.EMPTY_ARRAY;
		}
		// declaringType can be unresolved if we matched a synthetic member
		// generated by Aj...
		// should be fixed elsewhere but add this resolve call on the end for
		// now so that we can
		// focus on one problem at a time...
		ResolvedType firstDefiningType = firstDefiningMember.getDeclaringType().resolve(inAWorld);
		if (firstDefiningType != originalDeclaringType) {
			if (joinPointSignature.getKind() == Member.CONSTRUCTOR) {
				return JoinPointSignature.EMPTY_ARRAY;
			}
			// else if (shadowMember.isStatic()) {
			// return new ResolvedMember[] {firstDefiningMember};
			// }
		}

		List<ResolvedType> declaringTypes = new ArrayList<>();
		accumulateTypesInBetween(originalDeclaringType, firstDefiningType, declaringTypes);
		Set<ResolvedMember> memberSignatures = new LinkedHashSet<>();
		for (ResolvedType declaringType : declaringTypes) {
			memberSignatures.add(new JoinPointSignature(firstDefiningMember, declaringType));
		}

		if (shouldWalkUpHierarchyFor(firstDefiningMember)) {
			// now walk up the hierarchy from the firstDefiningMember and
			// include the signature for
			// every type between the firstDefiningMember and the root defining
			// member.
			Iterator<ResolvedType> superTypeIterator = firstDefiningType.getDirectSupertypes();
			List<ResolvedType> typesAlreadyVisited = new ArrayList<>();
			accumulateMembersMatching(firstDefiningMember, superTypeIterator, typesAlreadyVisited, memberSignatures, false);
		}

		JoinPointSignature[] ret = new JoinPointSignature[memberSignatures.size()];
		memberSignatures.toArray(ret);
		return ret;
	}

	private static boolean shouldWalkUpHierarchyFor(Member aMember) {
		if (aMember.getKind() == Member.CONSTRUCTOR) {
			return false;
		}
		if (aMember.getKind() == Member.FIELD) {
			return false;
		}
		if (Modifier.isStatic(aMember.getModifiers())) {
			return false;
		}
		return true;
	}

	/**
	 * Build a list containing every type between subtype and supertype, inclusively.
	 */
	private static void accumulateTypesInBetween(ResolvedType subType, ResolvedType superType, List<ResolvedType> types) {
		types.add(subType);
		if (subType == superType) {
			return;
		} else {
			for (Iterator<ResolvedType> iter = subType.getDirectSupertypes(); iter.hasNext();) {
				ResolvedType parent = iter.next();
				if (superType.isAssignableFrom(parent)) {
					accumulateTypesInBetween(parent, superType, types);
				}
			}
		}
	}

	/**
	 * We have a resolved member, possibly with type parameter references as parameters or return type. We need to find all its
	 * ancestor members. When doing this, a type parameter matches regardless of bounds (bounds can be narrowed down the hierarchy).
	 */
	private static void accumulateMembersMatching(ResolvedMemberImpl memberToMatch, Iterator<ResolvedType> typesToLookIn,
			List<ResolvedType> typesAlreadyVisited, Set<ResolvedMember> foundMembers, boolean ignoreGenerics) {
		while (typesToLookIn.hasNext()) {
			ResolvedType toLookIn = typesToLookIn.next();
			if (!typesAlreadyVisited.contains(toLookIn)) {
				typesAlreadyVisited.add(toLookIn);
				ResolvedMemberImpl foundMember = (ResolvedMemberImpl) toLookIn.lookupResolvedMember(memberToMatch, true,
						ignoreGenerics);
				if (foundMember != null && isVisibleTo(memberToMatch, foundMember)) {
					List<ResolvedType> declaringTypes = new ArrayList<>();
					// declaring type can be unresolved if the member can from
					// an ITD...
					ResolvedType resolvedDeclaringType = foundMember.getDeclaringType().resolve(toLookIn.getWorld());
					accumulateTypesInBetween(toLookIn, resolvedDeclaringType, declaringTypes);
					for (ResolvedType declaringType : declaringTypes) {
						// typesAlreadyVisited.add(declaringType);
						foundMembers.add(new JoinPointSignature(foundMember, declaringType));
					}
					if (!ignoreGenerics && toLookIn.isParameterizedType() && (foundMember.backingGenericMember != null)) {
						foundMembers.add(new JoinPointSignature(foundMember.backingGenericMember, foundMember.declaringType
								.resolve(toLookIn.getWorld())));
					}
					accumulateMembersMatching(foundMember, toLookIn.getDirectSupertypes(), typesAlreadyVisited, foundMembers,
							ignoreGenerics);
					// if this was a parameterized type, look in the generic
					// type that backs it too
				}
			}
		}
	}

	/**
	 * Returns true if the parent member is visible to the child member In the same declaring type this is always true, otherwise if
	 * parent is private it is false.
	 *
	 * @param childMember
	 * @param parentMember
	 * @return
	 */
	private static boolean isVisibleTo(ResolvedMember childMember, ResolvedMember parentMember) {
		if (childMember.getDeclaringType().equals(parentMember.getDeclaringType())) {
			return true;
		}
		if (Modifier.isPrivate(parentMember.getModifiers())) {
			return false;
		} else {
			return true;
		}
	}

	// ----

	@Override
	public final int getModifiers(World world) {
		return modifiers;
	}

	@Override
	public final int getModifiers() {
		return modifiers;
	}

	// ----

	@Override
	public final UnresolvedType[] getExceptions(World world) {
		return getExceptions();
	}

	public UnresolvedType[] getExceptions() {
		return checkedExceptions;
	}

	public ShadowMunger getAssociatedShadowMunger() {
		return null;
	}

	// ??? true or false?
	public boolean isAjSynthetic() {
		return isAjSynthetic;
	}

	protected void setAjSynthetic(boolean b) {
		isAjSynthetic = b;
	}

	public boolean hasAnnotations() {
		return (annotationTypes != null);
	}

	/**
	 * Check if this member has an annotation of the specified type. If the member has a backing generic member then this member
	 * represents a parameterization of a member in a generic type and the annotations available on the backing generic member
	 * should be used.
	 *
	 * @param ofType the type of the annotation being searched for
	 * @return true if the annotation is found on this member or its backing generic member
	 */
	public boolean hasAnnotation(UnresolvedType ofType) {
		// The ctors don't allow annotations to be specified ... yet - but
		// that doesn't mean it is an error to call this method.
		// Normally the weaver will be working with subtypes of
		// this type - BcelField/BcelMethod
		if (backingGenericMember != null) {
			if (annotationTypes != null) {
				throw new BCException("Unexpectedly found a backing generic member and a local set of annotations");
			}
			return backingGenericMember.hasAnnotation(ofType);
		}
		if (annotationTypes != null) {
			for (ResolvedType annotationType : annotationTypes) {
				if (annotationType.equals(ofType)) {
					return true;
				}
			}
		}
		return false;
	}

	public ResolvedType[] getAnnotationTypes() {
		// The ctors don't allow annotations to be specified ... yet - but
		// that doesn't mean it is an error to call this method.
		// Normally the weaver will be working with subtypes of
		// this type - BcelField/BcelMethod
		if (backingGenericMember != null) {
			if (annotationTypes != null) {
				throw new BCException("Unexpectedly found a backing generic member and a local set of annotations");
			}
			return backingGenericMember.getAnnotationTypes();
		}
		return annotationTypes;
	}

	public String getAnnotationDefaultValue() {
		throw new UnsupportedOperationException(
				"You should resolve this member and call getAnnotationDefaultValue() on the result...");
	}

	@Override
	public AnnotationAJ[] getAnnotations() {
		if (backingGenericMember != null) {
			return backingGenericMember.getAnnotations();
		}
		if (annotations!=null) {
			return annotations;
		}
		return super.getAnnotations();
	}

	public AnnotationAJ getAnnotationOfType(UnresolvedType ofType) {
		if (annotations!=null) {
			// this means they have been set (we are likely a placeholder for an ITD, so a fake member)
			for (AnnotationAJ annotation: annotations) {
				if (annotation.getType().equals(ofType)) {
					return annotation;
				}
			}
			return null;
		}
		throw new UnsupportedOperationException("You should resolve this member and call getAnnotationOfType() on the result...");
	}

	public void setAnnotations(AnnotationAJ[] annotations) {
		this.annotations = annotations;
	}

	public void setAnnotationTypes(ResolvedType[] annotationTypes) {
		this.annotationTypes = annotationTypes;
	}

	public ResolvedType[][] getParameterAnnotationTypes() {
		return parameterAnnotationTypes;
	}

	public AnnotationAJ[][] getParameterAnnotations() {
		if (backingGenericMember != null) {
			return backingGenericMember.getParameterAnnotations();
		}
		throw new BCException("Cannot return parameter annotations for a " + this.getClass().getName() + " member");
		// return super.getParameterAnnotations();
	}

	public void addAnnotation(AnnotationAJ annotation) {
		if (annotationTypes == null) {
			annotationTypes = new ResolvedType[1];
			annotationTypes[0] = annotation.getType();
			annotations = new AnnotationAJ[1];
			annotations[0] = annotation;
		} else {
			int len = annotations.length;
			AnnotationAJ[] ret = new AnnotationAJ[len + 1];
			System.arraycopy(annotations, 0, ret, 0, len);
			ret[len] = annotation;
			annotations = ret;

			ResolvedType[] newAnnotationTypes = new ResolvedType[len + 1];
			System.arraycopy(annotationTypes, 0, newAnnotationTypes, 0, len);
			newAnnotationTypes[len] = annotation.getType();
			annotationTypes = newAnnotationTypes;
		}
	}

	public boolean isBridgeMethod() {
		return (modifiers & Constants.ACC_BRIDGE) != 0 && getKind().equals(METHOD);
	}

	public boolean isVarargsMethod() {
		return (modifiers & Constants.ACC_VARARGS) != 0;
	}

	public void setVarargsMethod() {
		modifiers = modifiers | Constants.ACC_VARARGS;
	}

	public boolean isSynthetic() {
		// See Bcelmethod.isSynthetic() which takes account of preJava5
		// Synthetic modifier
		return (modifiers & 4096) != 0; // do we know better?
	}

	public void write(CompressingDataOutputStream s) throws IOException {
		getKind().write(s);
		s.writeBoolean(s.canCompress()); // boolean indicates if parts of this are compressed references

		// write out the signature of the declaring type of this member
		if (s.canCompress()) {
			s.writeCompressedSignature(getDeclaringType().getSignature());
		} else {
			getDeclaringType().write(s);
		}

		// write out the modifiers
		s.writeInt(modifiers);

		// write out the name and the signature of this member
		if (s.canCompress()) {
			s.writeCompressedName(getName());
			s.writeCompressedSignature(getSignature());
		} else {
			s.writeUTF(getName());
			s.writeUTF(getSignature());
		}

		// write out the array clauses
		UnresolvedType.writeArray(getExceptions(), s);

		s.writeInt(getStart());
		s.writeInt(getEnd());
		s.writeBoolean(isVarargsMethod());

		// Write out any type variables...
		if (typeVariables == null) {
			s.writeByte(0);
		} else {
			s.writeByte(typeVariables.length);
			for (TypeVariable typeVariable : typeVariables) {
				typeVariable.write(s);
			}
		}
		String gsig = getGenericSignature();

		// change this to a byte: 255=false 0>254 means true and encodes the number of parameters
		if (getSignature().equals(gsig)) {
			s.writeByte(0xff);
		} else {
			s.writeByte(parameterTypes.length);
			for (UnresolvedType parameterType : parameterTypes) {
				if (s.canCompress()) {
					s.writeCompressedSignature(parameterType.getSignature());
				} else {
					UnresolvedType array_element = parameterType;
					array_element.write(s);
				}
			}
			if (s.canCompress()) {
				s.writeCompressedSignature(returnType.getSignature());
			} else {
				returnType.write(s);
			}
		}
	}

	/**
	 * Return the member generic signature that would be suitable for inclusion in a class file Signature attribute. For: &lt;T&gt;
	 * List&lt;String&gt; getThem(T t) {} we would create: &lt;T:Ljava/lang/Object;&gt;(TT;)Ljava/util/List&lt;Ljava/lang/String;&gt;;;
	 *
	 * @return the generic signature for the member that could be inserted into a class file
	 */
	public String getSignatureForAttribute() {
		StringBuilder sb = new StringBuilder();
		if (typeVariables != null) {
			sb.append("<");
			for (TypeVariable typeVariable : typeVariables) {
				sb.append(typeVariable.getSignatureForAttribute()); // need
				// a
				// 'getSignatureForAttribute()'
			}
			sb.append(">");
		}
		sb.append("(");
		for (UnresolvedType parameterType : parameterTypes) {
			ResolvedType ptype = (ResolvedType) parameterType;
			sb.append(ptype.getSignatureForAttribute());
		}
		sb.append(")");
		sb.append(((ResolvedType) returnType).getSignatureForAttribute());
		return sb.toString();
	}

	public String getGenericSignature() {
		StringBuilder sb = new StringBuilder();
		if (typeVariables != null) {
			sb.append("<");
			for (TypeVariable typeVariable : typeVariables) {
				sb.append(typeVariable.getSignature());
			}
			sb.append(">");
		}
		sb.append("(");
		for (UnresolvedType ptype : parameterTypes) {
			sb.append(ptype.getSignature());
		}
		sb.append(")");
		sb.append(returnType.getSignature());
		return sb.toString();
	}

	public static void writeArray(ResolvedMember[] members, CompressingDataOutputStream s) throws IOException {
		s.writeInt(members.length);
		for (ResolvedMember member : members) {
			member.write(s);
		}
	}

	public static ResolvedMemberImpl readResolvedMember(VersionedDataInputStream s, ISourceContext sourceContext)
			throws IOException {

		MemberKind mk = MemberKind.read(s);
		boolean compressed = (s.isAtLeast169() ? s.readBoolean() : false);
		UnresolvedType declaringType = compressed ? UnresolvedType.forSignature(s.readUtf8(s.readShort())) : UnresolvedType.read(s);
		int modifiers = s.readInt();
		String name = compressed ? s.readUtf8(s.readShort()) : s.readUTF();
		String signature = compressed ? s.readUtf8(s.readShort()) : s.readUTF();
		ResolvedMemberImpl m = new ResolvedMemberImpl(mk, declaringType, modifiers, name, signature);
		m.checkedExceptions = UnresolvedType.readArray(s);

		m.start = s.readInt();
		m.end = s.readInt();
		m.sourceContext = sourceContext;

		if (s.getMajorVersion() >= AjAttribute.WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ150) {

			if (s.getMajorVersion() >= AjAttribute.WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ150M4) {
				boolean isvarargs = s.readBoolean();
				if (isvarargs) {
					m.setVarargsMethod();
				}
			}

			int tvcount = s.isAtLeast169() ? s.readByte() : s.readInt();
			if (tvcount != 0) {
				m.typeVariables = new TypeVariable[tvcount];
				for (int i = 0; i < tvcount; i++) {
					m.typeVariables[i] = TypeVariable.read(s);
					m.typeVariables[i].setDeclaringElement(m);
					m.typeVariables[i].setRank(i);
				}
			}
			if (s.getMajorVersion() >= AjAttribute.WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ150M4) {
				int pcount = -1;
				boolean hasAGenericSignature = false;
				if (s.isAtLeast169()) {
					pcount = s.readByte();
					hasAGenericSignature = (pcount >= 0 && pcount < 255);
				} else {
					hasAGenericSignature = s.readBoolean();
				}
				if (hasAGenericSignature) {
					int ps = (s.isAtLeast169() ? pcount : s.readInt());
					UnresolvedType[] params = new UnresolvedType[ps];
					for (int i = 0; i < params.length; i++) {
						if (compressed) {
							params[i] = TypeFactory.createTypeFromSignature(s.readSignature());
						} else {
							params[i] = TypeFactory.createTypeFromSignature(s.readUTF());
						}
					}
					UnresolvedType rt = compressed ? TypeFactory.createTypeFromSignature(s.readSignature()) : TypeFactory
							.createTypeFromSignature(s.readUTF());
					m.parameterTypes = params;
					m.returnType = rt;
				}
			}
		}
		return m;
	}

	public static ResolvedMember[] readResolvedMemberArray(VersionedDataInputStream s, ISourceContext context) throws IOException {
		int len = s.readInt();
		ResolvedMember[] members = new ResolvedMember[len];
		for (int i = 0; i < len; i++) {
			members[i] = ResolvedMemberImpl.readResolvedMember(s, context);
		}
		return members;
	}

	// OPTIMIZE dont like how resolve(world) on ResolvedMemberImpl does
	// something different to world.resolve(member)
	@Override
	public ResolvedMember resolve(World world) {
		if (isResolved) {
			return this;
		}
		// make sure all the pieces of a resolvedmember really are resolved
		try {
			if (typeVariables != null && typeVariables.length > 0) {
				for (int i = 0; i < typeVariables.length; i++) {
					typeVariables[i] = typeVariables[i].resolve(world);
				}
			}
			world.setTypeVariableLookupScope(this);
			// if (annotationTypes != null) {
			// Set<ResolvedType> r = new HashSet<ResolvedType>();
			// for (UnresolvedType element : annotationTypes) {
			// // for (Iterator iter = annotationTypes.iterator(); iter.hasNext();) {
			// // UnresolvedType element = (UnresolvedType) iter.next();
			// r.add(world.resolve(element));
			// }
			// annotationTypes = r;
			// }
			declaringType = declaringType.resolve(world);
			if (declaringType.isRawType()) {
				declaringType = ((ReferenceType) declaringType).getGenericType();
			}

			if (parameterTypes != null && parameterTypes.length > 0) {
				for (int i = 0; i < parameterTypes.length; i++) {
					parameterTypes[i] = parameterTypes[i].resolve(world);
				}
			}

			returnType = returnType.resolve(world);

		} finally {
			world.setTypeVariableLookupScope(null);
		}
		isResolved = true;
		return this;
	}

	public ISourceContext getSourceContext(World world) {
		return getDeclaringType().resolve(world).getSourceContext();
	}

	public String[] getParameterNames() {
		return parameterNames;
	}

	public final void setParameterNames(String[] pnames) {
		parameterNames = pnames;
	}

	@Override
	public final String[] getParameterNames(World world) {
		return getParameterNames();
	}

	public AjAttribute.EffectiveSignatureAttribute getEffectiveSignature() {
		return null;
	}

	public ISourceLocation getSourceLocation() {
		// System.out.println("get context: " + this + " is " + sourceContext);
		if (getSourceContext() == null) {
			// System.err.println("no context: " + this);
			return null;
		}
		return getSourceContext().makeSourceLocation(this);
	}

	public int getEnd() {
		return end;
	}

	public ISourceContext getSourceContext() {
		return sourceContext;
	}

	public int getStart() {
		return start;
	}

	public void setPosition(int sourceStart, int sourceEnd) {
		this.start = sourceStart;
		this.end = sourceEnd;
	}

	public void setDeclaringType(ReferenceType rt) {
		declaringType = rt;
	}

	public void setSourceContext(ISourceContext sourceContext) {
		this.sourceContext = sourceContext;
	}

	public boolean isAbstract() {
		return Modifier.isAbstract(modifiers);
	}

	public boolean isPublic() {
		return Modifier.isPublic(modifiers);
	}

	public boolean isDefault() {
		int mods = getModifiers();
		return !(Modifier.isPublic(mods) || Modifier.isProtected(mods) || Modifier.isPrivate(mods));
	}

	public boolean isVisible(ResolvedType fromType) {
		UnresolvedType declaringType = getDeclaringType();
		ResolvedType type = null;
		if (fromType.equals(declaringType)) {
			type = fromType;
		} else {
			World world = fromType.getWorld();
			type = declaringType.resolve(world);
		}
		return ResolvedType.isVisible(getModifiers(), type, fromType);
	}

	public void setCheckedExceptions(UnresolvedType[] checkedExceptions) {
		this.checkedExceptions = checkedExceptions;
	}

	public void setAnnotatedElsewhere(boolean b) {
		isAnnotatedElsewhere = b;
	}

	public boolean isAnnotatedElsewhere() {
		return isAnnotatedElsewhere;
	}

	/**
	 * Get the UnresolvedType for the return type, taking generic signature into account
	 */
	@Override
	public UnresolvedType getGenericReturnType() {
		return getReturnType();
	}

	/**
	 * Get the TypeXs of the parameter types, taking generic signature into account
	 */
	@Override
	public UnresolvedType[] getGenericParameterTypes() {
		return getParameterTypes();
	}

	public ResolvedMemberImpl parameterizedWith(UnresolvedType[] typeParameters, ResolvedType newDeclaringType,
			boolean isParameterized) {
		return parameterizedWith(typeParameters, newDeclaringType, isParameterized, null);
	}

	/**
	 * Return a resolvedmember in which all the type variables in the signature have been replaced with the given bindings. The
	 * 'isParameterized' flag tells us whether we are creating a raw type version or not. if (isParameterized) then List&lt;T&gt; will
	 * turn into List&lt;String&gt; (for example) - if (!isParameterized) then List&lt;T&gt; will turn into List.
	 */
	public ResolvedMemberImpl parameterizedWith(UnresolvedType[] typeParameters, ResolvedType newDeclaringType,
			boolean isParameterized, List<String> aliases) {
		// PR308773
		// this check had problems for the inner type of a generic type because the inner type can be represented
		// by a 'simple type' if it is only sharing type variables with the outer and has none of its own. To avoid the
		// check going bang in this case we check for $ (crap...) - we can't check the outer because the declaring type
		// is considered unresolved...
		if (// isParameterized && <-- might need this bit...
		!getDeclaringType().isGenericType() && !getDeclaringType().getName().contains("$")) {
			throw new IllegalStateException("Can't ask to parameterize a member of non-generic type: " + getDeclaringType()
					+ "  kind(" + getDeclaringType().typeKind + ")");
		}
		TypeVariable[] typeVariables = getDeclaringType().getTypeVariables();
		if (isParameterized && (typeVariables.length != typeParameters.length)) {
			throw new IllegalStateException("Wrong number of type parameters supplied");
		}
		Map<String, UnresolvedType> typeMap = new HashMap<>();
		boolean typeParametersSupplied = typeParameters != null && typeParameters.length > 0;
		if (typeVariables != null) {
			// If no 'replacements' were supplied in the typeParameters array
			// then collapse
			// type variables to their first bound.
			for (int i = 0; i < typeVariables.length; i++) {
				UnresolvedType ut = (!typeParametersSupplied ? typeVariables[i].getFirstBound() : typeParameters[i]);
				typeMap.put(typeVariables[i].getName(), ut);
			}
		}
		// For ITDs on generic types that use type variables from the target type, the aliases
		// record the alternative names used throughout the ITD expression that must map to
		// the same value as the type variables real name.
		if (aliases != null) {
			int posn = 0;
			for (String typeVariableAlias : aliases) {
				typeMap.put(typeVariableAlias, (!typeParametersSupplied ? typeVariables[posn].getFirstBound()
						: typeParameters[posn]));
				posn++;
			}
		}

		UnresolvedType parameterizedReturnType = parameterize(getGenericReturnType(), typeMap, isParameterized,
				newDeclaringType.getWorld());
		UnresolvedType[] parameterizedParameterTypes = new UnresolvedType[getGenericParameterTypes().length];
		UnresolvedType[] genericParameterTypes = getGenericParameterTypes();
		for (int i = 0; i < parameterizedParameterTypes.length; i++) {
			parameterizedParameterTypes[i] = parameterize(genericParameterTypes[i], typeMap, isParameterized,
					newDeclaringType.getWorld());
		}
		ResolvedMemberImpl ret = new ResolvedMemberImpl(getKind(), newDeclaringType, getModifiers(), parameterizedReturnType,
				getName(), parameterizedParameterTypes, getExceptions(), this);
		ret.setTypeVariables(getTypeVariables());
		ret.setSourceContext(getSourceContext());
		ret.setPosition(getStart(), getEnd());
		ret.setParameterNames(getParameterNames());
		return ret;
	}

	/**
	 * Replace occurrences of type variables in the signature with values contained in the map. The map is of the form
	 * A=String,B=Integer and so a signature List&lt;A&gt; Foo.m(B i) {} would become List&lt;String&gt; Foo.m(Integer i) {}
	 */
	public ResolvedMember parameterizedWith(Map<String, UnresolvedType> m, World w) {
		// if (//isParameterized && <-- might need this bit...
		// !getDeclaringType().isGenericType()) {
		// throw new IllegalStateException(
		// "Can't ask to parameterize a member of non-generic type: "
		// +getDeclaringType()+"  kind("+
		// getDeclaringType().typeKind+")");
		// }
		declaringType = declaringType.resolve(w);
		if (declaringType.isRawType()) {
			declaringType = ((ResolvedType) declaringType).getGenericType();
			// TypeVariable[] typeVariables = getDeclaringType().getTypeVariables();
			// if (isParameterized && (typeVariables.length !=
			// typeParameters.length)) {
			// throw new
			// IllegalStateException("Wrong number of type parameters supplied");
			// }
			// Map typeMap = new HashMap();
			// boolean typeParametersSupplied = typeParameters!=null &&
			// typeParameters.length>0;
			// if (typeVariables!=null) {
			// // If no 'replacements' were supplied in the typeParameters array
			// then collapse
			// // type variables to their first bound.
			// for (int i = 0; i < typeVariables.length; i++) {
			// UnresolvedType ut =
			// (!typeParametersSupplied?typeVariables[i].getFirstBound
			// ():typeParameters[i]);
			// typeMap.put(typeVariables[i].getName(),ut);
			// }
			// }
			// // For ITDs on generic types that use type variables from the target
			// type, the aliases
			// // record the alternative names used throughout the ITD expression
			// that must map to
			// // the same value as the type variables real name.
			// if (aliases!=null) {
			// int posn = 0;
			// for (Iterator iter = aliases.iterator(); iter.hasNext();) {
			// String typeVariableAlias = (String) iter.next();
			// typeMap.put(typeVariableAlias,(!typeParametersSupplied?typeVariables[
			// posn].getFirstBound():typeParameters[posn]));
			// posn++;
			// }
			// }
		}

		UnresolvedType parameterizedReturnType = parameterize(getGenericReturnType(), m, true, w);
		UnresolvedType[] parameterizedParameterTypes = new UnresolvedType[getGenericParameterTypes().length];
		UnresolvedType[] genericParameterTypes = getGenericParameterTypes();
		for (int i = 0; i < parameterizedParameterTypes.length; i++) {
			parameterizedParameterTypes[i] = parameterize(genericParameterTypes[i], m, true, w);
		}
		ResolvedMemberImpl ret = new ResolvedMemberImpl(getKind(), declaringType, getModifiers(), parameterizedReturnType,
				getName(), parameterizedParameterTypes, getExceptions(), this);
		ret.setTypeVariables(getTypeVariables());
		ret.setSourceContext(getSourceContext());
		ret.setPosition(getStart(), getEnd());
		ret.setParameterNames(getParameterNames());
		return ret;
	}

	public void setTypeVariables(TypeVariable[] tvars) {
		typeVariables = tvars;
	}

	public TypeVariable[] getTypeVariables() {
		return typeVariables;
	}

	protected UnresolvedType parameterize(UnresolvedType aType, Map<String, UnresolvedType> typeVariableMap,
			boolean inParameterizedType, World w) {
		if (aType instanceof TypeVariableReference) {
			String variableName = ((TypeVariableReference) aType).getTypeVariable().getName();
			if (!typeVariableMap.containsKey(variableName)) {
				return aType; // if the type variable comes from the method (and
				// not the type) thats OK
			}
			return typeVariableMap.get(variableName);
		} else if (aType.isParameterizedType()) {
			if (inParameterizedType) {
				if (w != null) {
					aType = aType.resolve(w);
				} else {
					UnresolvedType dType = getDeclaringType();
					aType = aType.resolve(((ResolvedType) dType).getWorld());
				}
				return aType.parameterize(typeVariableMap);
			} else {
				return aType.getRawType();
			}
		} else if (aType.isArray()) {
			// The component type might be a type variable (pr150095)
			int dims = 1;
			String sig = aType.getSignature();
			// while (sig.charAt(dims) == '[')
			// dims++;
			UnresolvedType arrayType = null;
			UnresolvedType componentSig = UnresolvedType.forSignature(sig.substring(dims));
			UnresolvedType parameterizedComponentSig = parameterize(componentSig, typeVariableMap, inParameterizedType, w);
			if (parameterizedComponentSig.isTypeVariableReference()
					&& parameterizedComponentSig instanceof UnresolvedTypeVariableReferenceType
					&& typeVariableMap.containsKey(((UnresolvedTypeVariableReferenceType) parameterizedComponentSig)
							.getTypeVariable().getName())) { // pr250632
				// TODO ASC bah, this code is rubbish - i should fix it properly
				StringBuilder newsig = new StringBuilder();
				newsig.append("[T");
				newsig.append(((UnresolvedTypeVariableReferenceType) parameterizedComponentSig).getTypeVariable().getName());
				newsig.append(";");
				arrayType = UnresolvedType.forSignature(newsig.toString());
			} else {
				arrayType = ResolvedType.makeArray(parameterizedComponentSig, dims);
			}
			return arrayType;
		}
		return aType;
	}

	/**
	 * If this member is defined by a parameterized super-type, return the erasure of that member. For example: interface I&lt;T&gt; { T
	 * foo(T aTea); } class C implements I&lt;String&gt; { String foo(String aString) { return "something"; } } The resolved member for
	 * C.foo has signature String foo(String). The erasure of that member is Object foo(Object) -- use upper bound of type variable.
	 * A type is a supertype of itself.
	 */
	// public ResolvedMember getErasure() {
	// if (calculatedMyErasure) return myErasure;
	// calculatedMyErasure = true;
	// ResolvedType resolvedDeclaringType = (ResolvedType) getDeclaringType();
	// // this next test is fast, and the result is cached.
	// if (!resolvedDeclaringType.hasParameterizedSuperType()) {
	// return null;
	// } else {
	// // we have one or more parameterized super types.
	// // this member may be defined by one of them... we need to find out.
	// Collection declaringTypes =
	// this.getDeclaringTypes(resolvedDeclaringType.getWorld());
	// for (Iterator iter = declaringTypes.iterator(); iter.hasNext();) {
	// ResolvedType aDeclaringType = (ResolvedType) iter.next();
	// if (aDeclaringType.isParameterizedType()) {
	// // we've found the (a?) parameterized type that defines this member.
	// // now get the erasure of it
	// ResolvedMemberImpl matchingMember = (ResolvedMemberImpl)
	// aDeclaringType.lookupMemberNoSupers(this);
	// if (matchingMember != null && matchingMember.backingGenericMember !=
	// null) {
	// myErasure = matchingMember.backingGenericMember;
	// return myErasure;
	// }
	// }
	// }
	// }
	// return null;
	// }
	//
	// private ResolvedMember myErasure = null;
	// private boolean calculatedMyErasure = false;
	public boolean hasBackingGenericMember() {
		return backingGenericMember != null;
	}

	public ResolvedMember getBackingGenericMember() {
		return backingGenericMember;
	}

	/**
	 * For ITDs, we use the default factory methods to build a resolved member, then alter a couple of characteristics using this
	 * method - this is safe.
	 */
	public void resetName(String newName) {
		this.name = newName;
	}

	public void resetKind(MemberKind newKind) {
		this.kind = newKind;
	}

	public void resetModifiers(int newModifiers) {
		this.modifiers = newModifiers;
	}

	public void resetReturnTypeToObjectArray() {
		returnType = UnresolvedType.OBJECTARRAY;
	}

	/**
	 * Returns true if this member matches the other. The matching takes into account name and parameter types only. When comparing
	 * parameter types, we allow any type variable to match any other type variable regardless of bounds.
	 */
	public boolean matches(ResolvedMember aCandidateMatch, boolean ignoreGenerics) {
		ResolvedMemberImpl candidateMatchImpl = (ResolvedMemberImpl) aCandidateMatch;
		if (!getName().equals(aCandidateMatch.getName())) {
			return false;
		}
		UnresolvedType[] parameterTypes = getGenericParameterTypes();
		UnresolvedType[] candidateParameterTypes = aCandidateMatch.getGenericParameterTypes();
		if (parameterTypes.length != candidateParameterTypes.length) {
			return false;
		}
		boolean b = false;
		/*
		 * if (ignoreGenerics) { String myParameterSignature = getParameterSigWithBoundsRemoved(); String
		 * candidateParameterSignature = candidateMatchImpl.getParameterSigWithBoundsRemoved(); if
		 * (myParameterSignature.equals(candidateParameterSignature)) { b = true; } else { myParameterSignature =
		 * (hasBackingGenericMember() ? backingGenericMember.getParameterSignatureErased() : getParameterSignatureErased());
		 * candidateParameterSignature = (candidateMatchImpl.hasBackingGenericMember() ? candidateMatchImpl.backingGenericMember
		 * .getParameterSignatureErased() : candidateMatchImpl.getParameterSignatureErased()); // System.out.println("my psig = " +
		 * myParameterSignature); // System.out.println("can psig = " + candidateParameterSignature); b =
		 * myParameterSignature.equals(candidateParameterSignature); } } else {
		 */
		String myParameterSignature = getParameterSigWithBoundsRemoved();
		String candidateParameterSignature = candidateMatchImpl.getParameterSigWithBoundsRemoved();
		if (myParameterSignature.equals(candidateParameterSignature)) {
			b = true;
		} else {
			// try erasure
			myParameterSignature = getParameterSignatureErased();
			candidateParameterSignature = candidateMatchImpl.getParameterSignatureErased();
			// myParameterSignature = (hasBackingGenericMember() ? backingGenericMember.getParameterSignatureErased()
			// : getParameterSignatureErased());
			// candidateParameterSignature = (candidateMatchImpl.hasBackingGenericMember() ?
			// candidateMatchImpl.backingGenericMember
			// .getParameterSignatureErased() : candidateMatchImpl.getParameterSignatureErased());
			// System.out.println("my psig = " + myParameterSignature);
			// System.out.println("can psig = " + candidateParameterSignature);
			b = myParameterSignature.equals(candidateParameterSignature);
			// }
		}
		// System.out.println("Checking param signatures: " + b);
		return b;
	}

	/**
	 * converts e.g. <T extends Number>.... List<T> to just Ljava/util/List<T;>; whereas the full signature would be
	 * Ljava/util/List<T:Ljava/lang/Number;>;
	 */
	private String myParameterSignatureWithBoundsRemoved = null;
	/**
	 * converts e.g. <T extends Number>.... List<T> to just Ljava/util/List;
	 */
	private String myParameterSignatureErasure = null;

	// does NOT produce a meaningful java signature, but does give a unique
	// string suitable for
	// comparison.
	private String getParameterSigWithBoundsRemoved() {
		if (myParameterSignatureWithBoundsRemoved != null) {
			return myParameterSignatureWithBoundsRemoved;
		}
		StringBuffer sig = new StringBuffer();
		UnresolvedType[] myParameterTypes = getGenericParameterTypes();
		for (UnresolvedType myParameterType : myParameterTypes) {
			appendSigWithTypeVarBoundsRemoved(myParameterType, sig, new HashSet<>());
		}
		myParameterSignatureWithBoundsRemoved = sig.toString();
		return myParameterSignatureWithBoundsRemoved;
	}

	/**
	 * Return the erased form of the signature with bounds collapsed for type variables, etc. Does not include the return type, @see
	 * getParam
	 */
	public String getParameterSignatureErased() {
		if (myParameterSignatureErasure == null) {
			StringBuilder sig = new StringBuilder();
			for (UnresolvedType parameter : getParameterTypes()) {
				sig.append(parameter.getErasureSignature());
			}
			myParameterSignatureErasure = sig.toString();
		}
		return myParameterSignatureErasure;
	}

	public String getSignatureErased() {
		StringBuilder sb = new StringBuilder();
		sb.append("(");
		sb.append(getParameterSignatureErased());
		sb.append(")");
		sb.append(getReturnType().getErasureSignature());
		return sb.toString();
	}

	// does NOT produce a meaningful java signature, but does give a unique
	// string suitable for
	// comparison.
	public static void appendSigWithTypeVarBoundsRemoved(UnresolvedType aType, StringBuffer toBuffer,
			Set<UnresolvedType> alreadyUsedTypeVars) {
		if (aType.isTypeVariableReference()) {
			TypeVariableReferenceType typeVariableRT = (TypeVariableReferenceType) aType;
			// pr204505
			if (alreadyUsedTypeVars.contains(aType)) {
				toBuffer.append("...");
			} else {
				alreadyUsedTypeVars.add(aType);
				appendSigWithTypeVarBoundsRemoved(typeVariableRT.getTypeVariable().getFirstBound(), toBuffer, alreadyUsedTypeVars);
			}
			// toBuffer.append("T;");
		} else if (aType.isParameterizedType()) {
			toBuffer.append(aType.getRawType().getSignature());
			toBuffer.append("<");
			for (int i = 0; i < aType.getTypeParameters().length; i++) {
				appendSigWithTypeVarBoundsRemoved(aType.getTypeParameters()[i], toBuffer, alreadyUsedTypeVars);
			}
			toBuffer.append(">;");
		} else {
			toBuffer.append(aType.getSignature());
		}
	}

	/**
	 * Useful for writing tests, returns *everything* we know about this member.
	 */
	public String toDebugString() {
		StringBuilder r = new StringBuilder();

		// modifiers
		int mods = modifiers;
		if ((mods & 4096) > 0) {
			mods = mods - 4096; // remove synthetic (added in the ASM case but
		}
		// not in the BCEL case...)
		if ((mods & 512) > 0) {
			mods = mods - 512; // remove interface (added in the BCEL case but
		}
		// not in the ASM case...)
		if ((mods & 131072) > 0) {
			mods = mods - 131072; // remove deprecated (added in the ASM case
		}
		// but not in the BCEL case...)
		String modsStr = Modifier.toString(mods);
		if (modsStr.length() != 0) {
			r.append(modsStr).append("(" + mods + ")").append(" ");
		}

		// type variables
		if (typeVariables != null && typeVariables.length > 0) {
			r.append("<");
			for (int i = 0; i < typeVariables.length; i++) {
				if (i > 0) {
					r.append(",");
				}
				TypeVariable t = typeVariables[i];
				r.append(t.toDebugString());
			}
			r.append("> ");
		}

		// 'declaring' type
		r.append(getGenericReturnType().toDebugString());
		r.append(' ');

		// name
		r.append(declaringType.getName());
		r.append('.');
		r.append(name);

		// parameter signature if a method
		if (kind != FIELD) {
			r.append("(");
			UnresolvedType[] params = getGenericParameterTypes();
			boolean parameterNamesExist = showParameterNames && parameterNames != null && parameterNames.length == params.length;
			if (params.length != 0) {
				for (int i = 0, len = params.length; i < len; i++) {
					if (i > 0) {
						r.append(", ");
					}
					r.append(params[i].toDebugString());
					if (parameterNamesExist) {
						r.append(" ").append(parameterNames[i]);
					}
				}
			}
			r.append(")");
		}
		return r.toString();
	}

	// SECRETAPI - controlling whether parameter names come out in the debug
	// string (for testing purposes)
	public static boolean showParameterNames = true;

	public String toGenericString() {
		StringBuilder buf = new StringBuilder();
		buf.append(getGenericReturnType().getSimpleName());
		buf.append(' ');
		buf.append(declaringType.getName());
		buf.append('.');
		buf.append(name);
		if (kind != FIELD) {
			buf.append("(");
			UnresolvedType[] params = getGenericParameterTypes();
			if (params.length != 0) {
				buf.append(params[0].getSimpleName());
				for (int i = 1, len = params.length; i < len; i++) {
					buf.append(", ");
					buf.append(params[i].getSimpleName());
				}
			}
			buf.append(")");
		}
		return buf.toString();
	}

	public boolean isCompatibleWith(Member am) {
		if (kind != METHOD || am.getKind() != METHOD) {
			return true;
		}
		if (!name.equals(am.getName())) {
			return true;
		}
		if (!equalTypes(getParameterTypes(), am.getParameterTypes())) {
			return true;
		}
		return getReturnType().equals(am.getReturnType());
	}

	private static boolean equalTypes(UnresolvedType[] a, UnresolvedType[] b) {
		int len = a.length;
		if (len != b.length) {
			return false;
		}
		for (int i = 0; i < len; i++) {
			if (!a[i].equals(b[i])) {
				return false;
			}
		}
		return true;
	}

	public TypeVariable getTypeVariableNamed(String name) {
		// Check locally...
		if (typeVariables != null) {
			for (TypeVariable typeVariable : typeVariables) {
				if (typeVariable.getName().equals(name)) {
					return typeVariable;
				}
			}
		}
		// check the declaring type!
		return declaringType.getTypeVariableNamed(name);

		// Do generic aspects with ITDs that share type variables with the
		// aspect and the target type and have their own tvars cause
		// this to be messier?
	}

	public void evictWeavingState() {
	}


	public boolean isEquivalentTo(Object other) {
		return this.equals(other);
	}

	public boolean isDefaultConstructor() {
		return false;
	}
}
