/* *******************************************************************
 * Copyright (c) 2002-2010 Contributors
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *     PARC                 initial implementation
 *     Andy Clement  6Jul05 generics - signature attribute
 *     Abraham Nevado
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;

import org.aspectj.apache.bcel.Constants;
import org.aspectj.apache.bcel.classfile.Attribute;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.Field;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.Method;
import org.aspectj.apache.bcel.classfile.Signature;
import org.aspectj.apache.bcel.classfile.Synthetic;
import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen;
import org.aspectj.apache.bcel.generic.BasicType;
import org.aspectj.apache.bcel.generic.ClassGen;
import org.aspectj.apache.bcel.generic.FieldGen;
import org.aspectj.apache.bcel.generic.InstructionConstants;
import org.aspectj.apache.bcel.generic.InstructionFactory;
import org.aspectj.apache.bcel.generic.InstructionHandle;
import org.aspectj.apache.bcel.generic.InstructionList;
import org.aspectj.apache.bcel.generic.ObjectType;
import org.aspectj.apache.bcel.generic.Type;
import org.aspectj.bridge.IMessage;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.weaver.AjAttribute;
import org.aspectj.weaver.AjAttribute.WeaverState;
import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.Member;
import org.aspectj.weaver.MemberKind;
import org.aspectj.weaver.NameMangler;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.RuntimeVersion;
import org.aspectj.weaver.Shadow;
import org.aspectj.weaver.SignatureUtils;
import org.aspectj.weaver.TypeVariable;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.UnresolvedType.TypeKind;
import org.aspectj.weaver.WeaverMessages;
import org.aspectj.weaver.WeaverStateInfo;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.asm.AsmDetector;
import org.aspectj.weaver.bcel.asm.StackMapAdder;

/**
 * Lazy lazy lazy. We don't unpack the underlying class unless necessary. Things like new methods and annotations accumulate in here
 * until they must be written out, don't add them to the underlying MethodGen! Things are slightly different if this represents an
 * Aspect.
 */
public final class LazyClassGen {

	private static final Type[] ARRAY_7STRING_INT = new Type[] { Type.STRING, Type.STRING, Type.STRING, Type.STRING, Type.STRING,
			Type.STRING, Type.STRING, Type.INT };

	private static final Type[] ARRAY_8STRING_INT = new Type[] { Type.STRING, Type.STRING, Type.STRING, Type.STRING, Type.STRING,
			Type.STRING, Type.STRING, Type.STRING, Type.INT };

