/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * 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:
 *     PARC     initial implementation
 * ******************************************************************/

package org.aspectj.weaver.patterns;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.FileUtil;
import org.aspectj.util.FuzzyBoolean;
import org.aspectj.weaver.AjAttribute;
import org.aspectj.weaver.ArrayReferenceType;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.BoundedReferenceType;
import org.aspectj.weaver.CompressingDataOutputStream;
import org.aspectj.weaver.IHasPosition;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.TypeFactory;
import org.aspectj.weaver.TypeVariable;
import org.aspectj.weaver.TypeVariableReference;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.UnresolvedTypeVariableReferenceType;
import org.aspectj.weaver.VersionedDataInputStream;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.WildcardedUnresolvedType;
import org.aspectj.weaver.World;

/**
 * The PatternParser always creates WildTypePatterns for type patterns in pointcut expressions (apart from *, which is sometimes
 * directly turned into TypePattern.ANY). resolveBindings() tries to work out what we've really got and turn it into a type pattern
 * that we can use for matching. This will normally be either an ExactTypePattern or a WildTypePattern.
 *
 * Here's how the process pans out for various generic and parameterized patterns: (see GenericsWildTypePatternResolvingTestCase)
 *
 * Foo where Foo exists and is generic Parser creates WildTypePattern namePatterns={Foo} resolveBindings resolves Foo to RT(Foo -
 * raw) return ExactTypePattern(LFoo;)
 *
 * Foo&lt;String&gt; where Foo exists and String meets the bounds Parser creates WildTypePattern namePatterns = {Foo},
 * typeParameters=WTP{String} resolveBindings resolves typeParameters to ExactTypePattern(String) resolves Foo to RT(Foo) returns
 * ExactTypePattern(PFoo&lt;String&gt;; - parameterized)
 *
 * Foo&lt;Str*&gt; where Foo exists and takes one bound Parser creates WildTypePattern namePatterns = {Foo}, typeParameters=WTP{Str*}
 * resolveBindings resolves typeParameters to WTP{Str*} resolves Foo to RT(Foo) returns WildTypePattern(name = Foo, typeParameters =
 * WTP{Str*} isGeneric=false)
 *
 * Fo*&lt;String&gt; Parser creates WildTypePattern namePatterns = {Fo*}, typeParameters=WTP{String} resolveBindings resolves
 * typeParameters to ETP{String} returns WildTypePattern(name = Fo*, typeParameters = ETP{String} isGeneric=false)
 *
 *
 * Foo&lt;?&gt;
 *
 * Foo&lt;? extends Number&gt;
 *
 * Foo&lt;? extends Number+&gt;
 *
 * Foo&lt;? super Number&gt;
 *
 */
public class WildTypePattern extends TypePattern {
	private static final String GENERIC_WILDCARD_CHARACTER = "?"; // signature of ? is *
	private static final String GENERIC_WILDCARD_SIGNATURE_CHARACTER = "*"; // signature of ? is *
	private NamePattern[] namePatterns;
	private boolean failedResolution = false;
	int ellipsisCount;
	String[] importedPrefixes;
	String[] knownMatches;
	int dim;

	// SECRETAPI - just for testing, turns off boundschecking temporarily...
	public static boolean boundscheckingoff = false;

	// these next three are set if the type pattern is constrained by extends or super clauses, in which case the
	// namePatterns must have length 1
	// TODO AMC: read/write/resolve of these fields
	TypePattern upperBound; // extends Foo
	TypePattern[] additionalInterfaceBounds; // extends Foo & A,B,C
	TypePattern lowerBound; // super Foo

	// if we have type parameters, these fields indicate whether we should be a generic type pattern or a parameterized
	// type pattern. We can only tell during resolve bindings.
	private boolean isGeneric = true;

	WildTypePattern(NamePattern[] namePatterns, boolean includeSubtypes, int dim, boolean isVarArgs, TypePatternList typeParams) {
		super(includeSubtypes, isVarArgs, typeParams);
		this.namePatterns = namePatterns;
		this.dim = dim;
		ellipsisCount = 0;
		for (NamePattern namePattern : namePatterns) {
			if (namePattern == NamePattern.ELLIPSIS) {
				ellipsisCount++;
			}
		}
		setLocation(namePatterns[0].getSourceContext(), namePatterns[0].getStart(), namePatterns[namePatterns.length - 1].getEnd());
	}

	public WildTypePattern(List<NamePattern> names, boolean includeSubtypes, int dim) {
		this(names.toArray(new NamePattern[0]), includeSubtypes, dim, false, TypePatternList.EMPTY);

	}

	public WildTypePattern(List<NamePattern> names, boolean includeSubtypes, int dim, int endPos) {
		this(names, includeSubtypes, dim);
		this.end = endPos;
	}

	public WildTypePattern(List<NamePattern> names, boolean includeSubtypes, int dim, int endPos, boolean isVarArg) {
		this(names, includeSubtypes, dim);
		this.end = endPos;
		this.isVarArgs = isVarArg;
	}

	public WildTypePattern(List<NamePattern> names, boolean includeSubtypes, int dim, int endPos, boolean isVarArg, TypePatternList typeParams,
			TypePattern upperBound, TypePattern[] additionalInterfaceBounds, TypePattern lowerBound) {
		this(names.toArray(new NamePattern[0]), includeSubtypes, dim, isVarArg, typeParams);
		this.end = endPos;
		this.upperBound = upperBound;
		this.lowerBound = lowerBound;
		this.additionalInterfaceBounds = additionalInterfaceBounds;
	}

	public WildTypePattern(List<NamePattern> names, boolean includeSubtypes, int dim, int endPos, boolean isVarArg, TypePatternList typeParams) {
		this(names.toArray(new NamePattern[0]), includeSubtypes, dim, isVarArg, typeParams);
		this.end = endPos;
	}

	public NamePattern[] getNamePatterns() {
		return namePatterns;
	}

	public TypePattern getUpperBound() {
		return upperBound;
	}

	public TypePattern getLowerBound() {
		return lowerBound;
	}

	public TypePattern[] getAdditionalIntefaceBounds() {
		return additionalInterfaceBounds;
	}

