/* *******************************************************************
 * 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
 *     Alexandre Vasseur    perClause support for @AJ aspects
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.ClassParser;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.generic.FieldInstruction;
import org.aspectj.apache.bcel.generic.INVOKEINTERFACE;
import org.aspectj.apache.bcel.generic.Instruction;
import org.aspectj.apache.bcel.generic.InstructionHandle;
import org.aspectj.apache.bcel.generic.InvokeInstruction;
import org.aspectj.apache.bcel.generic.MULTIANEWARRAY;
import org.aspectj.apache.bcel.generic.ObjectType;
import org.aspectj.apache.bcel.generic.Type;
import org.aspectj.apache.bcel.util.ClassLoaderReference;
import org.aspectj.apache.bcel.util.ClassLoaderRepository;
import org.aspectj.apache.bcel.util.ClassPath;
import org.aspectj.apache.bcel.util.NonCachingClassLoaderRepository;
import org.aspectj.apache.bcel.util.Repository;
import org.aspectj.asm.AsmManager;
import org.aspectj.asm.IRelationship;
import org.aspectj.asm.internal.CharOperation;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.Message;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.bridge.WeaveMessage;
import org.aspectj.weaver.Advice;
import org.aspectj.weaver.AdviceKind;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.AnnotationOnTypeMunger;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.Checker;
import org.aspectj.weaver.ICrossReferenceHandler;
import org.aspectj.weaver.IWeavingSupport;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.MemberImpl;
import org.aspectj.weaver.MemberKind;
import org.aspectj.weaver.NewParentTypeMunger;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ReferenceTypeDelegate;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.ResolvedTypeMunger;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.loadtime.definition.Definition;
import org.aspectj.weaver.loadtime.definition.DocumentParser;
import org.aspectj.weaver.model.AsmRelationshipProvider;
import org.aspectj.weaver.patterns.DeclareAnnotation;
import org.aspectj.weaver.patterns.DeclareParents;
import org.aspectj.weaver.patterns.ParserException;
import org.aspectj.weaver.patterns.PatternParser;
import org.aspectj.weaver.patterns.TypePattern;
import org.aspectj.weaver.tools.Trace;
import org.aspectj.weaver.tools.TraceFactory;

public class BcelWorld extends World implements Repository {

	private final ClassPathManager classPath;
	protected Repository delegate;
	private BcelWeakClassLoaderReference loaderRef;
	private final BcelWeavingSupport bcelWeavingSupport = new BcelWeavingSupport();
	private boolean isXmlConfiguredWorld = false;
	private WeavingXmlConfig xmlConfiguration;
	private List<TypeDelegateResolver> typeDelegateResolvers;

	private static Trace trace = TraceFactory.getTraceFactory().getTrace(BcelWorld.class);

	public BcelWorld() {
		this("");
	}

	public BcelWorld(String cp) {
		this(makeDefaultClasspath(cp), IMessageHandler.THROW, null);
	}

	public IRelationship.Kind determineRelKind(ShadowMunger munger) {
		AdviceKind ak = ((Advice) munger).getKind();
		if (ak.getKey() == AdviceKind.Before.getKey()) {
			return IRelationship.Kind.ADVICE_BEFORE;
		} else if (ak.getKey() == AdviceKind.After.getKey()) {
			return IRelationship.Kind.ADVICE_AFTER;
		} else if (ak.getKey() == AdviceKind.AfterThrowing.getKey()) {
			return IRelationship.Kind.ADVICE_AFTERTHROWING;
		} else if (ak.getKey() == AdviceKind.AfterReturning.getKey()) {
			return IRelationship.Kind.ADVICE_AFTERRETURNING;
		} else if (ak.getKey() == AdviceKind.Around.getKey()) {
			return IRelationship.Kind.ADVICE_AROUND;
		} else if (ak.getKey() == AdviceKind.CflowEntry.getKey() || ak.getKey() == AdviceKind.CflowBelowEntry.getKey()
				|| ak.getKey() == AdviceKind.InterInitializer.getKey() || ak.getKey() == AdviceKind.PerCflowEntry.getKey()
				|| ak.getKey() == AdviceKind.PerCflowBelowEntry.getKey() || ak.getKey() == AdviceKind.PerThisEntry.getKey()
				|| ak.getKey() == AdviceKind.PerTargetEntry.getKey() || ak.getKey() == AdviceKind.Softener.getKey()
				|| ak.getKey() == AdviceKind.PerTypeWithinEntry.getKey()) {
			// System.err.println("Dont want a message about this: "+ak);
			return null;
		}
		throw new RuntimeException("Shadow.determineRelKind: What the hell is it? " + ak);
	}

	@Override
	public void reportMatch(ShadowMunger munger, Shadow shadow) {
		if (getCrossReferenceHandler() != null) {
			final IRelationship.Kind kind = determineRelKind(munger);
			getCrossReferenceHandler().addCrossReference(
				munger.getSourceLocation(),           // What is being applied?
				shadow.getSourceLocation(),           // Where is it being applied?
				kind == null ? null : kind.getName(), // What kind of advice?
				((Advice) munger).hasDynamicTests()   // Is a runtime test being stuffed in the code?
			);
		}

		if (!getMessageHandler().isIgnoring(IMessage.WEAVEINFO)) {
			reportWeavingMessage(munger, shadow);
		}

		if (getModel() != null) {
			AsmRelationshipProvider.addAdvisedRelationship(getModelAsAsmManager(), shadow, munger);
		}
	}

	/*
	 * Report a message about the advice weave that has occurred. Some messing about to make it pretty ! This code is just asking
	 * for an NPE to occur ...
	 */
	private void reportWeavingMessage(ShadowMunger munger, Shadow shadow) {
		Advice advice = (Advice) munger;
		AdviceKind aKind = advice.getKind();
		// Only report on interesting advice kinds ...
		if (aKind == null || advice.getConcreteAspect() == null) {
			// We suspect someone is programmatically driving the weaver
			// (e.g. IdWeaveTestCase in the weaver testcases)
			return;
		}
		if (!(aKind.equals(AdviceKind.Before) || aKind.equals(AdviceKind.After) || aKind.equals(AdviceKind.AfterReturning)
				|| aKind.equals(AdviceKind.AfterThrowing) || aKind.equals(AdviceKind.Around) || aKind.equals(AdviceKind.Softener))) {
			return;
		}

		// synchronized blocks are implemented with multiple monitor_exit instructions in the bytecode
		// (one for normal exit from the method, one for abnormal exit), we only want to tell the user
		// once we have advised the end of the sync block, even though under the covers we will have
		// woven both exit points
		if (shadow.getKind() == Shadow.SynchronizationUnlock) {
			if (advice.lastReportedMonitorExitJoinpointLocation == null) {
				// this is the first time through, let's continue...
				advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
			} else {
				if (areTheSame(shadow.getSourceLocation(), advice.lastReportedMonitorExitJoinpointLocation)) {
					// Don't report it again!
					advice.lastReportedMonitorExitJoinpointLocation = null;
					return;
				}
				// hmmm, this means some kind of nesting is going on, urgh
				advice.lastReportedMonitorExitJoinpointLocation = shadow.getSourceLocation();
			}
		}

		String description = advice.getKind().toString();
		String advisedType = shadow.getEnclosingType().getName();
		String advisingType = advice.getConcreteAspect().getName();
		Message msg = null;
		if (advice.getKind().equals(AdviceKind.Softener)) {
			msg = WeaveMessage.constructWeavingMessage(
				WeaveMessage.WEAVEMESSAGE_SOFTENS,
				new String[] {
					advisedType, beautifyLocation(shadow.getSourceLocation()),
					advisingType, beautifyLocation(munger.getSourceLocation())
				},
				advisedType, advisingType,
				shadow.getSourceLocation(), munger.getSourceLocation()
			);
		}
		else {
			boolean runtimeTest = advice.hasDynamicTests();
			String joinPointDescription = shadow.toString();
			msg = WeaveMessage.constructWeavingMessage(
				WeaveMessage.WEAVEMESSAGE_ADVISES,
				new String[] {
					joinPointDescription,
					advisedType, beautifyLocation(shadow.getSourceLocation()),
					description,
					advisingType, beautifyLocation(munger.getSourceLocation()),
					(runtimeTest ? " [with runtime test]" : "")
				},
				advisedType, advisingType,
				shadow.getSourceLocation(), munger.getSourceLocation()
			);
			// Boolean.toString(runtimeTest)});
		}
		getMessageHandler().handleMessage(msg);
	}

	private boolean areTheSame(ISourceLocation locA, ISourceLocation locB) {
		if (locA == null) {
			return locB == null;
		}
		if (locB == null) {
			return false;
		}
		if (locA.getLine() != locB.getLine()) {
			return false;
		}
		File fA = locA.getSourceFile();
		File fB = locA.getSourceFile();
		if (fA == null) {
			return fB == null;
		}
		if (fB == null) {
			return false;
		}
		return fA.getName().equals(fB.getName());
	}

	/*
	 * Ensure we report a nice source location - particular in the case where the source info is missing (binary weave).
	 */
	private String beautifyLocation(ISourceLocation isl) {
		StringBuilder nice = new StringBuilder();
		if (isl == null || isl.getSourceFile() == null || isl.getSourceFile().getName().contains("no debug info available")) {
			nice.append("no debug info available");
		} else {
			// can't use File.getName() as this fails when a Linux box encounters a path created on Windows and vice-versa
			int takeFrom = isl.getSourceFile().getPath().lastIndexOf('/');
			if (takeFrom == -1) {
				takeFrom = isl.getSourceFile().getPath().lastIndexOf('\\');
			}
			int binary = isl.getSourceFile().getPath().lastIndexOf('!');
			if (binary != -1 && binary < takeFrom) {
				// we have been woven by a binary aspect
				String pathToBinaryLoc = isl.getSourceFile().getPath().substring(0, binary + 1);
				if (pathToBinaryLoc.contains(".jar")) {
					// only want to add the extra info if we're from a jar file
					int lastSlash = pathToBinaryLoc.lastIndexOf('/');
					if (lastSlash == -1) {
						lastSlash = pathToBinaryLoc.lastIndexOf('\\');
					}
					nice.append(pathToBinaryLoc.substring(lastSlash + 1));
				}
			}
			nice.append(isl.getSourceFile().getPath().substring(takeFrom + 1));
			if (isl.getLine() != 0) {
				nice.append(":").append(isl.getLine());
			}
			// if it's a binary file then also want to give the file name
			if (isl.getSourceFileName() != null) {
				nice.append("(from ").append(isl.getSourceFileName()).append(")");
			}
		}
		return nice.toString();
	}

	private static List<String> makeDefaultClasspath(String cp) {
		List<String> classPath = new ArrayList<>();
		classPath.addAll(getPathEntries(cp));
		classPath.addAll(getPathEntries(ClassPath.getClassPath()));
		return classPath;

	}

	private static List<String> getPathEntries(String s) {
		List<String> ret = new ArrayList<>();
		StringTokenizer tok = new StringTokenizer(s, File.pathSeparator);
		while (tok.hasMoreTokens()) {
			ret.add(tok.nextToken());
		}
		return ret;
	}

	public BcelWorld(List classPath, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
		// this.aspectPath = new ClassPathManager(aspectPath, handler);
		this.classPath = new ClassPathManager(classPath, handler);
		setMessageHandler(handler);
		setCrossReferenceHandler(xrefHandler);
		// Tell BCEL to use us for resolving any classes
		delegate = this;
	}

	public BcelWorld(ClassPathManager cpm, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
		classPath = cpm;
		setMessageHandler(handler);
		setCrossReferenceHandler(xrefHandler);
		// Tell BCEL to use us for resolving any classes
		delegate = this;
	}

	/**
	 * Build a World from a ClassLoader, for LTW support
	 *
	 * @param loader
	 * @param handler
	 * @param xrefHandler
	 */
	public BcelWorld(ClassLoader loader, IMessageHandler handler, ICrossReferenceHandler xrefHandler) {
		classPath = null;
		loaderRef = new BcelWeakClassLoaderReference(loader);
		setMessageHandler(handler);
		setCrossReferenceHandler(xrefHandler);
		// Tell BCEL to use us for resolving any classes
		// delegate = getClassLoaderRepositoryFor(loader);
	}

	public void ensureRepositorySetup() {
		if (delegate == null) {
			delegate = getClassLoaderRepositoryFor(loaderRef);
		}
	}

	public Repository getClassLoaderRepositoryFor(ClassLoaderReference loader) {
		if (bcelRepositoryCaching) {
			return new ClassLoaderRepository(loader);
		} else {
			return new NonCachingClassLoaderRepository(loader);
		}
	}

	public void addPath(String name) {
		classPath.addPath(name, this.getMessageHandler());
	}

	// ---- various interactions with bcel

	public static Type makeBcelType(UnresolvedType type) {
		return Type.getType(type.getErasureSignature());
	}

	static Type[] makeBcelTypes(UnresolvedType[] types) {
		Type[] ret = new Type[types.length];
		for (int i = 0, len = types.length; i < len; i++) {
			ret[i] = makeBcelType(types[i]);
		}
		return ret;
	}

	public static Type[] makeBcelTypes(String[] types) {
		if (types == null || types.length==0 ) {
			return null;
		}
		Type[] ret = new Type[types.length];
		for (int i=0, len=types.length; i<len; i++) {
			ret[i] = makeBcelType(types[i]);
		}
		return ret;
	}

	public static Type makeBcelType(String type) {
		return Type.getType(type);
	}


	static String[] makeBcelTypesAsClassNames(UnresolvedType[] types) {
		String[] ret = new String[types.length];
		for (int i = 0, len = types.length; i < len; i++) {
			ret[i] = types[i].getName();
		}
		return ret;
	}

	public static UnresolvedType fromBcel(Type t) {
		return UnresolvedType.forSignature(t.getSignature());
	}

	static UnresolvedType[] fromBcel(Type[] ts) {
		UnresolvedType[] ret = new UnresolvedType[ts.length];
		for (int i = 0, len = ts.length; i < len; i++) {
			ret[i] = fromBcel(ts[i]);
		}
		return ret;
	}

	public ResolvedType resolve(Type t) {
		return resolve(fromBcel(t));
	}

	@Override
	protected ReferenceTypeDelegate resolveDelegate(ReferenceType ty) {
		String name = ty.getName();
		ensureAdvancedConfigurationProcessed();
		JavaClass jc = lookupJavaClass(classPath, name);
		if (jc == null) {
			// Anyone else to ask?
			if (typeDelegateResolvers != null) {
				for (TypeDelegateResolver tdr : typeDelegateResolvers) {
					ReferenceTypeDelegate delegate = tdr.getDelegate(ty);
					if (delegate != null) {
						return delegate;
					}
				}
			}
			return null;
		} else {
			return buildBcelDelegate(ty, jc, false, false);
		}
	}

	public BcelObjectType buildBcelDelegate(ReferenceType type, JavaClass jc, boolean artificial, boolean exposedToWeaver) {
		BcelObjectType ret = new BcelObjectType(type, jc, artificial, exposedToWeaver);
		return ret;
	}

	private JavaClass lookupJavaClass(ClassPathManager classPath, String name) {
		if (classPath == null) {
			try {
				ensureRepositorySetup();
				JavaClass jc = delegate.loadClass(name);
				if (trace.isTraceEnabled()) {
					trace.event("lookupJavaClass", this, new Object[] { name, jc });
				}
				return jc;
			} catch (ClassNotFoundException e) {
				if (trace.isTraceEnabled()) {
					trace.error("Unable to find class '" + name + "' in repository", e);
				}
				return null;
			}
		}

		ClassPathManager.ClassFile file = null;
		try {
			file = classPath.find(UnresolvedType.forName(name));
			if (file == null) {
				return null;
			}
			ClassParser parser = new ClassParser(file.getInputStream(), file.getPath());
			JavaClass jc = parser.parse();
			return jc;
		} catch (IOException ioe) {
			if (trace.isTraceEnabled()) {
				trace.error("IOException whilst processing class",ioe);
			}
			return null;
		} finally {
			if (file != null) {
				file.close();
			}
		}
	}

	public BcelObjectType addSourceObjectType(JavaClass jc, boolean artificial) {
		return addSourceObjectType(jc.getClassName(), jc, artificial);
	}

	public BcelObjectType addSourceObjectType(String classname, JavaClass jc, boolean artificial) {
		BcelObjectType ret = null;
		if (!jc.getClassName().equals(classname)) {
			throw new RuntimeException(jc.getClassName() + "!=" + classname);
		}
		String signature = UnresolvedType.forName(jc.getClassName()).getSignature();

		ResolvedType resolvedTypeFromTypeMap = typeMap.get(signature);

		if (resolvedTypeFromTypeMap != null && !(resolvedTypeFromTypeMap instanceof ReferenceType)) {
			// what on earth is it then? See pr 112243
			StringBuilder exceptionText = new StringBuilder();
			exceptionText.append("Found invalid (not a ReferenceType) entry in the type map. ");
			exceptionText.append("Signature=[" + signature + "] Found=[" + resolvedTypeFromTypeMap + "] Class=[" + resolvedTypeFromTypeMap.getClass() + "]");
			throw new BCException(exceptionText.toString());
		}

		ReferenceType referenceTypeFromTypeMap = (ReferenceType) resolvedTypeFromTypeMap;

		if (referenceTypeFromTypeMap == null) {
			if (jc.isGeneric() && isInJava5Mode()) {
				ReferenceType rawType = ReferenceType.fromTypeX(UnresolvedType.forRawTypeName(jc.getClassName()), this);
				ret = buildBcelDelegate(rawType, jc, artificial, true);
				ReferenceType genericRefType = new ReferenceType(UnresolvedType.forGenericTypeSignature(signature,
						ret.getDeclaredGenericSignature()), this);
				rawType.setDelegate(ret);
				genericRefType.setDelegate(ret);
				rawType.setGenericType(genericRefType);
				typeMap.put(signature, rawType);
			} else {
				referenceTypeFromTypeMap = new ReferenceType(signature, this);
				ret = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true);
				typeMap.put(signature, referenceTypeFromTypeMap);
			}
		} else {
			ret = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true);
		}
		return ret;
	}

	public BcelObjectType addSourceObjectType(String classname, byte[] bytes, boolean artificial) {
		BcelObjectType retval = null;
		String signature = UnresolvedType.forName(classname).getSignature();
		ResolvedType resolvedTypeFromTypeMap = typeMap.get(signature);

		if (resolvedTypeFromTypeMap != null && !(resolvedTypeFromTypeMap instanceof ReferenceType)) {
			// what on earth is it then? See pr 112243
			StringBuilder exceptionText = new StringBuilder();
			exceptionText.append("Found invalid (not a ReferenceType) entry in the type map. ");
			exceptionText.append("Signature=[" + signature + "] Found=[" + resolvedTypeFromTypeMap + "] Class=[" + resolvedTypeFromTypeMap.getClass() + "]");
			throw new BCException(exceptionText.toString());
		}

		ReferenceType referenceTypeFromTypeMap = (ReferenceType) resolvedTypeFromTypeMap;

		if (referenceTypeFromTypeMap == null) {
			JavaClass jc = Utility.makeJavaClass(classname, bytes);
			if (jc.isGeneric() && isInJava5Mode()) {
				referenceTypeFromTypeMap = ReferenceType.fromTypeX(UnresolvedType.forRawTypeName(jc.getClassName()), this);
				retval = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true);
				ReferenceType genericRefType = new ReferenceType(UnresolvedType.forGenericTypeSignature(signature,
						retval.getDeclaredGenericSignature()), this);
				referenceTypeFromTypeMap.setDelegate(retval);
				genericRefType.setDelegate(retval);
				referenceTypeFromTypeMap.setGenericType(genericRefType);
				typeMap.put(signature, referenceTypeFromTypeMap);
			} else {
				referenceTypeFromTypeMap = new ReferenceType(signature, this);
				retval = buildBcelDelegate(referenceTypeFromTypeMap, jc, artificial, true);
				typeMap.put(signature, referenceTypeFromTypeMap);
			}
		} else {
			ReferenceTypeDelegate existingDelegate = referenceTypeFromTypeMap.getDelegate();
			if (!(existingDelegate instanceof BcelObjectType)) {
				throw new IllegalStateException("For " + classname + " should be BcelObjectType, but is " + existingDelegate.getClass());
			}
			retval = (BcelObjectType) existingDelegate;
			// Note1: If the type is already exposed to the weaver (retval.isExposedToWeaver()) then this is likely
			// to be a hotswap reweave so build a new delegate, don't accidentally use the old data.
			// Note2: Also seen when LTW and another agent precedes the AspectJ agent.  Earlier in LTW
			// a type is resolved (and ends up in the typemap but not exposed to the weaver at that time)
			// then later LTW actually is attempted on this type. We end up here with different
			// bytes to the current delegate if the earlier agent has modified them. See PR488216
//			if (retval.isArtificial() || retval.isExposedToWeaver()) {
			retval = buildBcelDelegate(referenceTypeFromTypeMap, Utility.makeJavaClass(classname, bytes), artificial, true);
//			}
		}
		return retval;
	}

	void deleteSourceObjectType(UnresolvedType ty) {
		typeMap.remove(ty.getSignature());
	}

	public static Member makeFieldJoinPointSignature(LazyClassGen cg, FieldInstruction fi) {
		ConstantPool cpg = cg.getConstantPool();
		return MemberImpl.field(fi.getClassName(cpg),
				(fi.opcode == Constants.GETSTATIC || fi.opcode == Constants.PUTSTATIC) ? Modifier.STATIC : 0, fi.getName(cpg),
				fi.getSignature(cpg));
	}

	public Member makeJoinPointSignatureFromMethod(LazyMethodGen mg, MemberKind kind) {
		Member ret = mg.getMemberView();
		if (ret == null) {
			int mods = mg.getAccessFlags();
			if (mg.getEnclosingClass().isInterface()) {
				mods |= Modifier.INTERFACE;
			}
			return new ResolvedMemberImpl(kind, UnresolvedType.forName(mg.getClassName()), mods, fromBcel(mg.getReturnType()),
					mg.getName(), fromBcel(mg.getArgumentTypes()));
		} else {
			return ret;
		}

	}

	public Member makeJoinPointSignatureForMonitorEnter(LazyClassGen cg, InstructionHandle h) {
		return MemberImpl.monitorEnter();
	}

	public Member makeJoinPointSignatureForMonitorExit(LazyClassGen cg, InstructionHandle h) {
		return MemberImpl.monitorExit();
	}

	public Member makeJoinPointSignatureForArrayConstruction(LazyClassGen cg, InstructionHandle handle) {
		Instruction i = handle.getInstruction();
		ConstantPool cpg = cg.getConstantPool();
		Member retval = null;

		if (i.opcode == Constants.ANEWARRAY) {
			// ANEWARRAY arrayInstruction = (ANEWARRAY)i;
			Type ot = i.getType(cpg);
			UnresolvedType ut = fromBcel(ot);
			ut = UnresolvedType.makeArray(ut, 1);
			retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", new ResolvedType[] { INT });
		} else if (i instanceof MULTIANEWARRAY) {
			MULTIANEWARRAY arrayInstruction = (MULTIANEWARRAY) i;
			UnresolvedType ut = null;
			short dimensions = arrayInstruction.getDimensions();
			ObjectType ot = arrayInstruction.getLoadClassType(cpg);
			if (ot != null) {
				ut = fromBcel(ot);
				ut = UnresolvedType.makeArray(ut, dimensions);
			} else {
				Type t = arrayInstruction.getType(cpg);
				ut = fromBcel(t);
			}
			ResolvedType[] parms = new ResolvedType[dimensions];
			for (int ii = 0; ii < dimensions; ii++) {
				parms[ii] = INT;
			}
			retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", parms);

		} else if (i.opcode == Constants.NEWARRAY) {
			// NEWARRAY arrayInstruction = (NEWARRAY)i;
			Type ot = i.getType();
			UnresolvedType ut = fromBcel(ot);
			retval = MemberImpl.method(ut, Modifier.PUBLIC, UnresolvedType.VOID, "<init>", new ResolvedType[] { INT });
		} else {
			throw new BCException("Cannot create array construction signature for this non-array instruction:" + i);
		}
		return retval;
	}

	public Member makeJoinPointSignatureForMethodInvocation(LazyClassGen cg, InvokeInstruction ii) {
		ConstantPool cpg = cg.getConstantPool();
		String name = ii.getName(cpg);
		String declaring = ii.getClassName(cpg);
		UnresolvedType declaringType = null;

		String signature = ii.getSignature(cpg);

		// 307147
		if (name.startsWith("ajc$privMethod$")) {
			// The invoke is on a privileged accessor. These may be created for different
			// kinds of target, not necessarily just private methods. In bug 307147 it is
			// for a private method. This code is identifying the particular case in 307147
			try {
				declaringType = UnresolvedType.forName(declaring);
				String typeNameAsFoundInAccessorName = declaringType.getName().replace('.', '_');
				int indexInAccessorName = name.lastIndexOf(typeNameAsFoundInAccessorName);
				if (indexInAccessorName != -1) {
					String methodName = name.substring(indexInAccessorName+typeNameAsFoundInAccessorName.length()+1);
					ResolvedType resolvedDeclaringType = declaringType.resolve(this);
					ResolvedMember[] methods = resolvedDeclaringType.getDeclaredMethods();
					for (ResolvedMember method: methods) {
						if (method.getName().equals(methodName) && method.getSignature().equals(signature) && Modifier.isPrivate(method.getModifiers())) {
							return method;
						}
					}
				}
			} catch (Exception e) {
				// Remove this once confident above code isn't having unexpected side effects
				// Added 1.8.7
				e.printStackTrace();
			}
		}

		int modifier = (ii instanceof INVOKEINTERFACE) ? Modifier.INTERFACE
				: (ii.opcode == Constants.INVOKESTATIC) ? Modifier.STATIC : (ii.opcode == Constants.INVOKESPECIAL && !name
						.equals("<init>")) ? Modifier.PRIVATE : 0;

		// in Java 1.4 and after, static method call of super class within
		// subclass method appears
		// as declared by the subclass in the bytecode - but they are not
		// see #104212
		if (ii.opcode == Constants.INVOKESTATIC) {
			ResolvedType appearsDeclaredBy = resolve(declaring);
			// look for the method there
			for (Iterator<ResolvedMember> iterator = appearsDeclaredBy.getMethods(true, true); iterator.hasNext();) {
				ResolvedMember method = iterator.next();
				if (Modifier.isStatic(method.getModifiers())) {
					if (name.equals(method.getName()) && signature.equals(method.getSignature())) {
						// we found it
						declaringType = method.getDeclaringType();
						break;
					}
				}
			}
		}

		if (declaringType == null) {
			if (declaring.charAt(0) == '[') {
				declaringType = UnresolvedType.forSignature(declaring);
			} else {
				declaringType = UnresolvedType.forName(declaring);
			}
		}
		return MemberImpl.method(declaringType, modifier, name, signature);
	}

	@Override
	public String toString() {
		StringBuilder buf = new StringBuilder();
		buf.append("BcelWorld(");
		// buf.append(shadowMungerMap);
		buf.append(")");
		return buf.toString();
	}

	/**
	 * Retrieve a bcel delegate for an aspect - this will return NULL if the delegate is an EclipseSourceType and not a
	 * BcelObjectType - this happens quite often when incrementally compiling.
	 */
	public static BcelObjectType getBcelObjectType(ResolvedType concreteAspect) {
		if (concreteAspect == null) {
			return null;
		}
		if (!(concreteAspect instanceof ReferenceType)) { // Might be Missing
			return null;
		}
		ReferenceTypeDelegate rtDelegate = ((ReferenceType) concreteAspect).getDelegate();
		if (rtDelegate instanceof BcelObjectType) {
			return (BcelObjectType) rtDelegate;
		} else {
			return null;
		}
	}

	public void tidyUp() {
		// At end of compile, close any open files so deletion of those archives
		// is possible
		classPath.closeArchives();
		typeMap.report();
		typeMap.demote(true);
		// ResolvedType.resetPrimitives();
	}

	// / The repository interface methods

	@Override
	public JavaClass findClass(String className) {
		return lookupJavaClass(classPath, className);
	}

	@Override
	public JavaClass loadClass(String className) throws ClassNotFoundException {
		return lookupJavaClass(classPath, className);
	}

	@Override
	public void storeClass(JavaClass clazz) {
		// doesn't need to do anything
	}

	@Override
	public void removeClass(JavaClass clazz) {
		throw new RuntimeException("Not implemented");
	}

	@Override
	public JavaClass loadClass(Class clazz) throws ClassNotFoundException {
		throw new RuntimeException("Not implemented");
	}

	@Override
	public void clear() {
		delegate.clear();
		// throw new RuntimeException("Not implemented");
	}

	/**
	 * The aim of this method is to make sure a particular type is 'ok'. Some operations on the delegate for a type modify it and
	 * this method is intended to undo that... see pr85132
	 */
	@Override
	public void validateType(UnresolvedType type) {
		ResolvedType result = typeMap.get(type.getSignature());
		if (result == null) {
			return; // We haven't heard of it yet
		}
		if (!result.isExposedToWeaver()) {
			return; // cant need resetting
		}
		result.ensureConsistent();
		// If we want to rebuild it 'from scratch' then:
		// ClassParser cp = new ClassParser(new
		// ByteArrayInputStream(newbytes),new String(cs));
		// try {
		// rt.setDelegate(makeBcelObjectType(rt,cp.parse(),true));
		// } catch (ClassFormatException e) {
		// e.printStackTrace();
		// } catch (IOException e) {
		// e.printStackTrace();
		// }
	}

	/**
	 * Apply a single declare parents - return true if we change the type
	 */
	private boolean applyDeclareParents(DeclareParents p, ResolvedType onType) {
		boolean didSomething = false;
		List<ResolvedType> newParents = p.findMatchingNewParents(onType, true);
		if (!newParents.isEmpty()) {
			didSomething = true;
			BcelObjectType classType = BcelWorld.getBcelObjectType(onType);
			// System.err.println("need to do declare parents for: " + onType);
			for (ResolvedType newParent : newParents) {
				// We set it here so that the imminent matching for ITDs can
				// succeed - we still haven't done the necessary changes to the class file
				// itself (like transform super calls) - that is done in
				// BcelTypeMunger.mungeNewParent()
				// classType.addParent(newParent);
				onType.addParent(newParent);
				ResolvedTypeMunger newParentMunger = new NewParentTypeMunger(newParent, p.getDeclaringType());
				newParentMunger.setSourceLocation(p.getSourceLocation());
				onType.addInterTypeMunger(new BcelTypeMunger(newParentMunger, getCrosscuttingMembersSet()
						.findAspectDeclaringParents(p)), false);
			}
		}
		return didSomething;
	}

	/**
	 * Apply a declare @type - return true if we change the type
	 */
	private boolean applyDeclareAtType(DeclareAnnotation decA, ResolvedType onType, boolean reportProblems) {
		boolean didSomething = false;
		if (decA.matches(onType)) {

			if (onType.hasAnnotation(decA.getAnnotation().getType())) {
				// already has it
				return false;
			}

			AnnotationAJ annoX = decA.getAnnotation();

			// check the annotation is suitable for the target
			boolean isOK = checkTargetOK(decA, onType, annoX);

			if (isOK) {
				didSomething = true;
				ResolvedTypeMunger newAnnotationTM = new AnnotationOnTypeMunger(annoX);
				newAnnotationTM.setSourceLocation(decA.getSourceLocation());
				onType.addInterTypeMunger(new BcelTypeMunger(newAnnotationTM, decA.getAspect().resolve(this)), false);
				decA.copyAnnotationTo(onType);
			}
		}
		return didSomething;
	}

	/**
	 * Apply the specified declare @field construct to any matching fields in the specified type.
	 * @param deca the declare annotation targeting fields
	 * @param type the type to check for members matching the declare annotation
	 * @return true if something matched and the type was modified
	 */
	private boolean applyDeclareAtField(DeclareAnnotation deca, ResolvedType type) {
		boolean changedType = false;
		ResolvedMember[] fields = type.getDeclaredFields();
		for (ResolvedMember field: fields) {
			if (deca.matches(field, this)) {
				AnnotationAJ anno = deca.getAnnotation();
				if (!field.hasAnnotation(anno.getType())) {
					field.addAnnotation(anno);
					changedType=true;
				}
			}
		}
		return changedType;
	}

	/**
	 * Checks for an @target() on the annotation and if found ensures it allows the annotation to be attached to the target type
	 * that matched.
	 */
	private boolean checkTargetOK(DeclareAnnotation decA, ResolvedType onType, AnnotationAJ annoX) {
		if (annoX.specifiesTarget()) {
			if ((onType.isAnnotation() && !annoX.allowedOnAnnotationType()) || (!annoX.allowedOnRegularType())) {
				return false;
			}
		}
		return true;
	}

	// Hmmm - very similar to the code in BcelWeaver.weaveParentTypeMungers -
	// this code
	// doesn't need to produce errors/warnings though as it won't really be
	// weaving.
	protected void weaveInterTypeDeclarations(ResolvedType onType) {

		List<DeclareParents> declareParentsList = getCrosscuttingMembersSet().getDeclareParents();
		if (onType.isRawType()) {
			onType = onType.getGenericType();
		}
		onType.clearInterTypeMungers();

		List<DeclareParents> decpToRepeat = new ArrayList<>();

		boolean aParentChangeOccurred = false;
		boolean anAnnotationChangeOccurred = false;
		// First pass - apply all decp mungers
		for (DeclareParents decp : declareParentsList) {
			boolean typeChanged = applyDeclareParents(decp, onType);
			if (typeChanged) {
				aParentChangeOccurred = true;
			} else { // Perhaps it would have matched if a 'dec @type' had
				// modified the type
				if (!decp.getChild().isStarAnnotation()) {
					decpToRepeat.add(decp);
				}
			}
		}

		// Still first pass - apply all dec @type mungers
		for (DeclareAnnotation decA : getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) {
			boolean typeChanged = applyDeclareAtType(decA, onType, true);
			if (typeChanged) {
				anAnnotationChangeOccurred = true;
			}
		}

		// apply declare @field
		for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) {
			if (applyDeclareAtField(deca,onType)) {
				anAnnotationChangeOccurred = true;
			}
		}

		while ((aParentChangeOccurred || anAnnotationChangeOccurred) && !decpToRepeat.isEmpty()) {
			anAnnotationChangeOccurred = aParentChangeOccurred = false;
			List<DeclareParents> decpToRepeatNextTime = new ArrayList<>();
			for (DeclareParents decp: decpToRepeat) {
				if (applyDeclareParents(decp, onType)) {
					aParentChangeOccurred = true;
				} else {
					decpToRepeatNextTime.add(decp);
				}
			}

			for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnTypes()) {
				if (applyDeclareAtType(deca, onType, false)) {
					anAnnotationChangeOccurred = true;
				}
			}

			for (DeclareAnnotation deca: getCrosscuttingMembersSet().getDeclareAnnotationOnFields()) {
				if (applyDeclareAtField(deca, onType)) {
					anAnnotationChangeOccurred = true;
				}
			}
			decpToRepeat = decpToRepeatNextTime;
		}

	}

	@Override
	public IWeavingSupport getWeavingSupport() {
		return bcelWeavingSupport;
	}

	@Override
	public void reportCheckerMatch(Checker checker, Shadow shadow) {
		IMessage iMessage = new Message(checker.getMessage(shadow), shadow.toString(), checker.isError() ? IMessage.ERROR
				: IMessage.WARNING, shadow.getSourceLocation(), null, new ISourceLocation[] { checker.getSourceLocation() }, true,
				0, -1, -1);

		getMessageHandler().handleMessage(iMessage);

		if (getCrossReferenceHandler() != null) {
			getCrossReferenceHandler()
					.addCrossReference(
							checker.getSourceLocation(),
							shadow.getSourceLocation(),
							(checker.isError() ? IRelationship.Kind.DECLARE_ERROR.getName() : IRelationship.Kind.DECLARE_WARNING
									.getName()), false);

		}

		if (getModel() != null) {
			AsmRelationshipProvider.addDeclareErrorOrWarningRelationship(getModelAsAsmManager(), shadow, checker);
		}

	}

	public AsmManager getModelAsAsmManager() {
		return (AsmManager) getModel(); // For now... always an AsmManager in a bcel environment
	}

	void raiseError(String message) {
		getMessageHandler().handleMessage(MessageUtil.error(message));
	}

	/**
	 * These are aop.xml files that can be used to alter the aspects that actually apply from those passed in - and also their scope
	 * of application to other files in the system.
	 *
	 * @param xmlFiles list of File objects representing any aop.xml files passed in to configure the build process
	 */
	public void setXmlFiles(List<File> xmlFiles) {
		if (!isXmlConfiguredWorld && !xmlFiles.isEmpty()) {
			raiseError("xml configuration files only supported by the compiler when -xmlConfigured option specified");
			return;
		}
		if (!xmlFiles.isEmpty()) {
			xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_COMPILE);
		}
		for (File xmlfile : xmlFiles) {
			try {
				Definition d = DocumentParser.parse(xmlfile.toURI().toURL());
				xmlConfiguration.add(d);
			} catch (MalformedURLException e) {
				raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage());
			} catch (Exception e) {
				raiseError("Unexpected problem processing XML config file '" + xmlfile.getName() + "' :" + e.getMessage());
			}
		}
	}

	/**
	 * Add a scoped aspects where the scoping was defined in an aop.xml file and this world is being used in a LTW configuration
	 */
	public void addScopedAspect(String name, String scope) {
		this.isXmlConfiguredWorld = true;
		if (xmlConfiguration == null) {
			xmlConfiguration = new WeavingXmlConfig(this, WeavingXmlConfig.MODE_LTW);
		}
		xmlConfiguration.addScopedAspect(name, scope);
	}

	public void setXmlConfigured(boolean b) {
		this.isXmlConfiguredWorld = b;
	}

	@Override
	public boolean isXmlConfigured() {
		return isXmlConfiguredWorld && xmlConfiguration != null;
	}

	public WeavingXmlConfig getXmlConfiguration() {
		return xmlConfiguration;
	}

	@Override
	public boolean isAspectIncluded(ResolvedType aspectType) {
		if (!isXmlConfigured()) {
			return true;
		}
		return xmlConfiguration.specifiesInclusionOfAspect(aspectType.getName());
	}

	@Override
	public TypePattern getAspectScope(ResolvedType declaringType) {
		return xmlConfiguration.getScopeFor(declaringType.getName());
	}

	@Override
	public boolean hasUnsatisfiedDependency(ResolvedType aspectType) {
		String aspectName = aspectType.getName();

		if (aspectType.hasAnnotations()) {
			AnnotationAJ[] annos = aspectType.getAnnotations();
			for (AnnotationAJ anno: annos) {
				if (anno.getTypeName().equals("org.aspectj.lang.annotation.RequiredTypes")) {
					String values = anno.getStringFormOfValue("value"); // Example: "[A,org.foo.Bar]"
					if (values != null && values.length() > 2) {
						values = values.substring(1,values.length()-1);
						StringTokenizer tokenizer = new StringTokenizer(values,",");
						boolean anythingMissing = false;
						while (tokenizer.hasMoreElements()) {
							String requiredTypeName = tokenizer.nextToken();
							ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName));
							if (rt.isMissing()) {
								if (!getMessageHandler().isIgnoring(IMessage.INFO)) {
									getMessageHandler().handleMessage(
											MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '"
													+ requiredTypeName + "' which cannot be found on the classpath"));
								}
								anythingMissing = true;
								if (aspectRequiredTypes == null) {
									aspectRequiredTypes = new HashMap<>();
								}
								// Record that it has an invalid type reference
								aspectRequiredTypes.put(aspectName,requiredTypeName);
							}
						}
						if (anythingMissing) {
							return true;
						}
						else {
							return false;
						}
					}
					else {
						// no value specified for annotation
						return false;
					}
				}
			}
		}
		if (aspectRequiredTypes == null) {
			// no aspects require anything, so there can be no unsatisfied dependencies
			return false;
		}
		if (!aspectRequiredTypesProcessed.contains(aspectName)) {
			String requiredTypeName = aspectRequiredTypes.get(aspectName);
			if (requiredTypeName==null) {
				aspectRequiredTypesProcessed.add(aspectName);
				return false;
			} else {
				ResolvedType rt = resolve(UnresolvedType.forName(requiredTypeName));
				if (!rt.isMissing()) {
					aspectRequiredTypesProcessed.add(aspectName);
					aspectRequiredTypes.remove(aspectName);
					return false;
				} else {
					if (!getMessageHandler().isIgnoring(IMessage.INFO)) {
						getMessageHandler().handleMessage(
								MessageUtil.info("deactivating aspect '" + aspectName + "' as it requires type '"
										+ requiredTypeName + "' which cannot be found on the classpath"));
					}
					aspectRequiredTypesProcessed.add(aspectName);
					return true;
				}
			}
		}
		return aspectRequiredTypes.containsKey(aspectName);
	}

	private List<String> aspectRequiredTypesProcessed = new ArrayList<>();
	private Map<String, String> aspectRequiredTypes = null;

	public void addAspectRequires(String aspectClassName, String requiredType) {
		if (aspectRequiredTypes == null) {
			aspectRequiredTypes = new HashMap<>();
		}
		aspectRequiredTypes.put(aspectClassName,requiredType);
	}

	/**
	 * A WeavingXmlConfig is initially a collection of definitions from XML files - once the world is ready and weaving is running
	 * it will initialize and transform those definitions into an optimized set of values (eg. resolve type patterns and string
	 * names to real entities). It can then answer questions quickly: (1) is this aspect included in the weaving? (2) Is there a
	 * scope specified for this aspect and does it include type X?
	 *
	 */
	static class WeavingXmlConfig {

		final static int MODE_COMPILE = 1;
		final static int MODE_LTW = 2;

		private int mode;

		private boolean initialized = false; // Lazily done
		private List<Definition> definitions = new ArrayList<>();

		private List<String> resolvedIncludedAspects = new ArrayList<>();
		private Map<String, TypePattern> scopes = new HashMap<>();

		// these are not set for LTW mode (exclusion of these fast match patterns is handled before the weaver/world are used)
		private List<String> includedFastMatchPatterns = Collections.emptyList();
		private List<TypePattern> includedPatterns = Collections.emptyList();
		private List<String> excludedFastMatchPatterns = Collections.emptyList();
		private List<TypePattern> excludedPatterns = Collections.emptyList();

		private BcelWorld world;

		public WeavingXmlConfig(BcelWorld bcelWorld, int mode) {
			this.world = bcelWorld;
			this.mode = mode;
		}

		public void add(Definition d) {
			definitions.add(d);
		}

		public void addScopedAspect(String aspectName, String scope) {
			ensureInitialized();
			resolvedIncludedAspects.add(aspectName);
			try {
				TypePattern scopePattern = new PatternParser(scope).parseTypePattern();
				scopePattern.resolve(world);
				scopes.put(aspectName, scopePattern);
				if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) {
					world.getMessageHandler().handleMessage(
							MessageUtil.info("Aspect '" + aspectName + "' is scoped to apply against types matching pattern '"
									+ scopePattern + "'"));
				}
			} catch (Exception e) {
				world.getMessageHandler().handleMessage(
						MessageUtil.error("Unable to parse scope as type pattern.  Scope was '" + scope + "': " + e.getMessage()));
			}
		}

		public void ensureInitialized() {
			if (!initialized) {
				try {
					resolvedIncludedAspects = new ArrayList<>();
					// Process the definitions into something more optimal
					for (Definition definition : definitions) {
						List<String> aspectNames = definition.getAspectClassNames();
						for (String name : aspectNames) {
							resolvedIncludedAspects.add(name);
							// TODO check for existence?
							// ResolvedType resolvedAspect = resolve(UnresolvedType.forName(name));
							// if (resolvedAspect.isMissing()) {
							// // ERROR
							// } else {
							// resolvedIncludedAspects.add(resolvedAspect);
							// }
							String scope = definition.getScopeForAspect(name);
							if (scope != null) {
								// Resolve the type pattern
								try {
									TypePattern scopePattern = new PatternParser(scope).parseTypePattern();
									scopePattern.resolve(world);
									scopes.put(name, scopePattern);
									if (!world.getMessageHandler().isIgnoring(IMessage.INFO)) {
										world.getMessageHandler().handleMessage(
												MessageUtil.info("Aspect '" + name
														+ "' is scoped to apply against types matching pattern '"
														+ scopePattern.toString() + "'"));
									}
								} catch (Exception e) {
									// TODO definitions should remember which file they came from, for inclusion in this message
									world.getMessageHandler().handleMessage(
											MessageUtil.error("Unable to parse scope as type pattern.  Scope was '" + scope + "': "
													+ e.getMessage()));
								}
							}
						}
						try {
							List<String> includePatterns = definition.getIncludePatterns();
							if (includePatterns.size() > 0) {
								includedPatterns = new ArrayList<>();
								includedFastMatchPatterns = new ArrayList<>();
							}
							for (String includePattern : includePatterns) {
								if (includePattern.endsWith("..*")) {
									// from 'blah.blah.blah..*' leave the 'blah.blah.blah.'
									includedFastMatchPatterns.add(includePattern.substring(0, includePattern.length() - 2));
								} else {
									TypePattern includedPattern = new PatternParser(includePattern).parseTypePattern();
									includedPatterns.add(includedPattern);
								}
							}
							List<String> excludePatterns = definition.getExcludePatterns();
							if (excludePatterns.size() > 0) {
								excludedPatterns = new ArrayList<>();
								excludedFastMatchPatterns = new ArrayList<>();
							}
							for (String excludePattern : excludePatterns) {
								if (excludePattern.endsWith("..*")) {
									// from 'blah.blah.blah..*' leave the 'blah.blah.blah.'
									excludedFastMatchPatterns.add(excludePattern.substring(0, excludePattern.length() - 2));
								} else {
									TypePattern excludedPattern = new PatternParser(excludePattern).parseTypePattern();
									excludedPatterns.add(excludedPattern);
								}
							}
						} catch (ParserException pe) {
							// TODO definitions should remember which file they came from, for inclusion in this message
							world.getMessageHandler().handleMessage(
									MessageUtil.error("Unable to parse type pattern: " + pe.getMessage()));

						}
					}
				} finally {
					initialized = true;
				}
			}
		}

		public boolean specifiesInclusionOfAspect(String name) {
			ensureInitialized();
			return resolvedIncludedAspects.contains(name);
		}

		public TypePattern getScopeFor(String name) {
			return scopes.get(name);
		}

		/**
		 * Checks if a given type is to be excluded from weaving.
		 * <p>
		 * For LTW, the development guide (<i>docs/devguide/ltw.adoc</i>) says:
		 * <p>
		 * <i>"The set of types to be woven are those types matched by at least one weaver {@code include} element and not
		 * matched by any weaver {@code exclude} element. If there are no weaver include statements, then all non-excluded
		 * types are included."</i>
		 * <p>
		 * In CTW mode, we cannot quite follow the same rules for exclusion as used for LTW: If the weaver is seeing it
		 * during this kind of build, the type is implicitly included. So all we should check for is exclusion.
		 *
		 * @param type resolved type to be checked
		 *
		 * @return Always false in LTW mode. In CTW mode true for excluded types, false otherwise.
		 */
		public boolean excludesType(ResolvedType type) {
			if (mode == MODE_LTW) {
				return false;
			}
			String typename = type.getName();
			boolean excluded = false;
			for (String excludedPattern : excludedFastMatchPatterns) {
				if (typename.startsWith(excludedPattern)) {
					excluded = true;
					break;
				}
			}
			if (!excluded) {
				for (TypePattern excludedPattern : excludedPatterns) {
					if (excludedPattern.matchesStatically(type)) {
						excluded = true;
						break;
					}
				}
			}
			return excluded;
		}

	}

	@Override
	public TypeMap getTypeMap() {
		return typeMap;
	}

	@Override
	public boolean isLoadtimeWeaving() {
		return false;
	}

	public void addTypeDelegateResolver(TypeDelegateResolver typeDelegateResolver) {
		if (typeDelegateResolvers == null) {
			typeDelegateResolvers = new ArrayList<>();
		}
		typeDelegateResolvers.add(typeDelegateResolver);
	}

	@Override
	public void classWriteEvent(char[][] compoundName) {
		typeMap.classWriteEvent(new String(CharOperation.concatWith(compoundName, '.')));
	}

	/**
	 * Force demote a type.
	 */
	public void demote(ResolvedType type) {
		typeMap.demote(type);
	}

}