	private static final Type[] PARAMSIGNATURE_MAKESJP_METHOD = new Type[] {
			Type.STRING, Type.INT, Type.STRING, Type.CLASS, Type.CLASS_ARRAY, Type.STRING_ARRAY, Type.CLASS_ARRAY, Type.CLASS, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_CONSTRUCTOR = new Type[] {
			Type.STRING, Type.INT, Type.CLASS, Type.CLASS_ARRAY, Type.STRING_ARRAY, Type.CLASS_ARRAY, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_CATCHCLAUSE = new Type[] {
			Type.STRING, Type.CLASS, Type.CLASS, Type.STRING, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_FIELD = new Type[] {
			Type.STRING, Type.INT, Type.STRING, Type.CLASS, Type.CLASS, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_INITIALIZER = new Type[] {
			Type.STRING, Type.INT, Type.CLASS, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_MONITOR = new Type[] {
			Type.STRING, Type.CLASS, Type.INT
	};

	private static final Type[] PARAMSIGNATURE_MAKESJP_ADVICE = new Type[] {
			Type.STRING, Type.INT, Type.STRING, Type.CLASS, Type.CLASS_ARRAY, Type.STRING_ARRAY,
			Type.CLASS_ARRAY, Type.CLASS, Type.INT
	};





	private static final int ACC_SYNTHETIC = 0x1000;

	private static final String[] NO_STRINGS = new String[0];

	int highestLineNumber = 0; // ---- JSR 45 info

	private final SortedMap<String, InlinedSourceFileInfo> inlinedFiles = new TreeMap<>();

	private boolean regenerateGenericSignatureAttribute = false;

	private BcelObjectType myType; // XXX is not set for types we create
	private ClassGen myGen;
	private final ConstantPool cp;
	private final World world;
	private final String packageName = null;

	private final List<BcelField> fields = new ArrayList<>();
	private final List<LazyMethodGen> methodGens = new ArrayList<>();
	private final List<LazyClassGen> classGens = new ArrayList<>();
	private final List<AnnotationGen> annotations = new ArrayList<>();
	private int childCounter = 0;

	private final InstructionFactory fact;

	private boolean isSerializable = false;
	private boolean hasSerialVersionUIDField = false;
	private boolean serialVersionUIDRequiresInitialization = false;
	private long calculatedSerialVersionUID;
	private boolean hasClinit = false;

	private ResolvedType[] extraSuperInterfaces = null;
	private ResolvedType superclass = null;

	// ---

	static class InlinedSourceFileInfo {
		int highestLineNumber;
		int offset; // calculated

		InlinedSourceFileInfo(int highestLineNumber) {
			this.highestLineNumber = highestLineNumber;
		}
	}

	void addInlinedSourceFileInfo(String fullpath, int highestLineNumber) {
		InlinedSourceFileInfo info = inlinedFiles.get(fullpath);
		if (info != null) {
			if (info.highestLineNumber < highestLineNumber) {
				info.highestLineNumber = highestLineNumber;
			}
		} else {
			inlinedFiles.put(fullpath, new InlinedSourceFileInfo(highestLineNumber));
		}
	}

	void calculateSourceDebugExtensionOffsets() {
		int i = roundUpToHundreds(highestLineNumber);
		for (InlinedSourceFileInfo element : inlinedFiles.values()) {
			element.offset = i;
			i = roundUpToHundreds(i + element.highestLineNumber);
		}
	}

	private static int roundUpToHundreds(int i) {
		return ((i / 100) + 1) * 100;
	}

	int getSourceDebugExtensionOffset(String fullpath) {
		return inlinedFiles.get(fullpath).offset;
	}

	// private Unknown getSourceDebugExtensionAttribute() {
	// int nameIndex = cp.addUtf8("SourceDebugExtension");
	// String data = getSourceDebugExtensionString();
	// //System.err.println(data);
	// byte[] bytes = Utility.stringToUTF(data);
	// int length = bytes.length;
	//
	// return new Unknown(nameIndex, length, bytes, cp);
	// }

	// private LazyClassGen() {}
	// public static void main(String[] args) {
	// LazyClassGen m = new LazyClassGen();
	// m.highestLineNumber = 37;
	// m.inlinedFiles.put("boo/baz/foo.java", new InlinedSourceFileInfo( 83));
	// m.inlinedFiles.put("boo/barz/foo.java", new InlinedSourceFileInfo(292));
	// m.inlinedFiles.put("boo/baz/moo.java", new InlinedSourceFileInfo(128));
	// m.calculateSourceDebugExtensionOffsets();
	// System.err.println(m.getSourceDebugExtensionString());
	// }

	// For the entire pathname, we're using package names. This is probably
	// wrong.
	// private String getSourceDebugExtensionString() {
	// StringBuffer out = new StringBuffer();
	// String myFileName = getFileName();
	// // header section
	// out.append("SMAP\n");
	// out.append(myFileName);
	// out.append("\nAspectJ\n");
	// // stratum section
	// out.append("*S AspectJ\n");
	// // file section
	// out.append("*F\n");
	// out.append("1 ");
	// out.append(myFileName);
	// out.append("\n");
	// int i = 2;
	// for (Iterator iter = inlinedFiles.keySet().iterator(); iter.hasNext();) {
	// String element = (String) iter.next();
	// int ii = element.lastIndexOf('/');
	// if (ii == -1) {
	// out.append(i++); out.append(' ');
	// out.append(element); out.append('\n');
	// } else {
	// out.append("+ "); out.append(i++); out.append(' ');
	// out.append(element.substring(ii+1)); out.append('\n');
	// out.append(element); out.append('\n');
	// }
	// }
	// // emit line section
	// out.append("*L\n");
	// out.append("1#1,");
	// out.append(highestLineNumber);
	// out.append(":1,1\n");
	// i = 2;
	// for (Iterator iter = inlinedFiles.values().iterator(); iter.hasNext();) {
	// InlinedSourceFileInfo element = (InlinedSourceFileInfo) iter.next();
	// out.append("1#");
	// out.append(i++); out.append(',');
	// out.append(element.highestLineNumber); out.append(":");
	// out.append(element.offset + 1); out.append(",1\n");
	// }
	// // end section
	// out.append("*E\n");
	// // and finish up...
	// return out.toString();
	// }

	// ---- end JSR45-related stuff

	/** Emit disassembled class and newline to out */
	public static void disassemble(String path, String name, PrintStream out) throws IOException {
		if (null == out) {
			return;
		}
		// out.println("classPath: " + classPath);

		BcelWorld world = new BcelWorld(path);

		UnresolvedType ut = UnresolvedType.forName(name);
		ut.setNeedsModifiableDelegate(true);
		LazyClassGen clazz = new LazyClassGen(BcelWorld.getBcelObjectType(world.resolve(ut)));
		clazz.print(out);
		out.println();
	}

	public String getNewGeneratedNameTag() {
		return Integer.toString(childCounter++);
	}

	// ----

	public LazyClassGen(String class_name, String super_class_name, String file_name, int access_flags, String[] interfaces,
			World world) {
		myGen = new ClassGen(class_name, super_class_name, file_name, access_flags, interfaces);
		cp = myGen.getConstantPool();
		fact = new InstructionFactory(myGen, cp);
		regenerateGenericSignatureAttribute = true;
		this.world = world;
	}

	public void setMajorMinor(int major, int minor) {
		myGen.setMajor(major);
		myGen.setMinor(minor);
	}

	public int getMajor() {
		return myGen.getMajor();
	}

	public int getMinor() {
		return myGen.getMinor();
	}

	// Non child type, so it comes from a real type in the world.
	public LazyClassGen(BcelObjectType myType) {
		myGen = new ClassGen(myType.getJavaClass());
		cp = myGen.getConstantPool();
		fact = new InstructionFactory(myGen, cp);
		this.myType = myType;
		world = myType.getResolvedTypeX().getWorld();

		/* Does this class support serialization */
		if (implementsSerializable(getType())) {
			isSerializable = true;

			// ResolvedMember[] fields = getType().getDeclaredFields();
			// for (int i = 0; i < fields.length; i++) {
			// ResolvedMember field = fields[i];
			// if (field.getName().equals("serialVersionUID")
			// && field.isStatic() && field.getType().equals(UnresolvedType.LONG))
			// {
			// hasSerialVersionUIDField = true;
			// }
			// }
			hasSerialVersionUIDField = hasSerialVersionUIDField(getType());

			ResolvedMember[] methods = getType().getDeclaredMethods();
			for (ResolvedMember method : methods) {
				if (method.getName().equals("<clinit>")) {
					if (method.getKind() != Member.STATIC_INITIALIZATION) {
						throw new RuntimeException("qui?");
					}
					hasClinit = true;
				}
			}

			// Do we need to calculate an SUID and add it?
			if (!getType().isInterface() && !hasSerialVersionUIDField && world.isAddSerialVerUID()) {
				calculatedSerialVersionUID = myGen.getSUID();
				FieldGen fg = new FieldGen(Constants.ACC_PRIVATE | Constants.ACC_FINAL | Constants.ACC_STATIC, BasicType.LONG,
						"serialVersionUID", getConstantPool());
				addField(fg);
				hasSerialVersionUIDField = true;
				serialVersionUIDRequiresInitialization = true;
				// warn about what we've done?
				if (world.getLint().calculatingSerialVersionUID.isEnabled()) {
					world.getLint().calculatingSerialVersionUID.signal(
							new String[] { getClassName(), Long.toString(calculatedSerialVersionUID) + "L" }, null, null);
				}
			}
		}

		ResolvedMember[] methods = myType.getDeclaredMethods();
		for (ResolvedMember method : methods) {
			addMethodGen(new LazyMethodGen((BcelMethod) method, this));
		}

		// Method[] methods = myGen.getMethods();
		// for (int i = 0; i < methods.length; i++) {
		// addMethodGen(new LazyMethodGen(methods[i], this));
		// }

		ResolvedMember[] fields = myType.getDeclaredFields();
		for (ResolvedMember field : fields) {
			this.fields.add((BcelField) field);
		}
	}

	public static boolean hasSerialVersionUIDField(ResolvedType type) {

		ResolvedMember[] fields = type.getDeclaredFields();
		for (ResolvedMember field : fields) {
			if (field.getName().equals("serialVersionUID") && Modifier.isStatic(field.getModifiers())
					&& field.getType().equals(UnresolvedType.LONG)) {
				return true;
			}
		}

		return false;
	}

	// public void addAttribute(Attribute i) {
	// myGen.addAttribute(i);
	// }

	// ----

	public String getInternalClassName() {
		return getConstantPool().getConstantString_CONSTANTClass(myGen.getClassNameIndex());
		// getConstantPool().getConstantString(
		// myGen.getClassNameIndex(),
		// Constants.CONSTANT_Class);

	}

	public String getInternalFileName() {
		String str = getInternalClassName();
		int index = str.lastIndexOf('/');
		if (index == -1) {
			return getFileName();
		} else {
			return str.substring(0, index + 1) + getFileName();
		}
	}

	/**
	 * Returns the packagename - if its the default package we return an empty string
	 */
	public String getPackageName() {
		if (packageName != null) {
			return packageName;
		}
		String str = getInternalClassName();
		int index = str.indexOf("<");
		if (index != -1) {
			str = str.substring(0, index); // strip off the generics guff
		}
		index = str.lastIndexOf("/");
		if (index == -1) {
			return "";
		}
		return str.substring(0, index).replace('/', '.');
	}

	public void addMethodGen(LazyMethodGen gen) {
		// assert gen.getClassName() == super.getClassName();
		methodGens.add(gen);
		if (highestLineNumber < gen.highestLineNumber) {
			highestLineNumber = gen.highestLineNumber;
		}
	}

	public boolean removeMethodGen(LazyMethodGen gen) {
		return methodGens.remove(gen);
	}

	public void addMethodGen(LazyMethodGen gen, ISourceLocation sourceLocation) {
		addMethodGen(gen);
		if (!gen.getMethod().isPrivate()) {
			warnOnAddedMethod(gen.getMethod(), sourceLocation);
		}
	}

	public void errorOnAddedField(FieldGen field, ISourceLocation sourceLocation) {
		if (isSerializable && !hasSerialVersionUIDField) {
			getWorld().getLint().serialVersionUIDBroken.signal(
					new String[] { myType.getResolvedTypeX().getName(), field.getName() }, sourceLocation, null);
		}
	}

	public void warnOnAddedInterface(String name, ISourceLocation sourceLocation) {
		warnOnModifiedSerialVersionUID(sourceLocation, "added interface " + name);
	}

	public void warnOnAddedMethod(Method method, ISourceLocation sourceLocation) {
		warnOnModifiedSerialVersionUID(sourceLocation, "added non-private method " + method.getName());
	}

	public void warnOnAddedStaticInitializer(Shadow shadow, ISourceLocation sourceLocation) {
		if (!hasClinit) {
			warnOnModifiedSerialVersionUID(sourceLocation, "added static initializer");
		}
	}

	public void warnOnModifiedSerialVersionUID(ISourceLocation sourceLocation, String reason) {
		if (isSerializable && !hasSerialVersionUIDField) {
			getWorld().getLint().needsSerialVersionUIDField.signal(new String[] { myType.getResolvedTypeX().getName().toString(),
					reason }, sourceLocation, null);
		}
	}

	public World getWorld() {
		return world;
	}

	public List<LazyMethodGen> getMethodGens() {
		return methodGens; // ???Collections.unmodifiableList(methodGens);
	}

	public List<BcelField> getFieldGens() {
		return fields;
	}

	public boolean fieldExists(String name) {
		//		Field[] allFields = myGen.getFields();
		//		if (allFields!=null) {
		//			for (int i=0;i<allFields.length;i++) {
		//				Field f = allFields[i];
		//				if (f.getName().equals(name)) {
		//					return f;
		//				}
		//			}
		//		}
		for (BcelField f: fields) {
			if (f.getName().equals(name)) {
				return true;
			}
		}
		return false;
	}

	private void writeBack(BcelWorld world) {
		if (getConstantPool().getSize() > Short.MAX_VALUE) {
			reportClassTooBigProblem();
			return;
		}

		if (annotations.size() > 0) {
			for (AnnotationGen element : annotations) {
				myGen.addAnnotation(element);
			}
			// Attribute[] annAttributes =
			// org.aspectj.apache.bcel.classfile.Utility.getAnnotationAttributes(
			// getConstantPool(),annotations);
			// for (int i = 0; i < annAttributes.length; i++) {
			// Attribute attribute = annAttributes[i];
			// System.err.println("Adding attribute for "+attribute);
			// myGen.addAttribute(attribute);
			// }
		}

		// Add a weaver version attribute to the file being produced (if
		// necessary...)
		if (!myGen.hasAttribute("org.aspectj.weaver.WeaverVersion")) {
			myGen.addAttribute(Utility.bcelAttribute(new AjAttribute.WeaverVersionInfo(), getConstantPool()));
		}

		// see 389678: TODO more finessing possible here?
		if (world.isOverWeaving()) {
			if (myGen.hasAttribute(WeaverState.AttributeName) && myType!=null && myType.getWeaverState() != null) {
				myGen.removeAttribute(myGen.getAttribute(WeaverState.AttributeName));
				myGen.addAttribute(Utility.bcelAttribute(new AjAttribute.WeaverState(myType.getWeaverState()), getConstantPool()));
			}
		} else {
			if (!myGen.hasAttribute(WeaverState.AttributeName) && myType != null && myType.getWeaverState() != null) {
				myGen.addAttribute(Utility.bcelAttribute(new AjAttribute.WeaverState(myType.getWeaverState()), getConstantPool()));
			}
		}

		// FIXME ATAJ needed only for slow Aspects.aspectOf() - keep or remove
		// make a lot of test fail since the test compare weaved class file
		// based on some test data as text files...
		// if (!myGen.isInterface()) {
		// addAjClassField();
		// }

		addAjcInitializers();

		// 17Feb05 - ASC - Skip this for now - it crashes IBM 1.4.2 jvms
		// (pr80430). Will be revisited when contents
		// of attribute are confirmed to be correct.
		boolean sourceDebugExtensionSupportSwitchedOn = false;

		if (sourceDebugExtensionSupportSwitchedOn) {
			calculateSourceDebugExtensionOffsets();
		}

		int len = methodGens.size();
		myGen.setMethods(Method.NoMethods);

		for (LazyMethodGen gen : methodGens) {
			// we skip empty clinits
			if (isEmptyClinit(gen)) {
				continue;
			}
			myGen.addMethod(gen.getMethod());
		}

		len = fields.size();
		myGen.setFields(Field.NoFields);
		for (int i = 0; i < len; i++) {
			BcelField gen = fields.get(i);
			myGen.addField(gen.getField(cp));
		}

		if (sourceDebugExtensionSupportSwitchedOn) {
			if (inlinedFiles.size() != 0) {
				if (hasSourceDebugExtensionAttribute(myGen)) {
					world.showMessage(IMessage.WARNING, WeaverMessages.format(WeaverMessages.OVERWRITE_JSR45, getFileName()), null,
							null);
				}
				// myGen.addAttribute(getSourceDebugExtensionAttribute());
			}
		}

		fixupGenericSignatureAttribute();
	}

	/**
	 * When working with Java generics, a signature attribute is attached to the type which indicates how it was declared. This
	 * routine ensures the signature attribute for the class we are about to write out is correct. Basically its responsibilities
	 * are:
	 * <ol>
	 * <li>
	 * Checking whether the attribute needs changing (ie. did weaving change the type hierarchy) - if it did, remove the old
	 * attribute
	 * <li>
	 * Check if we need an attribute at all, are we generic? are our supertypes parameterized/generic?
	 * <li>
	 * Build the new attribute which includes all typevariable, supertype and superinterface information
	 * </ol>
	 */
	private void fixupGenericSignatureAttribute() {

		if (getWorld() != null && !getWorld().isInJava5Mode()) {
			return;
		}

		// TODO asc generics Temporarily assume that types we generate dont need
		// a signature attribute (closure/etc).. will need
		// revisiting no doubt...
		// if (myType == null) {
		// return;
		// }

		// 1. Has anything changed that would require us to modify this
		// attribute?
		if (!regenerateGenericSignatureAttribute) {
			return;
		}

		// 2. Find the old attribute
		Signature sigAttr = null;
		if (myType != null) { // if null, this is a type built from scratch, it
			// won't already have a sig attribute
			sigAttr = (Signature) myGen.getAttribute("Signature");
		}

		// 3. Do we need an attribute?
		boolean needAttribute = false;
		// If we had one before, we definetly still need one as types can't be
		// 'removed' from the hierarchy
		if (sigAttr != null) {
			needAttribute = true;
		}

		// check the interfaces
		if (!needAttribute) {
			if (myType != null) {
				ResolvedType[] interfaceRTXs = myType.getDeclaredInterfaces();
				for (ResolvedType typeX : interfaceRTXs) {
					if (typeX.isGenericType() || typeX.isParameterizedType()) {
						needAttribute = true;
					}
				}
				if (extraSuperInterfaces != null) {
					for (ResolvedType interfaceType : extraSuperInterfaces) {
						if (interfaceType.isGenericType() || interfaceType.isParameterizedType()) {
							needAttribute = true;
						}
					}
				}
			}

			if (myType == null) {
				ResolvedType superclassRTX = superclass;
				if (superclassRTX != null) {
					if (superclassRTX.isGenericType() || superclassRTX.isParameterizedType()) {
						needAttribute = true;
					}
				}
			} else {
				// check the supertype
				ResolvedType superclassRTX = getSuperClass();
				if (superclassRTX.isGenericType() || superclassRTX.isParameterizedType()) {
					needAttribute = true;
				}
			}
		}

		if (needAttribute) {
			StringBuilder signature = new StringBuilder();
			// first, the type variables...
			if (myType != null) {
				TypeVariable[] tVars = myType.getTypeVariables();
				if (tVars.length > 0) {
					signature.append("<");
					for (TypeVariable variable : tVars) {
						signature.append(variable.getSignatureForAttribute());
					}
					signature.append(">");
				}
			}
			// now the supertype
			String supersig = getSuperClass().getSignatureForAttribute();
			signature.append(supersig);
			if (myType != null) {
				ResolvedType[] interfaceRTXs = myType.getDeclaredInterfaces();
				for (ResolvedType interfaceRTX : interfaceRTXs) {
					String s = interfaceRTX.getSignatureForAttribute();
					signature.append(s);
				}
				if (extraSuperInterfaces != null) {
					for (ResolvedType extraSuperInterface : extraSuperInterfaces) {
						String s = extraSuperInterface.getSignatureForAttribute();
						signature.append(s);
					}
				}
			}
			if (sigAttr != null) {
				myGen.removeAttribute(sigAttr);
			}
			myGen.addAttribute(createSignatureAttribute(signature.toString()));
		}
	}

	/**
	 * Helper method to create a signature attribute based on a string signature: e.g. "Ljava/lang/Object;LI<Ljava/lang/Double;>;"
	 */
	private Signature createSignatureAttribute(String signature) {
		int nameIndex = cp.addUtf8("Signature");
		int sigIndex = cp.addUtf8(signature);
		return new Signature(nameIndex, 2, sigIndex, cp);
	}

	/**
	 *
	 */
	private void reportClassTooBigProblem() {
		// PR 59208
		// we've generated a class that is just toooooooooo big (you've been
		// generating programs
		// again haven't you? come on, admit it, no-one writes classes this big
		// by hand).
		// create an empty myGen so that we can give back a return value that
		// doesn't upset the
		// rest of the process.
		myGen = new ClassGen(myGen.getClassName(), myGen.getSuperclassName(), myGen.getFileName(), myGen.getModifiers(),
				myGen.getInterfaceNames());
		// raise an error against this compilation unit.
		getWorld().showMessage(IMessage.ERROR, WeaverMessages.format(WeaverMessages.CLASS_TOO_BIG, this.getClassName()),
				new SourceLocation(new File(myGen.getFileName()), 0), null);
	}

	private static boolean hasSourceDebugExtensionAttribute(ClassGen gen) {
		return gen.hasAttribute("SourceDebugExtension");
	}

	public JavaClass getJavaClass(BcelWorld world) {
		writeBack(world);
		return myGen.getJavaClass();
	}

	public byte[] getJavaClassBytesIncludingReweavable(BcelWorld world) {
		writeBack(world);
		byte[] wovenClassFileData = myGen.getJavaClass().getBytes();
		// At 1.6 stackmaps are optional, whilst at 1.7 and later they
		// are required (unless turning off the verifier)
		if ((myGen.getMajor() == Constants.MAJOR_1_6 && world.shouldGenerateStackMaps()) || myGen.getMajor() > Constants.MAJOR_1_6) {
			if (!AsmDetector.isAsmAround) {
				if (
					AsmDetector.rootCause instanceof ClassNotFoundException ||
						AsmDetector.rootCause instanceof NoClassDefFoundError
				) {
					// Fix https://github.com/eclipse-aspectj/aspectj/issues/251, using "replace('Ä', 'Ö')" to avoid
					// non-relocated class names to be embedded into the error message during compile time, making it end up in
					// the class constant pool unwantedly, as this clashes with post-compile ASM package relocation.
					String errorMessage = "Unable to find ASM classes (" +
						AsmDetector.CLASS_READER.replace('Ä', 'Ö') + ", " + AsmDetector.CLASS_VISITOR.replace('Ä', 'Ö') + ") " +
						"for stackmap generation. Stackmap generation for woven code is required to avoid verify errors " +
						"on a Java 1.7 or higher runtime.";
					throw new BCException(errorMessage, AsmDetector.rootCause);
				}
				throw new BCException("Error processing class file", AsmDetector.rootCause);
			}
			wovenClassFileData = StackMapAdder.addStackMaps(world, myGen.getClassName(), wovenClassFileData);
		}

		WeaverStateInfo wsi = myType.getWeaverState();// getOrCreateWeaverStateInfo();
		if (wsi != null && wsi.isReweavable() && !world.isOverWeaving()) { // && !reweavableDataInserted
			// reweavableDataInserted = true;
			return wsi.replaceKeyWithDiff(wovenClassFileData);
		} else {
			return wovenClassFileData;
		}
	}

	public void addGeneratedInner(LazyClassGen newClass) {
		classGens.add(newClass);
	}

	public void addInterface(ResolvedType newInterface, ISourceLocation sourceLocation) {
		regenerateGenericSignatureAttribute = true;

		if (extraSuperInterfaces == null) {
			extraSuperInterfaces = new ResolvedType[1];
			extraSuperInterfaces[0] = newInterface;
		} else {
			ResolvedType[] x = new ResolvedType[extraSuperInterfaces.length + 1];
			System.arraycopy(extraSuperInterfaces, 0, x, 1, extraSuperInterfaces.length);
			x[0] = newInterface;
			extraSuperInterfaces = x;
		}
		myGen.addInterface(newInterface.getRawName());
		if (!newInterface.equals(UnresolvedType.SERIALIZABLE)) {
			warnOnAddedInterface(newInterface.getName(), sourceLocation);
		}
	}

	public void setSuperClass(ResolvedType newSuperclass) {
		regenerateGenericSignatureAttribute = true;
		superclass = newSuperclass;
		// myType.addParent(typeX); // used for the attribute
		if (newSuperclass.getGenericType() != null) {
			newSuperclass = newSuperclass.getGenericType();
		}
		myGen.setSuperclassName(newSuperclass.getName()); // used in the real
		// class data
	}

	// public String getSuperClassname() {
	// return myGen.getSuperclassName();
	// }

	public ResolvedType getSuperClass() {
		if (superclass != null) {
			return superclass;
		}
		return myType.getSuperclass();
	}

	public String[] getInterfaceNames() {
		return myGen.getInterfaceNames();
	}

	// non-recursive, may be a bug, ha ha.
	private List<LazyClassGen> getClassGens() {
		List<LazyClassGen> ret = new ArrayList<>();
		ret.add(this);
		ret.addAll(classGens);
		return ret;
	}

	public List<UnwovenClassFile.ChildClass> getChildClasses(BcelWorld world) {
		if (classGens.isEmpty()) {
			return Collections.emptyList();
		}
		List<UnwovenClassFile.ChildClass> ret = new ArrayList<>();
		for (LazyClassGen clazz : classGens) {
			byte[] bytes = clazz.getJavaClass(world).getBytes();
			String name = clazz.getName();
			int index = name.lastIndexOf('$');
			// XXX this could be bad, check use of dollar signs.
			name = name.substring(index + 1);
			ret.add(new UnwovenClassFile.ChildClass(name, bytes));
		}
		return ret;
	}

	@Override
	public String toString() {
		return toShortString();
	}

	public String toShortString() {
		String s = org.aspectj.apache.bcel.classfile.Utility.accessToString(myGen.getModifiers(), true);
		if (!s.equals("")) {
			s += " ";
		}
		s += org.aspectj.apache.bcel.classfile.Utility.classOrInterface(myGen.getModifiers());
		s += " ";
		s += myGen.getClassName();
		return s;
	}

	public String toLongString() {
		ByteArrayOutputStream s = new ByteArrayOutputStream();
		print(new PrintStream(s));
		return new String(s.toByteArray());
	}

	public void print() {
		print(System.out);
	}

	public void print(PrintStream out) {
		List<LazyClassGen> classGens = getClassGens();
		for (Iterator<LazyClassGen> iter = classGens.iterator(); iter.hasNext();) {
			LazyClassGen element = iter.next();
			element.printOne(out);
			if (iter.hasNext()) {
				out.println();
			}
		}
	}

	private void printOne(PrintStream out) {
		out.print(toShortString());
		out.print(" extends ");
		out.print(org.aspectj.apache.bcel.classfile.Utility.compactClassName(myGen.getSuperclassName(), false));

		int size = myGen.getInterfaces().length;

		if (size > 0) {
			out.print(" implements ");
			for (int i = 0; i < size; i++) {
				out.print(myGen.getInterfaceNames()[i]);
				if (i < size - 1) {
					out.print(", ");
				}
			}
		}
		out.print(":");
		out.println();
		// XXX make sure to pass types correctly around, so this doesn't happen.
		if (myType != null) {
			myType.printWackyStuff(out);
		}
		Field[] fields = myGen.getFields();
		for (Field field : fields) {
			out.print("  ");
			out.println(field);
		}
		List<LazyMethodGen> methodGens = getMethodGens();
		for (Iterator<LazyMethodGen> iter = methodGens.iterator(); iter.hasNext();) {
			LazyMethodGen gen = iter.next();
			// we skip empty clinits
			if (isEmptyClinit(gen)) {
				continue;
			}
			gen.print(out, (myType != null ? myType.getWeaverVersionAttribute() : WeaverVersionInfo.UNKNOWN));
			if (iter.hasNext()) {
				out.println();
			}
		}
		// out.println("  ATTRIBS: " + Arrays.asList(myGen.getAttributes()));

		out.println("end " + toShortString());
	}

	private boolean isEmptyClinit(LazyMethodGen gen) {

		if (!gen.getName().equals("<clinit>")) {
			return false;
		}
		// System.err.println("checking clinig: " + gen);
		InstructionHandle start = gen.getBody().getStart();
		while (start != null) {
			if (Range.isRangeHandle(start) || (start.getInstruction().opcode == Constants.RETURN)) {
				start = start.getNext();
			} else {
				return false;
			}
		}

		return true;
	}

	public ConstantPool getConstantPool() {
		return cp;
	}

	public String getName() {
		return myGen.getClassName();
	}

	public boolean isWoven() {
		return myType.getWeaverState() != null;
	}

	public boolean isReweavable() {
		if (myType.getWeaverState() == null) {
			return true;
		}
		return myType.getWeaverState().isReweavable();
	}

	public Set<String> getAspectsAffectingType() {
		if (myType.getWeaverState() == null) {
			return null;
		}
		return myType.getWeaverState().getAspectsAffectingType();
	}

	public WeaverStateInfo getOrCreateWeaverStateInfo(boolean inReweavableMode) {
		WeaverStateInfo ret = myType.getWeaverState();
		if (ret != null) {
			return ret;
		}
		ret = new WeaverStateInfo(inReweavableMode);
		myType.setWeaverState(ret);
		return ret;
	}

	public InstructionFactory getFactory() {
		return fact;
	}

	public LazyMethodGen getStaticInitializer() {
		for (LazyMethodGen gen : methodGens) {
			// OPTIMIZE persist kind of member into the gen object? for clinit
			if (gen.getName().equals("<clinit>")) {
				return gen;
			}
		}
		LazyMethodGen clinit = new LazyMethodGen(Modifier.STATIC, Type.VOID, "<clinit>", Type.NO_ARGS, NO_STRINGS, this);
		clinit.getBody().insert(InstructionConstants.RETURN);
		methodGens.add(clinit);
		return clinit;
	}

	/**
	 * Retrieve the ajc$preClinit method - this method captures any initialization AspectJ wants to ensure happens in a class. It is
	 * called from the static initializer. Maintaining this separation enables overweaving to ignore join points added due to
	 * earlier weaves. If the ajc$preClinit method cannot be found, it is created and a call to it is placed in the real static
	 * initializer (the call is placed at the start of the static initializer).
	 *
	 * @return the LazyMethodGen representing the ajc$ clinit
	 */
	public LazyMethodGen getAjcPreClinit() {
		if (this.isInterface()) {
			throw new IllegalStateException();
		}
		for (LazyMethodGen methodGen : methodGens) {
			if (methodGen.getName().equals(NameMangler.AJC_PRE_CLINIT_NAME)) {
				return methodGen;
			}
		}
		LazyMethodGen ajcPreClinit = new LazyMethodGen(Modifier.PRIVATE | Modifier.STATIC, Type.VOID,
				NameMangler.AJC_PRE_CLINIT_NAME, Type.NO_ARGS, NO_STRINGS, this);
		ajcPreClinit.getBody().insert(InstructionConstants.RETURN);
		methodGens.add(ajcPreClinit);
		InstructionList clinitBody = getStaticInitializer().getBody();
		clinitBody.insert(Utility.createInvoke(fact, ajcPreClinit));
		if (serialVersionUIDRequiresInitialization) {
			InstructionList il = new InstructionList();
			il.append(InstructionFactory.PUSH(getConstantPool(), calculatedSerialVersionUID));
			il.append(getFactory().createFieldAccess(getClassName(), "serialVersionUID", BasicType.LONG,
					Constants.PUTSTATIC));
			clinitBody.insert(il);
		}
		return ajcPreClinit;
	}

	/**
	 * factory method for building multiple extended clinit methods. Constructs a new clinit method that invokes the previous one
	 * and then returns it. The index is used as a name suffix.
	 *
	 * @param previousPreClinit
	 * @param i
	 */
	public LazyMethodGen createExtendedAjcPreClinit(LazyMethodGen previousPreClinit, int i) {
		LazyMethodGen ajcPreClinit = new LazyMethodGen(Modifier.PRIVATE | Modifier.STATIC, Type.VOID,
				NameMangler.AJC_PRE_CLINIT_NAME + i, Type.NO_ARGS, NO_STRINGS, this);
		ajcPreClinit.getBody().insert(InstructionConstants.RETURN);
		methodGens.add(ajcPreClinit);
		previousPreClinit.getBody().insert(Utility.createInvoke(fact, ajcPreClinit));
		return ajcPreClinit;
	}

	//

	// reflective thisJoinPoint support
	private Map<BcelShadow, Field> tjpFields = new HashMap<>();
	Map<CacheKey, Field> annotationCachingFieldCache = new HashMap<>();
	private int tjpFieldsCounter = -1; // -1 means not yet initialized
	private int annoFieldsCounter = 0;
	public static final ObjectType proceedingTjpType = new ObjectType("org.aspectj.lang.ProceedingJoinPoint");
	public static final ObjectType tjpType = new ObjectType("org.aspectj.lang.JoinPoint");
	public static final ObjectType staticTjpType = new ObjectType("org.aspectj.lang.JoinPoint$StaticPart");
	public static final ObjectType typeForAnnotation = new ObjectType("java.lang.annotation.Annotation");
	public static final ObjectType enclosingStaticTjpType = new ObjectType("org.aspectj.lang.JoinPoint$EnclosingStaticPart");
	private static final ObjectType sigType = new ObjectType("org.aspectj.lang.Signature");
	// private static final ObjectType slType =
	// new ObjectType("org.aspectj.lang.reflect.SourceLocation");
	private static final ObjectType factoryType = new ObjectType("org.aspectj.runtime.reflect.Factory");
	private static final ObjectType classType = new ObjectType("java.lang.Class");

	public Field getTjpField(BcelShadow shadow, final boolean isEnclosingJp) {
		Field tjpField = tjpFields.get(shadow);
		if (tjpField != null) {
			return tjpField;
		}

		int modifiers = Modifier.STATIC;

		// J9: Can't always be final on Java 9 because it is set outside of clinit
		// But must be final in interface
		if (shadow.getEnclosingClass().isInterface()) {
			modifiers |= Modifier.FINAL;
		}

		// XXX - Do we ever inline before or after advice? If we do, then we
		// better include them in the check below. (or just change it to
		// shadow.getEnclosingMethod().getCanInline())

		// If the enclosing method is around advice, we could inline the join
		// point that has led to this shadow. If we do that then the TJP we are
		// creating here must be PUBLIC so it is visible to the type in which the
		// advice is inlined. (PR71377)
		LazyMethodGen encMethod = shadow.getEnclosingMethod();
		boolean shadowIsInAroundAdvice = false;
		if (encMethod != null && encMethod.getName().startsWith(NameMangler.PREFIX + "around")) {
			shadowIsInAroundAdvice = true;
		}

		if (getType().isInterface() || shadowIsInAroundAdvice) {
			modifiers |= Modifier.PUBLIC;
		} else {
			modifiers |= Modifier.PRIVATE;
		}
		ObjectType jpType = null;
		// Did not have different static joinpoint types in 1.2
		if (world.isTargettingAspectJRuntime12()) {
			jpType = staticTjpType;
		} else {
			jpType = isEnclosingJp ? enclosingStaticTjpType : staticTjpType;
		}
		if (tjpFieldsCounter == -1) {
			// not yet initialized, do it now
			if (!world.isOverWeaving()) {
				tjpFieldsCounter = 0;
			} else {
				List<BcelField> existingFields = getFieldGens();
				if (existingFields == null) {
					tjpFieldsCounter = 0;
				} else {
					BcelField lastField = null;
					// OPTIMIZE: go from last to first?
					for (BcelField field : existingFields) {
						if (field.getName().startsWith("ajc$tjp_")) {
							lastField = field;
						}
					}
					if (lastField == null) {
						tjpFieldsCounter = 0;
					} else {
						tjpFieldsCounter = Integer.parseInt(lastField.getName().substring(8)) + 1;
						// System.out.println("tjp counter starting at " + tjpFieldsCounter);
					}
				}
			}
		}
		if (!isInterface() && world.isTransientTjpFields()) {
			modifiers|=Modifier.TRANSIENT;
		}
		FieldGen fGen = new FieldGen(modifiers, jpType, "ajc$tjp_" + tjpFieldsCounter++, getConstantPool());
		addField(fGen);
		tjpField = fGen.getField();
		tjpFields.put(shadow, tjpField);
		return tjpField;
	}

	/**
	 * Create a field in the type containing the shadow where the annotation retrieved during binding can be stored - for later fast
	 * access.
	 *
	 * @param shadow the shadow at which the @annotation result is being cached
	 * @return a field
	 */
	public Field getAnnotationCachingField(BcelShadow shadow, ResolvedType toType, boolean isWithin) {
		// Multiple annotation types at a shadow. A different field would be required for each
		CacheKey cacheKey = new CacheKey(shadow, toType, isWithin);
		Field field = annotationCachingFieldCache.get(cacheKey);
		// System.out.println(field + " for shadow " + shadow);
		if (field == null) {
			// private static Annotation ajc$anno$<nnn>
			StringBuilder sb = new StringBuilder();
			sb.append(NameMangler.ANNOTATION_CACHE_FIELD_NAME);
			sb.append(annoFieldsCounter++);
			FieldGen annotationCacheField = new FieldGen(Modifier.PRIVATE | Modifier.STATIC, typeForAnnotation, sb.toString(), cp);
			addField(annotationCacheField);
			field = annotationCacheField.getField();
			annotationCachingFieldCache.put(cacheKey, field);
		}
		return field;
	}

	static class CacheKey {
		private Object key;
		private ResolvedType annotationType;

		// If the annotation is being accessed via @annotation on a shadow then we can use the shadows toString() (so two shadows
		// the same share a variable), but if it is @withincode() or @within() we can't share them (as the shadows may look the same
		// but be occurring 'within' different things). In the within cases we continue to use the shadow itself as the key.
		CacheKey(BcelShadow shadow, ResolvedType annotationType, boolean isWithin) {
			this.key = isWithin ? shadow : shadow.toString();
			this.annotationType = annotationType;
		}

		@Override
		public int hashCode() {
			return key.hashCode() * 37 + annotationType.hashCode();
		}

		@Override
		public boolean equals(Object other) {
			if (!(other instanceof CacheKey)) {
				return false;
			}
			CacheKey oCacheKey = (CacheKey) other;
			return key.equals(oCacheKey.key) && annotationType.equals(oCacheKey.annotationType);
		}
	}

	// FIXME ATAJ needed only for slow Aspects.aspectOf - keep or remove
	// private void addAjClassField() {
	// // Andy: Why build it again??
	// Field ajClassField = new FieldGen(
	// Modifier.PRIVATE | Modifier.FINAL | Modifier.STATIC,
	// classType,
	// "aj$class",
	// getConstantPool()).getField();
	// addField(ajClassField);
	//
	// InstructionList il = new InstructionList();
	// il.append(new PUSH(getConstantPool(), getClassName()));
	// il.append(fact.createInvoke("java.lang.Class", "forName", classType,
	// new Type[] {Type.STRING}, Constants.INVOKESTATIC));
	// il.append(fact.createFieldAccess(getClassName(), ajClassField.getName(),
	// classType, Constants.PUTSTATIC));
	//
	// getStaticInitializer().getBody().insert(il);
	// }

	private void addAjcInitializers() {
		if (tjpFields.size() == 0 && !serialVersionUIDRequiresInitialization) {
			return;
		}

		InstructionList[] il = initializeAllTjps();

		LazyMethodGen prevMethod;
		LazyMethodGen nextMethod = null;
		if (this.isInterface()) { // Cannot sneak stuff into another static method in an interface
			prevMethod = getStaticInitializer();
		} else {
			prevMethod = getAjcPreClinit();
		}
		for (int counter = 1; counter <= il.length; counter++) {
			if (il.length > counter) {
				nextMethod = createExtendedAjcPreClinit(prevMethod, counter);
			}
			prevMethod.getBody().insert(il[counter - 1]);
			prevMethod = nextMethod;
		}
	}

	private InstructionList initInstructionList() {
		InstructionList list = new InstructionList();
		InstructionFactory fact = getFactory();

		// make a new factory
		list.append(fact.createNew(factoryType));
		list.append(InstructionFactory.createDup(1));

		list.append(InstructionFactory.PUSH(getConstantPool(), getFileName()));

		// load the current Class object
		// XXX check that this works correctly for inners/anonymous
		list.append(fact.PUSHCLASS(cp, myGen.getClassName()));
		// XXX do we need to worry about the fact the theorectically this could
		// throw
		// a ClassNotFoundException

		list.append(fact.createInvoke(factoryType.getClassName(), "<init>", Type.VOID, new Type[] { Type.STRING, classType },
				Constants.INVOKESPECIAL));

		list.append(InstructionFactory.createStore(factoryType, 0));
		return list;
	}

	private InstructionList[] initializeAllTjps() {
		Vector<InstructionList> lists = new Vector<>();

		InstructionList list = initInstructionList();
		lists.add(list);

		List<Map.Entry<BcelShadow, Field>> entries = new ArrayList<>(tjpFields.entrySet());
		entries.sort(new Comparator<Map.Entry<BcelShadow, Field>>() {
			@Override
			public int compare(Map.Entry<BcelShadow, Field> a, Map.Entry<BcelShadow, Field> b) {
				return (a.getValue()).getName().compareTo((b.getValue()).getName());
			}
		});

		long estimatedSize = 0;
		for (Map.Entry<BcelShadow, Field> entry : entries) {
			if (estimatedSize > Constants.MAX_CODE_SIZE) {
				estimatedSize = 0;
				list = initInstructionList();
				lists.add(list);
			}
			estimatedSize += entry.getValue().getSignature().getBytes().length;
			initializeTjp(fact, list, entry.getValue(), entry.getKey());
		}
		InstructionList listArrayModel[] = new InstructionList[1];
		return lists.toArray(listArrayModel);
	}

	private void initializeTjp(InstructionFactory fact, InstructionList list, Field field, BcelShadow shadow) {
		if (world.getTargetAspectjRuntimeLevel() == RuntimeVersion.V1_9) {
			initializeTjpOptimal(fact, list, field, shadow);
			return;
		}
		boolean fastSJP = false;
		// avoid fast SJP if it is for an enclosing joinpoint
		boolean isFastSJPAvailable = shadow.getWorld().isTargettingRuntime1_6_10()
				&& !enclosingStaticTjpType.equals(field.getType());

		Member sig = shadow.getSignature();

		// load the factory
		list.append(InstructionFactory.createLoad(factoryType, 0));

		// load the kind
		list.append(InstructionFactory.PUSH(getConstantPool(), shadow.getKind().getName()));

		// create the signature
		if (world.isTargettingAspectJRuntime12() || !isFastSJPAvailable || !sig.getKind().equals(Member.METHOD)) {
			list.append(InstructionFactory.createLoad(factoryType, 0));
		}

		String signatureMakerName = SignatureUtils.getSignatureMakerName(sig);
		ObjectType signatureType = new ObjectType(SignatureUtils.getSignatureType(sig));
		UnresolvedType[] exceptionTypes = null;
		if (world.isTargettingAspectJRuntime12()) { // TAG:SUPPORTING12: We didn't have optimized factory methods in 1.2
			list.append(InstructionFactory.PUSH(cp, SignatureUtils.getSignatureString(sig, shadow.getWorld())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY1,
					Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.METHOD)) {
			BcelWorld w = shadow.getWorld();

			// For methods, push the parts of the signature on.
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getModifiers(w))));
			list.append(InstructionFactory.PUSH(cp, sig.getName()));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterTypes())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterNames(w))));
			exceptionTypes = sig.getExceptions(w);
			if (isFastSJPAvailable && exceptionTypes.length == 0) {
				fastSJP = true;
			} else {
				list.append(InstructionFactory.PUSH(cp, makeString(exceptionTypes)));
			}
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getReturnType())));
			// And generate a call to the variant of makeMethodSig() that takes the strings
			if (isFastSJPAvailable) {
				fastSJP = true;
			} else {
				list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY7,
						Constants.INVOKEVIRTUAL));
			}

		} else if (sig.getKind().equals(Member.MONITORENTER)) {
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY1,
					Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.MONITOREXIT)) {
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY1,
					Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.HANDLER)) {
			BcelWorld w = shadow.getWorld();
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterTypes())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterNames(w))));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY3,
					Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.CONSTRUCTOR)) {
			BcelWorld w = shadow.getWorld();
			if (w.isJoinpointArrayConstructionEnabled() && sig.getDeclaringType().isArray()) {
				// its the magical new jp
				list.append(InstructionFactory.PUSH(cp, makeString(Modifier.PUBLIC)));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterTypes())));
				list.append(InstructionFactory.PUSH(cp, "")); // sig.getParameterNames?
				list.append(InstructionFactory.PUSH(cp, ""));// sig.getExceptions?
				list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY5,
						Constants.INVOKEVIRTUAL));
			} else {
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getModifiers(w))));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterTypes())));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterNames(w))));
				list.append(InstructionFactory.PUSH(cp, makeString(sig.getExceptions(w))));
				list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY5,
						Constants.INVOKEVIRTUAL));
			}
		} else if (sig.getKind().equals(Member.FIELD)) {
			BcelWorld w = shadow.getWorld();
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getModifiers(w))));
			list.append(InstructionFactory.PUSH(cp, sig.getName()));
			// see pr227401
			UnresolvedType dType = sig.getDeclaringType();
			if (dType.getTypekind() == TypeKind.PARAMETERIZED || dType.getTypekind() == TypeKind.GENERIC) {
				dType = sig.getDeclaringType().resolve(world).getGenericType();
			}
			list.append(InstructionFactory.PUSH(cp, makeString(dType)));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getReturnType())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY4,
					Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.ADVICE)) {
			BcelWorld w = shadow.getWorld();
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getModifiers(w))));
			list.append(InstructionFactory.PUSH(cp, sig.getName()));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterTypes())));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getParameterNames(w))));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getExceptions(w))));
			list.append(InstructionFactory.PUSH(cp, makeString((sig.getReturnType()))));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, new Type[] { Type.STRING,
					Type.STRING, Type.STRING, Type.STRING, Type.STRING, Type.STRING, Type.STRING }, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.STATIC_INITIALIZATION)) {
			BcelWorld w = shadow.getWorld();
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getModifiers(w))));
			list.append(InstructionFactory.PUSH(cp, makeString(sig.getDeclaringType())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY2,
					Constants.INVOKEVIRTUAL));
		} else {
			// TODO looks like this block is unused code
			list.append(InstructionFactory.PUSH(cp, SignatureUtils.getSignatureString(sig, shadow.getWorld())));
			list.append(fact.createInvoke(factoryType.getClassName(), signatureMakerName, signatureType, Type.STRINGARRAY1,
					Constants.INVOKEVIRTUAL));
		}

		// XXX should load source location from shadow
		list.append(Utility.createConstant(fact, shadow.getSourceLine()));

		final String factoryMethod;

		// TAG:SUPPORTING12: We didn't have makeESJP() in 1.2
		if (world.isTargettingAspectJRuntime12()) {
			list.append(fact.createInvoke(factoryType.getClassName(), "makeSJP", staticTjpType, new Type[] { Type.STRING, sigType,
					Type.INT }, Constants.INVOKEVIRTUAL));

			// put it in the field
			list.append(fact.createFieldAccess(getClassName(), field.getName(), staticTjpType, Constants.PUTSTATIC));

		} else {
			if (staticTjpType.equals(field.getType())) {
				factoryMethod = "makeSJP";
			} else if (enclosingStaticTjpType.equals(field.getType())) {
				factoryMethod = "makeESJP";
			} else {
				throw new Error("should not happen");
			}

			if (fastSJP) {
				if (exceptionTypes != null && exceptionTypes.length != 0) {
					list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(), ARRAY_8STRING_INT,
							Constants.INVOKEVIRTUAL));
				} else {
					list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(), ARRAY_7STRING_INT,
							Constants.INVOKEVIRTUAL));
				}
			} else {
				list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(), new Type[] { Type.STRING,
						sigType, Type.INT }, Constants.INVOKEVIRTUAL));
			}

			// put it in the field
			list.append(fact.createFieldAccess(getClassName(), field.getName(), field.getType(), Constants.PUTSTATIC));
		}
	}

	public String getFactoryMethod(Field field, BcelShadow shadow) {
		StringBuilder b = new StringBuilder();
		b.append("make");
		MemberKind kind = shadow.getSignature().getKind();
		if (kind.equals(Member.METHOD)) {
			b.append("Method");
		} else if (kind.equals(Member.CONSTRUCTOR)) {
			b.append("Constructor");
		} else if (kind.equals(Member.HANDLER)) {
			b.append("CatchClause");
		} else if (kind.equals(Member.FIELD)) {
			b.append("Field");
		} else if (kind.equals(Member.STATIC_INITIALIZATION)) {
			b.append("Initializer");
		} else if (kind.equals(Member.MONITORENTER)) {
			b.append("Lock");
		} else if (kind.equals(Member.MONITOREXIT)) {
			b.append("Unlock");
		} else if (kind.equals(Member.ADVICE)) {
			b.append("Advice");
		} else {
			throw new IllegalStateException(kind.toString());
		}
		if (staticTjpType.equals(field.getType())) {
			b.append("SJP");
		} else if (enclosingStaticTjpType.equals(field.getType())) {
			b.append("ESJP");
		}
		return b.toString();
	}

	/**
	 * Generate optimal joinpoint initialization code.
	 *
	 * As of version 1.9.1 the runtime includes new factory methods for joinpoints that take classes, not strings
	 * and using them requires different code generation. Using these instead of the old ones means we can avoid
	 * deferred classloading for these types. By using the LDC instruction that loads classes, it also means
	 * anything modifying woven code and changing type names will also pick up on these references.
	 */
	private void initializeTjpOptimal(InstructionFactory fact, InstructionList list, Field field, BcelShadow shadow) {
		list.append(InstructionFactory.createLoad(factoryType, 0));
		pushString(list, shadow.getKind().getName());
		String factoryMethod = getFactoryMethod(field, shadow);
		Member sig = shadow.getSignature();
		BcelWorld w = shadow.getWorld();

		if (sig.getKind().equals(Member.METHOD)) {
			pushInt(list, sig.getModifiers(w));
			pushString(list, sig.getName());
			pushClass(list, sig.getDeclaringType());
			pushClasses(list, sig.getParameterTypes());
			pushStrings(list, sig.getParameterNames(w));
			pushClasses(list, sig.getExceptions(w));
			pushClass(list, sig.getReturnType());
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_METHOD, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.CONSTRUCTOR)) {
			if (w.isJoinpointArrayConstructionEnabled() && sig.getDeclaringType().isArray()) {
				pushInt(list, Modifier.PUBLIC);
				pushClass(list, sig.getDeclaringType());
				pushClasses(list, sig.getParameterTypes());
				pushStrings(list, null);
				pushClasses(list, null);
			} else {
				pushInt(list, sig.getModifiers(w));
				pushClass(list, sig.getDeclaringType());
				pushClasses(list, sig.getParameterTypes());
				pushStrings(list, sig.getParameterNames(w));
				pushClasses(list, sig.getExceptions(w));
			}
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_CONSTRUCTOR, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.HANDLER)) {
			pushClass(list, sig.getDeclaringType());
			pushClass(list, sig.getParameterTypes()[0]);
			String pname = null;
			String[] pnames = sig.getParameterNames(w);
			if (pnames != null && pnames.length>0) {
				pname = pnames[0];
			}
			pushString(list, pname);
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_CATCHCLAUSE, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.FIELD)) {
			pushInt(list, sig.getModifiers(w));
			pushString(list, sig.getName());
			// see pr227401
			UnresolvedType dType = sig.getDeclaringType();
			if (dType.getTypekind() == TypeKind.PARAMETERIZED || dType.getTypekind() == TypeKind.GENERIC) {
				dType = sig.getDeclaringType().resolve(world).getGenericType();
			}
			pushClass(list, dType);
			pushClass(list, sig.getReturnType());
			pushInt(list,shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_FIELD, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.STATIC_INITIALIZATION)) {
			pushInt(list, sig.getModifiers(w));
			pushClass(list, sig.getDeclaringType());
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_INITIALIZER, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.MONITORENTER)) {
			pushClass(list, sig.getDeclaringType());
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_MONITOR, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.MONITOREXIT)) {
			pushClass(list, sig.getDeclaringType());
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_MONITOR, Constants.INVOKEVIRTUAL));
		} else if (sig.getKind().equals(Member.ADVICE)) {
			pushInt(list, sig.getModifiers(w));
			pushString(list, sig.getName());
			pushClass(list, sig.getDeclaringType());
			pushClasses(list, sig.getParameterTypes());
			pushStrings(list, sig.getParameterNames(w));
			pushClasses(list, sig.getExceptions(w));
			pushClass(list, sig.getReturnType());
			pushInt(list, shadow.getSourceLine());
			list.append(fact.createInvoke(factoryType.getClassName(), factoryMethod, field.getType(),
					PARAMSIGNATURE_MAKESJP_ADVICE, Constants.INVOKEVIRTUAL));
		} else {
			throw new IllegalStateException("not sure what to do: "+shadow);
		}
		list.append(fact.createFieldAccess(getClassName(), field.getName(), field.getType(), Constants.PUTSTATIC));
	}

	private void pushStrings(InstructionList list, String[] strings) {
		// Build an array loaded with the strings
		if (strings == null || strings.length == 0) {
			list.append(InstructionFactory.ACONST_NULL);
		} else {
			list.append(InstructionFactory.PUSH(cp, strings.length));
			list.append(fact.createNewArray(Type.STRING, (short)1));
			for (int s=0;s<strings.length;s++) {
				list.append(InstructionFactory.DUP);
				list.append(InstructionFactory.PUSH(cp, s));
				list.append(InstructionFactory.PUSH(cp, strings[s]));
				list.append(InstructionFactory.AASTORE);
			}
		}
	}

	private void pushClass(InstructionList list, UnresolvedType type) {
		if (type.isPrimitiveType()) {
			if (type.getSignature().equals("I")) {
				list.append(fact.createGetStatic("java/lang/Integer","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("D")) {
				list.append(fact.createGetStatic("java/lang/Double","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("S")) {
				list.append(fact.createGetStatic("java/lang/Short","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("J")) {
				list.append(fact.createGetStatic("java/lang/Long","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("F")) {
				list.append(fact.createGetStatic("java/lang/Float","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("C")) {
				list.append(fact.createGetStatic("java/lang/Character","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("B")) {
				list.append(fact.createGetStatic("java/lang/Byte","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("Z")) {
				list.append(fact.createGetStatic("java/lang/Boolean","TYPE", Type.CLASS));
			} else if (type.getSignature().equals("V")) {
				list.append(InstructionFactory.ACONST_NULL);
			}
			return;
		}
		String classString = makeLdcClassString(type);
		if (classString == null) {
			list.append(InstructionFactory.ACONST_NULL);
		} else {
			list.append(fact.PUSHCLASS(cp, classString));
		}
	}

	private void pushClasses(InstructionList list, UnresolvedType[] types) {
		// Build an array loaded with the class objects
		if (types == null || types.length == 0) {
			list.append(InstructionFactory.ACONST_NULL);
		} else {
			list.append(InstructionFactory.PUSH(cp, types.length));
			list.append(fact.createNewArray(Type.CLASS, (short)1));
			for (int t=0;t<types.length;t++) {
				list.append(InstructionFactory.DUP);
				list.append(InstructionFactory.PUSH(cp, t));
				pushClass(list, types[t]);
				list.append(InstructionFactory.AASTORE);
			}
		}
	}

	private final void pushString(InstructionList list, String string) {
		list.append(InstructionFactory.PUSH(cp, string));
	}

	private final void pushInt(InstructionList list, int value) {
		list.append(InstructionFactory.PUSH(cp, value));
	}

	protected String makeString(int i) {
		return Integer.toString(i, 16); // ??? expensive
	}

	protected String makeString(UnresolvedType t) {
		// this is the inverse of the odd behavior for Class.forName w/ arrays
		if (t.isArray()) {
			// this behavior matches the string used by the eclipse compiler for
			// Foo.class literals
			return t.getSignature().replace('/', '.');
		} else {
			if (t.isParameterizedType()) {
				return t.getRawType().getName();
			} else {
				return t.getName();
			}
		}
	}

	protected String makeLdcClassString(UnresolvedType type) {
		if (type.isVoid() || type.isPrimitiveType()) {
			return null;
		}
		if (type.isArray()) {
			return type.getSignature();
		} else {
			if (type.isParameterizedType()) {
				type = type.getRawType();
			}
			String signature = type.getSignature();
			if (signature.length() ==1 ) {
				return signature;
			}
			return signature.substring(1,signature.length()-1);
		}
	}

	protected String makeString(UnresolvedType[] types) {
		if (types == null) {
			return "";
		}
		StringBuilder buf = new StringBuilder();
		for (int i = 0, len = types.length; i < len; i++) {
			if (i > 0) {
				buf.append(':');
			}
			buf.append(makeString(types[i]));
		}
		return buf.toString();
	}

	protected String makeString(String[] names) {
		if (names == null) {
			return "";
		}
		StringBuilder buf = new StringBuilder();
		for (int i = 0, len = names.length; i < len; i++) {
			if (i > 0) {
				buf.append(':');
			}
			buf.append(names[i]);
		}
		return buf.toString();
	}

	public ResolvedType getType() {
		if (myType == null) {
			return null;
		}
		return myType.getResolvedTypeX();
	}

	public BcelObjectType getBcelObjectType() {
		return myType;
	}

	public String getFileName() {
		return myGen.getFileName();
	}

	// for *new* fields
	private void addField(FieldGen field) {
		makeSyntheticAndTransientIfNeeded(field);
		BcelField bcelField = null;
		if (getBcelObjectType() != null) {
			bcelField = new BcelField(getBcelObjectType(), field.getField());
		} else {
			bcelField = new BcelField(getName(), field.getField(), world);
		}
		fields.add(bcelField);
		// myGen.addField(field.getField());
	}

	private void makeSyntheticAndTransientIfNeeded(FieldGen field) {
		if (field.getName().startsWith(NameMangler.PREFIX) && !field.getName().startsWith("ajc$interField$")
				&& !field.getName().startsWith("ajc$instance$")) {
			// it's an aj added field
			// first do transient
			if (!field.isStatic()) {
				field.setModifiers(field.getModifiers() | Constants.ACC_TRANSIENT);
			}
			// then do synthetic
			if (getWorld().isInJava5Mode()) {
				// add the synthetic modifier flag
				field.setModifiers(field.getModifiers() | ACC_SYNTHETIC);
			}
			if (!hasSyntheticAttribute(field.getAttributes())) {
				// belt and braces, do the attribute even on Java 5 in addition
				// to the modifier flag
				// Attribute[] oldAttrs = field.getAttributes();
				// Attribute[] newAttrs = new Attribute[oldAttrs.length + 1];
				// System.arraycopy(oldAttrs, 0, newAttrs, 0, oldAttrs.length);
				ConstantPool cpg = myGen.getConstantPool();
				int index = cpg.addUtf8("Synthetic");
				Attribute synthetic = new Synthetic(index, 0, new byte[0], cpg);
				field.addAttribute(synthetic);
				// newAttrs[newAttrs.length - 1] = synthetic;
				// field.setAttributes(newAttrs);
			}
		}
	}

	private boolean hasSyntheticAttribute(List<Attribute> attributes) {
		for (Attribute attribute : attributes) {
			if (attribute.getName().equals("Synthetic")) {
				return true;
			}
		}
		return false;
	}

	public void addField(FieldGen field, ISourceLocation sourceLocation) {
		addField(field);
		if (!(field.isPrivate() && (field.isStatic() || field.isTransient()))) {
			errorOnAddedField(field, sourceLocation);
		}
	}

	public String getClassName() {
		return myGen.getClassName();
	}

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

	public boolean isAbstract() {
		return myGen.isAbstract();
	}

	public LazyMethodGen getLazyMethodGen(Member m) {
		return getLazyMethodGen(m.getName(), m.getSignature(), false);
	}

	public LazyMethodGen getLazyMethodGen(String name, String signature) {
		return getLazyMethodGen(name, signature, false);
	}

	public LazyMethodGen getLazyMethodGen(String name, String signature, boolean allowMissing) {
		for (LazyMethodGen gen : methodGens) {
			if (gen.getName().equals(name) && gen.getSignature().equals(signature)) {
				return gen;
			}
		}

		if (!allowMissing) {
			throw new BCException("Class " + this.getName() + " does not have a method " + name + " with signature " + signature);
		}

		return null;
	}

	public void forcePublic() {
		myGen.setModifiers(Utility.makePublic(myGen.getModifiers()));
	}

	public boolean hasAnnotation(UnresolvedType t) {

		// annotations on the real thing
		AnnotationGen agens[] = myGen.getAnnotations();
		if (agens == null) {
			return false;
		}
		for (AnnotationGen gen : agens) {
			if (t.equals(UnresolvedType.forSignature(gen.getTypeSignature()))) {
				return true;
			}
		}

		// annotations added during this weave

		return false;
	}

	public void addAnnotation(AnnotationGen a) {
		if (!hasAnnotation(UnresolvedType.forSignature(a.getTypeSignature()))) {
			annotations.add(new AnnotationGen(a, getConstantPool(), true));
		}
	}

	public void addAttribute(AjAttribute attribute) {
		myGen.addAttribute(Utility.bcelAttribute(attribute, getConstantPool()));
	}

	public void addAttribute(Attribute attribute) {
		myGen.addAttribute(attribute);
	}

	public Collection<Attribute> getAttributes() {
		return myGen.getAttributes();
	}

	// this test is like asking:
	// if
	// (UnresolvedType.SERIALIZABLE.resolve(getType().getWorld()).isAssignableFrom
	// (getType())) {
	// only we don't do that because this forces us to find all the supertypes
	// of the type,
	// and if one of them is missing we fail, and it's not worth failing just to
	// put out
	// a warning message!
	private boolean implementsSerializable(ResolvedType aType) {
		if (aType.getSignature().equals(UnresolvedType.SERIALIZABLE.getSignature())) {
			return true;
		}

		ResolvedType[] interfaces = aType.getDeclaredInterfaces();
		for (ResolvedType anInterface : interfaces) {
			if (anInterface.isMissing()) {
				continue;
			}
			if (implementsSerializable(anInterface)) {
				return true;
			}
		}
		ResolvedType superType = aType.getSuperclass();
		if (superType != null && !superType.isMissing()) {
			return implementsSerializable(superType);
		}
		return false;
	}

	public boolean isAtLeastJava5() {
		return (myGen.getMajor() >= Constants.MAJOR_1_5);
	}

	/**
	 * Return the next available field name with the specified 'prefix', e.g. for prefix 'class$' where class$0, class$1 exist then
	 * return class$2
	 */
	public String allocateField(String prefix) {
		int highestAllocated = -1;
		List<BcelField> fs = getFieldGens();
		for (BcelField field : fs) {
			if (field.getName().startsWith(prefix)) {
				try {
					int num = Integer.parseInt(field.getName().substring(prefix.length()));
					if (num > highestAllocated) {
						highestAllocated = num;
					}
				} catch (NumberFormatException nfe) {
					// something wrong with the number on the end of that
					// field...
				}
			}
		}
		return prefix + Integer.toString(highestAllocated + 1);
	}

}