	// called by parser after parsing a type pattern, must bump dim as well as setting flag
	@Override
	public void setIsVarArgs(boolean isVarArgs) {
		this.isVarArgs = isVarArgs;
		if (isVarArgs) {
			this.dim += 1;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see org.aspectj.weaver.patterns.TypePattern#couldEverMatchSameTypesAs(org.aspectj.weaver.patterns.TypePattern)
	 */
	@Override
	protected boolean couldEverMatchSameTypesAs(TypePattern other) {
		if (super.couldEverMatchSameTypesAs(other)) {
			return true;
		}
		// false is necessary but not sufficient
		UnresolvedType otherType = other.getExactType();
		if (!ResolvedType.isMissing(otherType)) {
			if (namePatterns.length > 0) {
				if (!namePatterns[0].matches(otherType.getName())) {
					return false;
				}
			}
		}
		if (other instanceof WildTypePattern) {
			WildTypePattern owtp = (WildTypePattern) other;
			String mySimpleName = namePatterns[0].maybeGetSimpleName();
			String yourSimpleName = owtp.namePatterns[0].maybeGetSimpleName();
			if (mySimpleName != null && yourSimpleName != null) {
				return (mySimpleName.startsWith(yourSimpleName) || yourSimpleName.startsWith(mySimpleName));
			}
		}
		return true;
	}

	// XXX inefficient implementation
	// we don't know whether $ characters are from nested types, or were
	// part of the declared type name (generated code often uses $s in type
	// names). More work required on our part to get this right...
	public static char[][] splitNames(String s, boolean convertDollar) {
		List<char[]> ret = new ArrayList<>();
		int startIndex = 0;
		while (true) {
			int breakIndex = s.indexOf('.', startIndex); // what about /
			if (convertDollar && (breakIndex == -1)) {
				breakIndex = s.indexOf('$', startIndex); // we treat $ like . here
			}
			if (breakIndex == -1) {
				break;
			}
			char[] name = s.substring(startIndex, breakIndex).toCharArray();
			ret.add(name);
			startIndex = breakIndex + 1;
		}
		ret.add(s.substring(startIndex).toCharArray());
		return ret.toArray(new char[ret.size()][]);
	}

	/**
	 * @see org.aspectj.weaver.patterns.TypePattern#matchesExactly(ResolvedType)
	 */
	@Override
	protected boolean matchesExactly(ResolvedType type) {
		return matchesExactly(type, type);
	}

	@Override
	protected boolean matchesExactly(ResolvedType type, ResolvedType annotatedType) {
		String targetTypeName = type.getName();

		// System.err.println("match: " + targetTypeName + ", " + knownMatches); //Arrays.asList(importedPrefixes));
		// Ensure the annotation pattern is resolved
		annotationPattern.resolve(type.getWorld());

		return matchesExactlyByName(targetTypeName.replaceFirst("(\\[\\])+$", ""), type.isAnonymous(), type.isNested())
				&& matchesParameters(type, STATIC)
				&& matchesArray(type)
				&& matchesBounds(type, STATIC)
				&& annotationPattern.matches(annotatedType, type.temporaryAnnotationTypes).alwaysTrue();
	}

	// we've matched against the base (or raw) type, but if this type pattern specifies parameters or
	// type variables we need to make sure we match against them too
	private boolean matchesParameters(ResolvedType aType, MatchKind staticOrDynamic) {
		// For array reference types, match type parameters on component type, not on array type itself
		if (aType instanceof ArrayReferenceType)
			aType = aType.getResolvedComponentType();
		if (!isGeneric && typeParameters.size() > 0) {
			if (!aType.isParameterizedType()) {
				return false;
			}
			// we have to match type parameters
			return typeParameters.matches(aType.getResolvedTypeParameters(), staticOrDynamic).alwaysTrue();
		}
		return true;
	}

	@Override
	protected boolean matchesArray(UnresolvedType type) {
		return type.getDimensions() == getDimensions() ||
			 getDimensions() == 0 && namePatterns.length > 0 && namePatterns[namePatterns.length-1].toString().endsWith("*");
	}

	// we've matched against the base (or raw) type, but if this type pattern specifies bounds because
	// it is a ? extends or ? super deal then we have to match them too.
	private boolean matchesBounds(ResolvedType aType, MatchKind staticOrDynamic) {
		if (!(aType instanceof BoundedReferenceType)) {
			return true;
		}
		BoundedReferenceType boundedRT = (BoundedReferenceType) aType;
		if (upperBound == null && boundedRT.getUpperBound() != null) {
			// for upper bound, null can also match against Object - but anything else and we're out.
			if (!boundedRT.getUpperBound().getName().equals(UnresolvedType.OBJECT.getName())) {
				return false;
			}
		}
		if (lowerBound == null && boundedRT.getLowerBound() != null) {
			return false;
		}
		if (upperBound != null) {
			// match ? extends
			if (aType.isGenericWildcard() && boundedRT.isSuper()) {
				return false;
			}
			if (boundedRT.getUpperBound() == null) {
				return false;
			}
			return upperBound.matches((ResolvedType) boundedRT.getUpperBound(), staticOrDynamic).alwaysTrue();
		}
		if (lowerBound != null) {
			// match ? super
			if (!(boundedRT.isGenericWildcard() && boundedRT.isSuper())) {
				return false;
			}
			return lowerBound.matches((ResolvedType) boundedRT.getLowerBound(), staticOrDynamic).alwaysTrue();
		}
		return true;
	}

	/**
	 * Used in conjunction with checks on 'isStar()' to tell you if this pattern represents '*' or '*[]' which are different !
	 */
	public int getDimensions() {
		return dim;
	}

	@Override
	public boolean isArray() {
		return dim > 0;
	}

	/**
	 * @param targetTypeName
	 * @return
	 */
	private boolean matchesExactlyByName(String targetTypeName, boolean isAnonymous, boolean isNested) {
		// we deal with parameter matching separately...
		if (targetTypeName.indexOf('<') != -1) {
			targetTypeName = targetTypeName.substring(0, targetTypeName.indexOf('<'));
		}
		// we deal with bounds matching separately too...
		if (targetTypeName.startsWith(GENERIC_WILDCARD_CHARACTER)) {
			targetTypeName = GENERIC_WILDCARD_CHARACTER;
		}
		// XXX hack
		if (knownMatches == null && importedPrefixes == null) {
			return innerMatchesExactly(targetTypeName, isAnonymous, isNested);
		}

		// if our pattern is length 1, then known matches are exact matches
		// if it's longer than that, then known matches are prefixes of a sort
		if (namePatterns.length == 1) {
			if (isAnonymous) {
				// we've already ruled out "*", and no other name pattern should match an anonymous type
				return false;
			}
			for (String knownMatch : knownMatches) {
				if (knownMatch.equals(targetTypeName)) {
					return true;
				}
			}
		} else {
			for (String knownMatch : knownMatches) {
				// String knownPrefix = knownMatches[i] + "$";
				// if (targetTypeName.startsWith(knownPrefix)) {
				if (targetTypeName.startsWith(knownMatch) && targetTypeName.length() > knownMatch.length()
						&& targetTypeName.charAt(knownMatch.length()) == '$') {
					int pos = lastIndexOfDotOrDollar(knownMatch);
					if (innerMatchesExactly(targetTypeName.substring(pos + 1), isAnonymous, isNested)) {
						return true;
					}
				}
			}
		}

		// if any prefixes match, strip the prefix and check that the rest matches
		// assumes that prefixes have a dot at the end
		for (String prefix : importedPrefixes) {
			// System.err.println("prefix match? " + prefix + " to " + targetTypeName);
			if (targetTypeName.startsWith(prefix)) {

				if (innerMatchesExactly(targetTypeName.substring(prefix.length()), isAnonymous, isNested)) {
					return true;
				}
			}
		}

		return innerMatchesExactly(targetTypeName, isAnonymous, isNested);
	}

	private int lastIndexOfDotOrDollar(String string) {
		for (int pos = string.length() - 1; pos > -1; pos--) {
			char ch = string.charAt(pos);
			if (ch == '.' || ch == '$') {
				return pos;
			}
		}
		return -1;
	}

	private boolean innerMatchesExactly(String s, boolean isAnonymous, boolean convertDollar /* isNested */) {

		List<char[]> ret = new ArrayList<>();
		int startIndex = 0;
		while (true) {
			int breakIndex = s.indexOf('.', startIndex); // what about /
			if (convertDollar && (breakIndex == -1)) {
				breakIndex = s.indexOf('$', startIndex); // we treat $ like . here
			}
			if (breakIndex == -1) {
				break;
			}
			char[] name = s.substring(startIndex, breakIndex).toCharArray();
			ret.add(name);
			startIndex = breakIndex + 1;
		}
		ret.add(s.substring(startIndex).toCharArray());

		int namesLength = ret.size();
		int patternsLength = namePatterns.length;

		int namesIndex = 0;
		int patternsIndex = 0;

		if ((!namePatterns[patternsLength - 1].isAny()) && isAnonymous) {
			return false;
		}

		if (ellipsisCount == 0) {
			if (namesLength != patternsLength) {
				return false;
			}
			while (patternsIndex < patternsLength) {
				if (!namePatterns[patternsIndex++].matches(ret.get(namesIndex++))) {
					return false;
				}
			}
			return true;
		} else if (ellipsisCount == 1) {
			if (namesLength < patternsLength - 1) {
				return false;
			}
			while (patternsIndex < patternsLength) {
				NamePattern p = namePatterns[patternsIndex++];
				if (p == NamePattern.ELLIPSIS) {
					namesIndex = namesLength - (patternsLength - patternsIndex);
				} else {
					if (!p.matches(ret.get(namesIndex++))) {
						return false;
					}
				}
			}
			return true;
		} else {
			// System.err.print("match(\"" + Arrays.asList(namePatterns) + "\", \"" + Arrays.asList(names) + "\") -> ");
			boolean b = outOfStar(namePatterns, ret.toArray(new char[ret.size()][]), 0, 0, patternsLength - ellipsisCount,
					namesLength, ellipsisCount);
			// System.err.println(b);
			return b;
		}
	}

	private static boolean outOfStar(final NamePattern[] pattern, final char[][] target, int pi, int ti, int pLeft, int tLeft,
			final int starsLeft) {
		if (pLeft > tLeft) {
			return false;
		}
		while (true) {
			// invariant: if (tLeft > 0) then (ti < target.length && pi < pattern.length)
			if (tLeft == 0) {
				return true;
			}
			if (pLeft == 0) {
				return (starsLeft > 0);
			}
			if (pattern[pi] == NamePattern.ELLIPSIS) {
				return inStar(pattern, target, pi + 1, ti, pLeft, tLeft, starsLeft - 1);
			}
			if (!pattern[pi].matches(target[ti])) {
				return false;
			}
			pi++;
			ti++;
			pLeft--;
			tLeft--;
		}
	}

	private static boolean inStar(final NamePattern[] pattern, final char[][] target, int pi, int ti, final int pLeft, int tLeft,
			int starsLeft) {
		// invariant: pLeft > 0, so we know we'll run out of stars and find a real char in pattern
		// of course, we probably can't parse multiple ..'s in a row, but this keeps the algorithm
		// exactly parallel with that in NamePattern
		NamePattern patternChar = pattern[pi];
		while (patternChar == NamePattern.ELLIPSIS) {
			starsLeft--;
			patternChar = pattern[++pi];
		}
		while (true) {
			// invariant: if (tLeft > 0) then (ti < target.length)
			if (pLeft > tLeft) {
				return false;
			}
			if (patternChar.matches(target[ti])) {
				if (outOfStar(pattern, target, pi + 1, ti + 1, pLeft - 1, tLeft - 1, starsLeft)) {
					return true;
				}
			}
			ti++;
			tLeft--;
		}
	}

	/**
	 * @see org.aspectj.weaver.patterns.TypePattern#matchesInstanceof(ResolvedType)
	 */
	@Override
	public FuzzyBoolean matchesInstanceof(ResolvedType type) {
		// XXX hack to let unmatched types just silently remain so
		if (maybeGetSimpleName() != null) {
			return FuzzyBoolean.NO;
		}

		type.getWorld().getMessageHandler().handleMessage(
				new Message("can't do instanceof matching on patterns with wildcards", IMessage.ERROR, null, getSourceLocation()));
		return FuzzyBoolean.NO;
	}

	public NamePattern extractName() {
		if (isIncludeSubtypes() || isVarArgs() || isArray() || (typeParameters.size() > 0)) {
			// we can't extract a name, the pattern is something like Foo+ and therefore
			// it is not ok to treat Foo as a method name!
			return null;
		}
		// System.err.println("extract from : " + Arrays.asList(namePatterns));
		int len = namePatterns.length;
		if (len == 1 && !annotationPattern.isAny()) {
			return null; // can't extract
		}
		NamePattern ret = namePatterns[len - 1];
		NamePattern[] newNames = new NamePattern[len - 1];
		System.arraycopy(namePatterns, 0, newNames, 0, len - 1);
		namePatterns = newNames;
		// System.err.println("    left : " + Arrays.asList(namePatterns));
		return ret;
	}

	/**
	 * Method maybeExtractName.
	 *
	 * @param string
	 * @return boolean
	 */
	public boolean maybeExtractName(String string) {
		int len = namePatterns.length;
		NamePattern ret = namePatterns[len - 1];
		String simple = ret.maybeGetSimpleName();
		if (simple != null && simple.equals(string)) {
			extractName();
			return true;
		}
		return false;
	}

	/**
	 * If this type pattern has no '.' or '*' in it, then return a simple string
	 *
	 * otherwise, this will return null;
	 */
	public String maybeGetSimpleName() {
		if (namePatterns.length == 1) {
			return namePatterns[0].maybeGetSimpleName();
		}
		return null;
	}

	/**
	 * If this type pattern has no '*' or '..' in it
	 */
	public String maybeGetCleanName() {
		if (namePatterns.length == 0) {
			throw new RuntimeException("bad name: " + namePatterns);
		}
		// System.out.println("get clean: " + this);
		StringBuilder buf = new StringBuilder();
		for (int i = 0, len = namePatterns.length; i < len; i++) {
			NamePattern p = namePatterns[i];
			String simpleName = p.maybeGetSimpleName();
			if (simpleName == null) {
				return null;
			}
			if (i > 0) {
				buf.append(".");
			}
			buf.append(simpleName);
		}
		// System.out.println(buf);
		return buf.toString();
	}

	@Override
	public TypePattern parameterizeWith(Map<String,UnresolvedType> typeVariableMap, World w) {
		NamePattern[] newNamePatterns = new NamePattern[namePatterns.length];
		System.arraycopy(namePatterns, 0, newNamePatterns, 0, namePatterns.length);
		if (newNamePatterns.length == 1) {
			String simpleName = newNamePatterns[0].maybeGetSimpleName();
			if (simpleName != null) {
				if (typeVariableMap.containsKey(simpleName)) {
					String newName = ((ReferenceType) typeVariableMap.get(simpleName)).getName().replace('$', '.');
					StringTokenizer strTok = new StringTokenizer(newName, ".");
					newNamePatterns = new NamePattern[strTok.countTokens()];
					int index = 0;
					while (strTok.hasMoreTokens()) {
						newNamePatterns[index++] = new NamePattern(strTok.nextToken());
					}
				}
			}
		}
		WildTypePattern ret = new WildTypePattern(newNamePatterns, includeSubtypes, dim, isVarArgs, typeParameters
				.parameterizeWith(typeVariableMap, w));
		ret.annotationPattern = this.annotationPattern.parameterizeWith(typeVariableMap, w);
		if (additionalInterfaceBounds == null) {
			ret.additionalInterfaceBounds = null;
		} else {
			ret.additionalInterfaceBounds = new TypePattern[additionalInterfaceBounds.length];
			for (int i = 0; i < additionalInterfaceBounds.length; i++) {
				ret.additionalInterfaceBounds[i] = additionalInterfaceBounds[i].parameterizeWith(typeVariableMap, w);
			}
		}
		ret.upperBound = upperBound != null ? upperBound.parameterizeWith(typeVariableMap, w) : null;
		ret.lowerBound = lowerBound != null ? lowerBound.parameterizeWith(typeVariableMap, w) : null;
		ret.isGeneric = isGeneric;
		ret.knownMatches = knownMatches;
		ret.importedPrefixes = importedPrefixes;
		ret.copyLocationFrom(this);
		return ret;
	}

	/**
	 * Need to determine if I'm really a pattern or a reference to a formal
	 *
	 * We may wish to further optimize the case of pattern vs. non-pattern
	 *
	 * We will be replaced by what we return
	 */
	@Override
	public TypePattern resolveBindings(IScope scope, Bindings bindings, boolean allowBinding, boolean requireExactType) {
		if (isNamePatternStar()) {
			TypePattern anyPattern = maybeResolveToAnyPattern(scope, bindings, allowBinding, requireExactType);
			if (anyPattern != null) {
				if (requireExactType) {
					scope.getWorld().getMessageHandler().handleMessage(
							MessageUtil.error(WeaverMessages.format(WeaverMessages.WILDCARD_NOT_ALLOWED), getSourceLocation()));
					return NO;
				} else {
					return anyPattern;
				}
			}
		}

		TypePattern bindingTypePattern = maybeResolveToBindingTypePattern(scope, bindings, allowBinding, requireExactType);
		if (bindingTypePattern != null) {
			return bindingTypePattern;
		}

		annotationPattern = annotationPattern.resolveBindings(scope, bindings, allowBinding);

		// resolve any type parameters
		if (typeParameters != null && typeParameters.size() > 0) {
			typeParameters.resolveBindings(scope, bindings, allowBinding, requireExactType);
			isGeneric = false;
		}

		// resolve any bounds
		if (upperBound != null) {
			upperBound = upperBound.resolveBindings(scope, bindings, allowBinding, requireExactType);
		}
		if (lowerBound != null) {
			lowerBound = lowerBound.resolveBindings(scope, bindings, allowBinding, requireExactType);
			// amc - additional interface bounds only needed if we support type vars again.
		}

		String fullyQualifiedName = maybeGetCleanName();
		if (fullyQualifiedName != null) {
			return resolveBindingsFromFullyQualifiedTypeName(fullyQualifiedName, scope, bindings, allowBinding, requireExactType);
		} else {
			if (requireExactType) {
				scope.getWorld().getMessageHandler().handleMessage(
						MessageUtil.error(WeaverMessages.format(WeaverMessages.WILDCARD_NOT_ALLOWED), getSourceLocation()));
				return NO;
			}
			importedPrefixes = scope.getImportedPrefixes();
			knownMatches = preMatch(scope.getImportedNames());
			return this; // pattern contains wildcards so can't be resolved to an ExactTypePattern...
			// XXX need to implement behavior for Lint.invalidWildcardTypeName
		}
	}

	private TypePattern maybeResolveToAnyPattern(IScope scope, Bindings bindings, boolean allowBinding, boolean requireExactType) {
		// If there is an annotation specified we have to
		// use a special variant of Any TypePattern called
		// AnyWithAnnotation
		if (annotationPattern == AnnotationTypePattern.ANY) {
			if (dim == 0 && !isVarArgs && upperBound == null && lowerBound == null
					&& (additionalInterfaceBounds == null || additionalInterfaceBounds.length == 0)) { // pr72531
				return TypePattern.ANY; // ??? loses source location
			}
		} else if (!isVarArgs) {
			annotationPattern = annotationPattern.resolveBindings(scope, bindings, allowBinding);
			AnyWithAnnotationTypePattern ret = new AnyWithAnnotationTypePattern(annotationPattern);
			ret.setLocation(sourceContext, start, end);
			return ret;
		}
		return null; // can't resolve to a simple "any" pattern
	}

	private TypePattern maybeResolveToBindingTypePattern(IScope scope, Bindings bindings, boolean allowBinding,
			boolean requireExactType) {
		String simpleName = maybeGetSimpleName();
		if (simpleName != null) {
			FormalBinding formalBinding = scope.lookupFormal(simpleName);
			if (formalBinding != null) {
				if (bindings == null) {
					scope.message(IMessage.ERROR, this, "negation doesn't allow binding");
					return this;
				}
				if (!allowBinding) {
					scope.message(IMessage.ERROR, this, "name binding only allowed in target, this, and args pcds");
					return this;
				}

				BindingTypePattern binding = new BindingTypePattern(formalBinding, isVarArgs);
				binding.copyLocationFrom(this);
				bindings.register(binding, scope);

				return binding;
			}
		}
		return null; // not possible to resolve to a binding type pattern
	}

	private TypePattern resolveBindingsFromFullyQualifiedTypeName(String fullyQualifiedName, IScope scope, Bindings bindings,
			boolean allowBinding, boolean requireExactType) {
		String originalName = fullyQualifiedName;
		ResolvedType resolvedTypeInTheWorld = null;
		UnresolvedType type;

		// System.out.println("resolve: " + cleanName);
		// ??? this loop has too many inefficiencies to count
		resolvedTypeInTheWorld = lookupTypeInWorldIncludingPrefixes(scope.getWorld(), fullyQualifiedName, scope
				.getImportedPrefixes());

		if (resolvedTypeInTheWorld.isGenericWildcard()) {
			type = resolvedTypeInTheWorld;
		} else {
			type = lookupTypeInScope(scope, fullyQualifiedName, this);
		}
		if ((type instanceof ResolvedType) && ((ResolvedType) type).isMissing()) {
			return resolveBindingsForMissingType(resolvedTypeInTheWorld, originalName, scope, bindings, allowBinding,
					requireExactType);
		} else {
			return resolveBindingsForExactType(scope, type, fullyQualifiedName, requireExactType);
		}
	}

	private UnresolvedType lookupTypeInScope(IScope scope, String typeName, IHasPosition location) {
		UnresolvedType type = null;
		while (ResolvedType.isMissing(type = scope.lookupType(typeName, location))) {
			int lastDot = typeName.lastIndexOf('.');
			if (lastDot == -1) {
				break;
			}
			typeName = typeName.substring(0, lastDot) + '$' + typeName.substring(lastDot + 1);
		}
		return type;
	}

	/**
	 * Searches the world for the ResolvedType with the given typeName. If one isn't found then for each of the supplied prefixes,
	 * it prepends the typeName with the prefix and searches the world for the ResolvedType with this new name. If one still isn't
	 * found then a MissingResolvedTypeWithKnownSignature is returned with the originally requested typeName (this ensures the
	 * typeName makes sense).
	 */
	private ResolvedType lookupTypeInWorldIncludingPrefixes(World world, String typeName, String[] prefixes) {
		ResolvedType ret = lookupTypeInWorld(world, typeName);
		if (!ret.isMissing()) {
			return ret;
		}
		ResolvedType retWithPrefix = ret;
		int counter = 0;
		while (retWithPrefix.isMissing() && (counter < prefixes.length)) {
			retWithPrefix = lookupTypeInWorld(world, prefixes[counter] + typeName);
			counter++;
		}
		if (!retWithPrefix.isMissing()) {
			return retWithPrefix;
		}
		return ret;
	}

	private ResolvedType lookupTypeInWorld(World world, String typeName) {
		UnresolvedType ut = UnresolvedType.forName(typeName);
		ResolvedType ret = world.resolve(ut, true);
		while (ret.isMissing()) {
			int lastDot = typeName.lastIndexOf('.');
			if (lastDot == -1) {
				break;
			}
			typeName = typeName.substring(0, lastDot) + '$' + typeName.substring(lastDot + 1);
			ret = world.resolve(UnresolvedType.forName(typeName), true);
		}
		return ret;
	}

	private TypePattern resolveBindingsForExactType(IScope scope, UnresolvedType aType, String fullyQualifiedName,
			boolean requireExactType) {
		TypePattern ret = null;
		if (aType.isTypeVariableReference()) {
			// we have to set the bounds on it based on the bounds of this pattern
			ret = resolveBindingsForTypeVariable(scope, (UnresolvedTypeVariableReferenceType) aType);
		} else if (typeParameters.size() > 0) {
			ret = resolveParameterizedType(scope, aType, requireExactType);
		} else if (upperBound != null || lowerBound != null) {
			// this must be a generic wildcard with bounds
			ret = resolveGenericWildcard(scope, aType);
		} else {
			if (dim != 0) {
				aType = UnresolvedType.makeArray(aType, dim);
			}
			ret = new ExactTypePattern(aType, includeSubtypes, isVarArgs, typeParameters);
		}
		ret.setAnnotationTypePattern(annotationPattern);
		ret.copyLocationFrom(this);
		return ret;
	}

	private TypePattern resolveGenericWildcard(IScope scope, UnresolvedType aType) {
		if (!aType.getSignature().equals(GENERIC_WILDCARD_SIGNATURE_CHARACTER)) {
			throw new IllegalStateException("Can only have bounds for a generic wildcard");
		}
		boolean canBeExact = true;
		if ((upperBound != null) && ResolvedType.isMissing(upperBound.getExactType())) {
			canBeExact = false;
		}
		if ((lowerBound != null) && ResolvedType.isMissing(lowerBound.getExactType())) {
			canBeExact = false;
		}
		if (canBeExact) {
			ResolvedType type = null;
			if (upperBound != null) {
				if (upperBound.isIncludeSubtypes()) {
					canBeExact = false;
				} else {
					ReferenceType upper = (ReferenceType) upperBound.getExactType().resolve(scope.getWorld());
					type = new BoundedReferenceType(upper, true, scope.getWorld());
				}
			} else {
				if (lowerBound.isIncludeSubtypes()) {
					canBeExact = false;
				} else {
					ReferenceType lower = (ReferenceType) lowerBound.getExactType().resolve(scope.getWorld());
					type = new BoundedReferenceType(lower, false, scope.getWorld());
				}
			}
			if (canBeExact) {
				// might have changed if we find out include subtypes is set on one of the bounds...
				return new ExactTypePattern(type, includeSubtypes, isVarArgs, typeParameters);
			}
		}

		// we weren't able to resolve to an exact type pattern...
		// leave as wild type pattern
		importedPrefixes = scope.getImportedPrefixes();
		knownMatches = preMatch(scope.getImportedNames());
		return this;
	}

	private TypePattern resolveParameterizedType(IScope scope, UnresolvedType aType, boolean requireExactType) {
		ResolvedType rt = aType.resolve(scope.getWorld());
		if (!verifyTypeParameters(rt, scope, requireExactType)) {
			return TypePattern.NO; // messages already isued
		}
		// Only if the type is exact *and* the type parameters are exact should we create an
		// ExactTypePattern for this WildTypePattern
		if (typeParameters.areAllExactWithNoSubtypesAllowed()) {
			TypePattern[] typePats = typeParameters.getTypePatterns();
			UnresolvedType[] typeParameterTypes = new UnresolvedType[typePats.length];
			for (int i = 0; i < typeParameterTypes.length; i++) {
				typeParameterTypes[i] = ((ExactTypePattern) typePats[i]).getExactType();
			}
			// rt could be a parameterized type 156058
			if (rt.isParameterizedType()) {
				rt = rt.getGenericType();
			}
			ResolvedType type = TypeFactory.createParameterizedType(rt, typeParameterTypes, scope.getWorld());
			if (isGeneric) {
				type = type.getGenericType();
			}
			// UnresolvedType tx = UnresolvedType.forParameterizedTypes(aType,typeParameterTypes);
			// UnresolvedType type = scope.getWorld().resolve(tx,true);
			if (dim != 0) {
				type = ResolvedType.makeArray(type, dim);
			}
			return new ExactTypePattern(type, includeSubtypes, isVarArgs, typeParameters);
		} else {
			// AMC... just leave it as a wild type pattern then?
			importedPrefixes = scope.getImportedPrefixes();
			knownMatches = preMatch(scope.getImportedNames());
			return this;
		}
	}

	private TypePattern resolveBindingsForMissingType(ResolvedType typeFoundInWholeWorldSearch, String nameWeLookedFor,
			IScope scope, Bindings bindings, boolean allowBinding, boolean requireExactType) {
		if (requireExactType) {
			if (!allowBinding) {
				scope.getWorld().getMessageHandler().handleMessage(
						MessageUtil.error(WeaverMessages.format(WeaverMessages.CANT_BIND_TYPE, nameWeLookedFor),
								getSourceLocation()));
			} else if (scope.getWorld().getLint().invalidAbsoluteTypeName.isEnabled()) {
				scope.getWorld().getLint().invalidAbsoluteTypeName.signal(nameWeLookedFor, getSourceLocation());
			}
			return NO;
		} else if (scope.getWorld().getLint().invalidAbsoluteTypeName.isEnabled()) {
			// Only put the lint warning out if we can't find it in the world
			if (typeFoundInWholeWorldSearch.isMissing()) {
				scope.getWorld().getLint().invalidAbsoluteTypeName.signal(nameWeLookedFor, getSourceLocation());
				this.failedResolution = true;
			}
		}
		importedPrefixes = scope.getImportedPrefixes();
		knownMatches = preMatch(scope.getImportedNames());
		return this;
	}

	/**
	 * We resolved the type to a type variable declared in the pointcut designator. Now we have to create either an exact type
	 * pattern or a wild type pattern for it, with upper and lower bounds set accordingly. XXX none of this stuff gets serialized
	 * yet
	 *
	 * @param scope
	 * @param tvrType
	 * @return
	 */
	private TypePattern resolveBindingsForTypeVariable(IScope scope, UnresolvedTypeVariableReferenceType tvrType) {
		Bindings emptyBindings = new Bindings(0);
		if (upperBound != null) {
			upperBound = upperBound.resolveBindings(scope, emptyBindings, false, false);
		}
		if (lowerBound != null) {
			lowerBound = lowerBound.resolveBindings(scope, emptyBindings, false, false);
		}
		if (additionalInterfaceBounds != null) {
			TypePattern[] resolvedIfBounds = new TypePattern[additionalInterfaceBounds.length];
			for (int i = 0; i < resolvedIfBounds.length; i++) {
				resolvedIfBounds[i] = additionalInterfaceBounds[i].resolveBindings(scope, emptyBindings, false, false);
			}
			additionalInterfaceBounds = resolvedIfBounds;
		}
		if (upperBound == null && lowerBound == null && additionalInterfaceBounds == null) {
			// no bounds to worry about...
			ResolvedType rType = tvrType.resolve(scope.getWorld());
			if (dim != 0) {
				rType = ResolvedType.makeArray(rType, dim);
			}
			return new ExactTypePattern(rType, includeSubtypes, isVarArgs, typeParameters);
		} else {
			// we have to set bounds on the TypeVariable held by tvrType before resolving it
			boolean canCreateExactTypePattern = true;
			if (upperBound != null && ResolvedType.isMissing(upperBound.getExactType())) {
				canCreateExactTypePattern = false;
			}
			if (lowerBound != null && ResolvedType.isMissing(lowerBound.getExactType())) {
				canCreateExactTypePattern = false;
			}
			if (additionalInterfaceBounds != null) {
				for (TypePattern additionalInterfaceBound : additionalInterfaceBounds) {
					if (ResolvedType.isMissing(additionalInterfaceBound.getExactType())) {
						canCreateExactTypePattern = false;
					}
				}
			}
			if (canCreateExactTypePattern) {
				TypeVariable tv = tvrType.getTypeVariable();
				if (upperBound != null) {
					tv.setSuperclass(upperBound.getExactType());
				}
				if (additionalInterfaceBounds != null) {
					UnresolvedType[] ifBounds = new UnresolvedType[additionalInterfaceBounds.length];
					for (int i = 0; i < ifBounds.length; i++) {
						ifBounds[i] = additionalInterfaceBounds[i].getExactType();
					}
					tv.setAdditionalInterfaceBounds(ifBounds);
				}
				ResolvedType rType = tvrType.resolve(scope.getWorld());
				if (dim != 0) {
					rType = ResolvedType.makeArray(rType, dim);
				}
				return new ExactTypePattern(rType, includeSubtypes, isVarArgs, typeParameters);
			}
			return this; // leave as wild type pattern then
		}
	}

	/**
	 * When this method is called, we have resolved the base type to an exact type. We also have a set of type patterns for the
	 * parameters. Time to perform some basic checks: - can the base type be parameterized? (is it generic) - can the type parameter
	 * pattern list match the number of parameters on the base type - do all parameter patterns meet the bounds of the respective
	 * type variables If any of these checks fail, a warning message is issued and we return false.
	 *
	 * @return
	 */
	private boolean verifyTypeParameters(ResolvedType baseType, IScope scope, boolean requireExactType) {
		ResolvedType genericType = baseType.getGenericType();
		if (genericType == null) {
			// issue message "does not match because baseType.getName() is not generic"
			scope.message(MessageUtil.warn(WeaverMessages.format(WeaverMessages.NOT_A_GENERIC_TYPE, baseType.getName()),
					getSourceLocation()));
			return false;
		}
		int minRequiredTypeParameters = typeParameters.size();
		boolean foundEllipsis = false;
		TypePattern[] typeParamPatterns = typeParameters.getTypePatterns();
		for (TypePattern typeParamPattern : typeParamPatterns) {
			if (typeParamPattern instanceof WildTypePattern) {
				WildTypePattern wtp = (WildTypePattern) typeParamPattern;
				if (wtp.ellipsisCount > 0) {
					foundEllipsis = true;
					minRequiredTypeParameters--;
				}
			}
		}
		TypeVariable[] tvs = genericType.getTypeVariables();
		if ((tvs.length < minRequiredTypeParameters) || (!foundEllipsis && minRequiredTypeParameters != tvs.length)) {
			// issue message "does not match because wrong no of type params"
			String msg = WeaverMessages.format(WeaverMessages.INCORRECT_NUMBER_OF_TYPE_ARGUMENTS, genericType.getName(),
					tvs.length);
			if (requireExactType) {
				scope.message(MessageUtil.error(msg, getSourceLocation()));
			} else {
				scope.message(MessageUtil.warn(msg, getSourceLocation()));
			}
			return false;
		}

		// now check that each typeParameter pattern, if exact, matches the bounds
		// of the type variable.

		// pr133307 - delay verification until type binding completion, these next few lines replace
		// the call to checkBoundsOK
		if (!boundscheckingoff) {
			VerifyBoundsForTypePattern verification = new VerifyBoundsForTypePattern(scope, genericType, requireExactType,
					typeParameters, getSourceLocation());
			scope.getWorld().getCrosscuttingMembersSet().recordNecessaryCheck(verification);
		}
		// return checkBoundsOK(scope,genericType,requireExactType);

		return true;
	}

	/**
	 * By capturing the verification in this class, rather than performing it in verifyTypeParameters(), we can cope with situations
	 * where the interactions between generics and declare parents would otherwise cause us problems. For example, if verifying as
	 * we go along we may report a problem which would have been fixed by a declare parents that we haven't looked at yet. If we
	 * create and store a verification object, we can verify this later when the type system is considered 'complete'
	 */
	static class VerifyBoundsForTypePattern implements IVerificationRequired {

		private final IScope scope;
		private final ResolvedType genericType;
		private final boolean requireExactType;
		private TypePatternList typeParameters = TypePatternList.EMPTY;
		private final ISourceLocation sLoc;

		public VerifyBoundsForTypePattern(IScope scope, ResolvedType genericType, boolean requireExactType,
				TypePatternList typeParameters, ISourceLocation sLoc) {
			this.scope = scope;
			this.genericType = genericType;
			this.requireExactType = requireExactType;
			this.typeParameters = typeParameters;
			this.sLoc = sLoc;
		}

		public void verify() {
			TypeVariable[] tvs = genericType.getTypeVariables();
			TypePattern[] typeParamPatterns = typeParameters.getTypePatterns();
			if (typeParameters.areAllExactWithNoSubtypesAllowed()) {
				for (int i = 0; i < tvs.length; i++) {
					UnresolvedType ut = typeParamPatterns[i].getExactType();
					boolean continueCheck = true;
					// FIXME asc dont like this but ok temporary measure. If the type parameter
					// is itself a type variable (from the generic aspect) then assume it'll be
					// ok... (see pr112105) Want to break this? Run GenericAspectK test.
					if (ut.isTypeVariableReference()) {
						continueCheck = false;
					}

					// System.err.println("Verifying "+ut.getName()+" meets bounds for "+tvs[i]);
					if (continueCheck && !tvs[i].canBeBoundTo(ut.resolve(scope.getWorld()))) {
						// issue message that type parameter does not meet specification
						String parameterName = ut.getName();
						if (ut.isTypeVariableReference()) {
							parameterName = ((TypeVariableReference) ut).getTypeVariable().getDisplayName();
						}
						String msg = WeaverMessages.format(WeaverMessages.VIOLATES_TYPE_VARIABLE_BOUNDS, parameterName,
								i + 1, tvs[i].getDisplayName(), genericType.getName());
						if (requireExactType) {
							scope.message(MessageUtil.error(msg, sLoc));
						} else {
							scope.message(MessageUtil.warn(msg, sLoc));
						}
					}
				}
			}
		}
	}

	// pr133307 - moved to verification object
	// public boolean checkBoundsOK(IScope scope,ResolvedType genericType,boolean requireExactType) {
	// if (boundscheckingoff) return true;
	// TypeVariable[] tvs = genericType.getTypeVariables();
	// TypePattern[] typeParamPatterns = typeParameters.getTypePatterns();
	// if (typeParameters.areAllExactWithNoSubtypesAllowed()) {
	// for (int i = 0; i < tvs.length; i++) {
	// UnresolvedType ut = typeParamPatterns[i].getExactType();
	// boolean continueCheck = true;
	// // FIXME asc dont like this but ok temporary measure. If the type parameter
	// // is itself a type variable (from the generic aspect) then assume it'll be
	// // ok... (see pr112105) Want to break this? Run GenericAspectK test.
	// if (ut.isTypeVariableReference()) {
	// continueCheck = false;
	// }
	//
	// if (continueCheck &&
	// !tvs[i].canBeBoundTo(ut.resolve(scope.getWorld()))) {
	// // issue message that type parameter does not meet specification
	// String parameterName = ut.getName();
	// if (ut.isTypeVariableReference()) parameterName = ((TypeVariableReference)ut).getTypeVariable().getDisplayName();
	// String msg =
	// WeaverMessages.format(
	// WeaverMessages.VIOLATES_TYPE_VARIABLE_BOUNDS,
	// parameterName,
	// new Integer(i+1),
	// tvs[i].getDisplayName(),
	// genericType.getName());
	// if (requireExactType) scope.message(MessageUtil.error(msg,getSourceLocation()));
	// else scope.message(MessageUtil.warn(msg,getSourceLocation()));
	// return false;
	// }
	// }
	// }
	// return true;
	// }

	@Override
	public boolean isStar() {
		boolean annPatternStar = annotationPattern == AnnotationTypePattern.ANY;
		return (isNamePatternStar() && annPatternStar && dim == 0);
	}

	private boolean isNamePatternStar() {
		return namePatterns.length == 1 && namePatterns[0].isAny();
	}

	/**
	 * @return those possible matches which I match exactly the last element of
	 */
	private String[] preMatch(String[] possibleMatches) {
		// if (namePatterns.length != 1) return CollectionUtil.NO_STRINGS;

		List<String> ret = new ArrayList<>();
		for (String possibleMatch : possibleMatches) {
			char[][] names = splitNames(possibleMatch, true); // ??? not most efficient
			if (namePatterns[0].matches(names[names.length - 1])) {
				ret.add(possibleMatch);
				continue;
			}
			if (possibleMatch.contains("$")) {
				names = splitNames(possibleMatch, false); // ??? not most efficient
				if (namePatterns[0].matches(names[names.length - 1])) {
					ret.add(possibleMatch);
				}
			}
		}
		return ret.toArray(new String[0]);
	}

	// public void postRead(ResolvedType enclosingType) {
	// this.importedPrefixes = enclosingType.getImportedPrefixes();
	// this.knownNames = prematch(enclosingType.getImportedNames());
	// }

	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		if (annotationPattern != AnnotationTypePattern.ANY) {
			buf.append('(');
			buf.append(annotationPattern);
			buf.append(' ');
		}
		for (int i = 0, len = namePatterns.length; i < len; i++) {
			NamePattern name = namePatterns[i];
			if (name == null) {
				buf.append(".");
			} else {
				if (i > 0) {
					buf.append(".");
				}
				buf.append(name);
			}
		}
		if (upperBound != null) {
			buf.append(" extends ");
			buf.append(upperBound);
		}
		if (lowerBound != null) {
			buf.append(" super ");
			buf.append(lowerBound);
		}
		if (typeParameters != null && typeParameters.size() != 0) {
			buf.append("<");
			buf.append(typeParameters.toString().replaceFirst("^.(.*).$", "$1"));
			buf.append(">");
		}
		if (includeSubtypes) {
			buf.append('+');
		}
		for (int i = 0; i < getDimensions(); i++) {
			buf.append("[]");
		}
		if (isVarArgs) {
			buf.append("...");
		}
		if (annotationPattern != AnnotationTypePattern.ANY) {
			buf.append(')');
		}
		return buf.toString();
	}

	@Override
	public boolean equals(Object other) {
		if (!(other instanceof WildTypePattern)) {
			return false;
		}
		WildTypePattern o = (WildTypePattern) other;
		int len = o.namePatterns.length;
		if (len != this.namePatterns.length) {
			return false;
		}
		if (this.includeSubtypes != o.includeSubtypes) {
			return false;
		}
		if (this.dim != o.dim) {
			return false;
		}
		if (this.isVarArgs != o.isVarArgs) {
			return false;
		}
		if (this.upperBound != null) {
			if (o.upperBound == null) {
				return false;
			}
			if (!this.upperBound.equals(o.upperBound)) {
				return false;
			}
		} else {
			if (o.upperBound != null) {
				return false;
			}
		}
		if (this.lowerBound != null) {
			if (o.lowerBound == null) {
				return false;
			}
			if (!this.lowerBound.equals(o.lowerBound)) {
				return false;
			}
		} else {
			if (o.lowerBound != null) {
				return false;
			}
		}
		if (!typeParameters.equals(o.typeParameters)) {
			return false;
		}
		for (int i = 0; i < len; i++) {
			if (!o.namePatterns[i].equals(this.namePatterns[i])) {
				return false;
			}
		}
		return (o.annotationPattern.equals(this.annotationPattern));
	}

	@Override
	public int hashCode() {
		int result = 17;
		for (NamePattern namePattern : namePatterns) {
			result = 37 * result + namePattern.hashCode();
		}
		result = 37 * result + (includeSubtypes ? 1 : 0);
		result = 37 * result + dim;
		result = 37 * result + (isVarArgs ? 1 : 0);
		result = 37 * result + annotationPattern.hashCode();
		result = 37 * result + typeParameters.hashCode();
		if (upperBound != null) {
			result = 37 * result + upperBound.hashCode();
		}
		if (lowerBound != null) {
			result = 37 * result + lowerBound.hashCode();
		}
		return result;
	}

	private static final byte VERSION = 1; // rev on change

	@Override
	public void write(CompressingDataOutputStream s) throws IOException {
		s.writeByte(TypePattern.WILD);
		s.writeByte(VERSION);
		s.writeShort(namePatterns.length);
		for (NamePattern namePattern : namePatterns) {
			namePattern.write(s);
		}
		s.writeBoolean(includeSubtypes);
		s.writeInt(dim);
		s.writeBoolean(isVarArgs);
		typeParameters.write(s); // ! change from M2
		// ??? storing this information with every type pattern is wasteful of .class
		// file size. Storing it on enclosing types would be more efficient
		FileUtil.writeStringArray(knownMatches, s);
		FileUtil.writeStringArray(importedPrefixes, s);
		writeLocation(s);
		annotationPattern.write(s);
		// generics info, new in M3
		s.writeBoolean(isGeneric);
		s.writeBoolean(upperBound != null);
		if (upperBound != null) {
			upperBound.write(s);
		}
		s.writeBoolean(lowerBound != null);
		if (lowerBound != null) {
			lowerBound.write(s);
		}
		s.writeInt(additionalInterfaceBounds == null ? 0 : additionalInterfaceBounds.length);
		if (additionalInterfaceBounds != null) {
			for (TypePattern additionalInterfaceBound : additionalInterfaceBounds) {
				additionalInterfaceBound.write(s);
			}
		}
	}

	public static TypePattern read(VersionedDataInputStream s, ISourceContext context) throws IOException {
		if (s.getMajorVersion() >= AjAttribute.WeaverVersionInfo.WEAVER_VERSION_MAJOR_AJ150) {
			return readTypePattern150(s, context);
		} else {
			return readTypePatternOldStyle(s, context);
		}
	}

	public static TypePattern readTypePattern150(VersionedDataInputStream s, ISourceContext context) throws IOException {
		byte version = s.readByte();
		if (version > VERSION) {
			throw new BCException("WildTypePattern was written by a more recent version of AspectJ, cannot read");
		}
		int len = s.readShort();
		NamePattern[] namePatterns = new NamePattern[len];
		for (int i = 0; i < len; i++) {
			namePatterns[i] = NamePattern.read(s);
		}
		boolean includeSubtypes = s.readBoolean();
		int dim = s.readInt();
		boolean varArg = s.readBoolean();
		TypePatternList typeParams = TypePatternList.read(s, context);
		WildTypePattern ret = new WildTypePattern(namePatterns, includeSubtypes, dim, varArg, typeParams);
		ret.knownMatches = FileUtil.readStringArray(s);
		ret.importedPrefixes = FileUtil.readStringArray(s);
		ret.readLocation(context, s);
		ret.setAnnotationTypePattern(AnnotationTypePattern.read(s, context));
		// generics info, new in M3
		ret.isGeneric = s.readBoolean();
		if (s.readBoolean()) {
			ret.upperBound = TypePattern.read(s, context);
		}
		if (s.readBoolean()) {
			ret.lowerBound = TypePattern.read(s, context);
		}
		int numIfBounds = s.readInt();
		if (numIfBounds > 0) {
			ret.additionalInterfaceBounds = new TypePattern[numIfBounds];
			for (int i = 0; i < numIfBounds; i++) {
				ret.additionalInterfaceBounds[i] = TypePattern.read(s, context);
			}
		}
		return ret;
	}

	public static TypePattern readTypePatternOldStyle(VersionedDataInputStream s, ISourceContext context) throws IOException {
		int len = s.readShort();
		NamePattern[] namePatterns = new NamePattern[len];
		for (int i = 0; i < len; i++) {
			namePatterns[i] = NamePattern.read(s);
		}
		boolean includeSubtypes = s.readBoolean();
		int dim = s.readInt();
		WildTypePattern ret = new WildTypePattern(namePatterns, includeSubtypes, dim, false, null);
		ret.knownMatches = FileUtil.readStringArray(s);
		ret.importedPrefixes = FileUtil.readStringArray(s);
		ret.readLocation(context, s);
		return ret;
	}

	@Override
	public Object accept(PatternNodeVisitor visitor, Object data) {
		return visitor.visit(this, data);
	}

	public boolean hasFailedResolution() {
		return failedResolution;
	}

}
