/* *******************************************************************
 * Copyright (c) 2002 Palo Alto Research Center, Incorporated (PARC).
 * All rights reserved.
 * This program and the accompanying materials are made available
 * under the terms of the Eclipse Public License v 2.0
 * which accompanies this distribution and is available at
 * https://www.eclipse.org/org/documents/epl-2.0/EPL-2.0.txt
 *
 * Contributors:
 *     PARC     initial implementation
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.aspectj.apache.bcel.classfile.AnnotationDefault;
import org.aspectj.apache.bcel.classfile.Attribute;
import org.aspectj.apache.bcel.classfile.ExceptionTable;
import org.aspectj.apache.bcel.classfile.JavaClass;
import org.aspectj.apache.bcel.classfile.LineNumber;
import org.aspectj.apache.bcel.classfile.LineNumberTable;
import org.aspectj.apache.bcel.classfile.LocalVariable;
import org.aspectj.apache.bcel.classfile.LocalVariableTable;
import org.aspectj.apache.bcel.classfile.Method;
import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen;
import org.aspectj.apache.bcel.classfile.annotation.NameValuePair;
import org.aspectj.bridge.ISourceLocation;
import org.aspectj.bridge.SourceLocation;
import org.aspectj.util.GenericSignature;
import org.aspectj.util.GenericSignature.TypeVariableSignature;
import org.aspectj.util.GenericSignatureParser;
import org.aspectj.weaver.AjAttribute;
import org.aspectj.weaver.AjAttribute.WeaverVersionInfo;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.MemberKind;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedPointcutDefinition;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.ShadowMunger;
import org.aspectj.weaver.TypeVariable;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.BcelGenericSignatureToTypeXConverter.GenericSignatureFormatException;

//public final
class BcelMethod extends ResolvedMemberImpl {

  private final static String ASPECTJ_ANNOTATION_PACKAGE = "org.aspectj.lang.annotation";
  private final static char PACKAGE_INITIAL_CHAR = ASPECTJ_ANNOTATION_PACKAGE.charAt(0);

	private Method method;

	// these fields are not set for many BcelMethods...
	private ShadowMunger associatedShadowMunger;
	private ResolvedPointcutDefinition preResolvedPointcut; // used when ajc has pre-resolved the pointcut of some @Advice
	private AjAttribute.EffectiveSignatureAttribute effectiveSignature;

	private AjAttribute.MethodDeclarationLineNumberAttribute declarationLineNumber;
	private final BcelObjectType bcelObjectType;

	private int bitflags;
	private static final int KNOW_IF_SYNTHETIC = 0x0001;
	private static final int PARAMETER_NAMES_INITIALIZED = 0x0002;
	private static final int CAN_BE_PARAMETERIZED = 0x0004;
	private static final int UNPACKED_GENERIC_SIGNATURE = 0x0008;
	private static final int IS_AJ_SYNTHETIC = 0x0040;
	private static final int IS_SYNTHETIC = 0x0080;
	private static final int IS_SYNTHETIC_INVERSE = 0x7f7f; // all bits but
	// IS_SYNTHETIC (and
	// topmost bit)
	private static final int HAS_ANNOTATIONS = 0x0400;
	private static final int HAVE_DETERMINED_ANNOTATIONS = 0x0800;

	// genericized version of return and parameter types
	private UnresolvedType genericReturnType = null;
	private UnresolvedType[] genericParameterTypes = null;

	BcelMethod(BcelObjectType declaringType, Method method) {
		super(method.getName().equals("<init>") ? CONSTRUCTOR : (method.getName().equals("<clinit>") ? STATIC_INITIALIZATION
				: METHOD), declaringType.getResolvedTypeX(), method.getModifiers(), method.getName(), method.getSignature());
		this.method = method;
		sourceContext = declaringType.getResolvedTypeX().getSourceContext();
		bcelObjectType = declaringType;
		unpackJavaAttributes();
		unpackAjAttributes(bcelObjectType.getWorld());
	}

	/**
	 * This constructor expects to be passed the attributes, rather than deserializing them.
	 */
	BcelMethod(BcelObjectType declaringType, Method method, List<AjAttribute> attributes) {
		super(method.getName().equals("<init>") ? CONSTRUCTOR : (method.getName().equals("<clinit>") ? STATIC_INITIALIZATION
				: METHOD), declaringType.getResolvedTypeX(), method.getModifiers(), method.getName(), method.getSignature());
		this.method = method;
		sourceContext = declaringType.getResolvedTypeX().getSourceContext();
		bcelObjectType = declaringType;
		unpackJavaAttributes();
		processAttributes(bcelObjectType.getWorld(), attributes);
	}

	// ----

	private void unpackJavaAttributes() {
		ExceptionTable exnTable = method.getExceptionTable();
		checkedExceptions = (exnTable == null) ? UnresolvedType.NONE : UnresolvedType.forNames(exnTable.getExceptionNames());
	}

	@Override
	public String[] getParameterNames() {
		determineParameterNames();
		return super.getParameterNames();
	}

	public int getLineNumberOfFirstInstruction() {
		LineNumberTable lnt = method.getLineNumberTable();
		if (lnt == null) {
			return -1;
		}
		LineNumber[] lns = lnt.getLineNumberTable();
		if (lns == null || lns.length == 0) {
			return -1;
		}
		return lns[0].getLineNumber();
	}

	public void determineParameterNames() {
		if ((bitflags & PARAMETER_NAMES_INITIALIZED) != 0) {
			return;
		}
		bitflags |= PARAMETER_NAMES_INITIALIZED;
		LocalVariableTable varTable = method.getLocalVariableTable();
		int len = getArity();
		if (varTable == null) {
			// do we have an annotation with the argNames value specified...
			AnnotationAJ[] annos = getAnnotations();
			if (annos != null && annos.length != 0) {
				AnnotationAJ[] axs = getAnnotations();
				for (AnnotationAJ annotationX : axs) {
					String typename = annotationX.getTypeName();
					if (typename.charAt(0) == PACKAGE_INITIAL_CHAR) {
						if (typename.equals("org.aspectj.lang.annotation.Pointcut")
								|| typename.equals("org.aspectj.lang.annotation.Before")
								|| typename.equals("org.aspectj.lang.annotation.Around")
								|| typename.startsWith("org.aspectj.lang.annotation.After")) {
							AnnotationGen a = ((BcelAnnotation) annotationX).getBcelAnnotation();
							if (a != null) {
								List<NameValuePair> values = a.getValues();
								for (NameValuePair nvPair : values) {
									if (nvPair.getNameString().equals("argNames")) {
										String argNames = nvPair.getValue().stringifyValue();
										StringTokenizer argNameTokenizer = new StringTokenizer(argNames, " ,");
										List<String> argsList = new ArrayList<>();
										while (argNameTokenizer.hasMoreTokens()) {
											argsList.add(argNameTokenizer.nextToken());
										}
										int requiredCount = getParameterTypes().length;
										while (argsList.size() < requiredCount) {
											argsList.add("arg" + argsList.size());
										}
										setParameterNames(argsList.toArray(new String[]{}));
										return;
									}
								}
							}
						}
					}
				}
			}
			setParameterNames(Utility.makeArgNames(len));
		} else {
			UnresolvedType[] paramTypes = getParameterTypes();
			String[] paramNames = new String[len];
			int index = Modifier.isStatic(modifiers) ? 0 : 1;
			for (int i = 0; i < len; i++) {
				LocalVariable lv = varTable.getLocalVariable(index);
				if (lv == null) {
					paramNames[i] = "arg" + i;
				} else {
					paramNames[i] = lv.getName();
				}
				index += paramTypes[i].getSize();
			}
			setParameterNames(paramNames);
		}
	}

	private void unpackAjAttributes(World world) {
		associatedShadowMunger = null;
		ResolvedType resolvedDeclaringType = getDeclaringType().resolve(world);
		WeaverVersionInfo wvinfo = bcelObjectType.getWeaverVersionAttribute();
		List<AjAttribute> as = Utility.readAjAttributes(resolvedDeclaringType.getClassName(), method.getAttributes(),
				resolvedDeclaringType.getSourceContext(), world, wvinfo, new BcelConstantPoolReader(method.getConstantPool()));
		processAttributes(world, as);
		as = AtAjAttributes.readAj5MethodAttributes(method, this, resolvedDeclaringType, preResolvedPointcut,
				resolvedDeclaringType.getSourceContext(), world.getMessageHandler());
		processAttributes(world, as);
	}

	private void processAttributes(World world, List<AjAttribute> as) {
		for (AjAttribute attr : as) {
			if (attr instanceof AjAttribute.MethodDeclarationLineNumberAttribute) {
				declarationLineNumber = (AjAttribute.MethodDeclarationLineNumberAttribute) attr;
			} else if (attr instanceof AjAttribute.AdviceAttribute) {
				associatedShadowMunger = ((AjAttribute.AdviceAttribute) attr).reify(this, world, (ResolvedType) getDeclaringType());
			} else if (attr instanceof AjAttribute.AjSynthetic) {
				bitflags |= IS_AJ_SYNTHETIC;
			} else if (attr instanceof AjAttribute.EffectiveSignatureAttribute) {
				effectiveSignature = (AjAttribute.EffectiveSignatureAttribute) attr;
			} else if (attr instanceof AjAttribute.PointcutDeclarationAttribute) {
				// this is an @AspectJ annotated advice method, with pointcut pre-resolved by ajc
				preResolvedPointcut = ((AjAttribute.PointcutDeclarationAttribute) attr).reify();
			} else {
				throw new BCException("weird method attribute " + attr);
			}
		}
	}

	//
	// // for testing - if we have this attribute, return it - will return null
	// if
	// // it doesnt know anything
	// public AjAttribute[] getAttributes(String name) {
	// List results = new ArrayList();
	// List l = Utility.readAjAttributes(getDeclaringType().getClassName(),
	// method.getAttributes(),
	// getSourceContext(bcelObjectType.getWorld()), bcelObjectType.getWorld(),
	// bcelObjectType.getWeaverVersionAttribute());
	// for (Iterator iter = l.iterator(); iter.hasNext();) {
	// AjAttribute element = (AjAttribute) iter.next();
	// if (element.getNameString().equals(name))
	// results.add(element);
	// }
	// if (results.size() > 0) {
	// return (AjAttribute[]) results.toArray(new AjAttribute[] {});
	// }
	// return null;
	// }

	@Override
	public String getAnnotationDefaultValue() {
		Attribute[] attrs = method.getAttributes();
		for (Attribute attribute : attrs) {
			if (attribute.getName().equals("AnnotationDefault")) {
				AnnotationDefault def = (AnnotationDefault) attribute;
				return def.getElementValue().stringifyValue();
			}
		}
		return null;
	}

	// for testing - use with the method above
	public String[] getAttributeNames(boolean onlyIncludeAjOnes) {
		Attribute[] as = method.getAttributes();
		List<String> names = new ArrayList<>();
		// String[] strs = new String[as.length];
		for (Attribute a : as) {
			if (!onlyIncludeAjOnes || a.getName().startsWith(AjAttribute.AttributePrefix)) {
				names.add(a.getName());
			}
		}
		return names.toArray(new String[] {});
	}

	@Override
	public boolean isAjSynthetic() {
		return (bitflags & IS_AJ_SYNTHETIC) != 0;
	}

	@Override
	public ShadowMunger getAssociatedShadowMunger() {
		return associatedShadowMunger;
	}

	@Override
	public AjAttribute.EffectiveSignatureAttribute getEffectiveSignature() {
		return effectiveSignature;
	}

	public boolean hasDeclarationLineNumberInfo() {
		return declarationLineNumber != null;
	}

	public int getDeclarationLineNumber() {
		if (declarationLineNumber != null) {
			return declarationLineNumber.getLineNumber();
		} else {
			return -1;
		}
	}

	public int getDeclarationOffset() {
		if (declarationLineNumber != null) {
			return declarationLineNumber.getOffset();
		} else {
			return -1;
		}
	}

	@Override
	public ISourceLocation getSourceLocation() {
		ISourceLocation ret = super.getSourceLocation();
		if ((ret == null || ret.getLine() == 0) && hasDeclarationLineNumberInfo()) {
			// lets see if we can do better
			ISourceContext isc = getSourceContext();
			if (isc != null) {
				ret = isc.makeSourceLocation(getDeclarationLineNumber(), getDeclarationOffset());
			} else {
				ret = new SourceLocation(null, getDeclarationLineNumber());
			}
		}
		return ret;
	}

	@Override
	public MemberKind getKind() {
		if (associatedShadowMunger != null) {
			return ADVICE;
		} else {
			return super.getKind();
		}
	}

	@Override
	public boolean hasAnnotation(UnresolvedType ofType) {
		ensureAnnotationsRetrieved();
		for (ResolvedType aType : annotationTypes) {
			if (aType.equals(ofType)) {
				return true;
			}
		}
		return false;
	}

	@Override
	public AnnotationAJ[] getAnnotations() {
		ensureAnnotationsRetrieved();
		if ((bitflags & HAS_ANNOTATIONS) != 0) {
			return annotations;
		} else {
			return AnnotationAJ.EMPTY_ARRAY;
		}
	}

	@Override
	public ResolvedType[] getAnnotationTypes() {
		ensureAnnotationsRetrieved();
		return annotationTypes;
	}

	@Override
	public AnnotationAJ getAnnotationOfType(UnresolvedType ofType) {
		ensureAnnotationsRetrieved();
		if ((bitflags & HAS_ANNOTATIONS) == 0) {
			return null;
		}
		for (AnnotationAJ annotation : annotations) {
			if (annotation.getTypeName().equals(ofType.getName())) {
				return annotation;
			}
		}
		return null;
	}

	@Override
	public void addAnnotation(AnnotationAJ annotation) {
		ensureAnnotationsRetrieved();
		if ((bitflags & HAS_ANNOTATIONS) == 0) {
			annotations = new AnnotationAJ[1];
			annotations[0] = annotation;
			annotationTypes = new ResolvedType[1];
			annotationTypes[0] = annotation.getType();
		} else {
			// Add it to the set of annotations
			int len = annotations.length;
			AnnotationAJ[] ret = new AnnotationAJ[len + 1];
			System.arraycopy(annotations, 0, ret, 0, len);
			ret[len] = annotation;
			annotations = ret;
			ResolvedType[] newAnnotationTypes = new ResolvedType[len + 1];
			System.arraycopy(annotationTypes, 0, newAnnotationTypes, 0, len);
			newAnnotationTypes[len] = annotation.getType();
			annotationTypes = newAnnotationTypes;
		}
		bitflags |= HAS_ANNOTATIONS;
	}

	public void removeAnnotation(ResolvedType annotationType) {
		ensureAnnotationsRetrieved();
		if ((bitflags & HAS_ANNOTATIONS) == 0) {
			// nothing to do, why did we get called?
		} else {
			int len = annotations.length;
			if (len == 1) {
				bitflags &= ~HAS_ANNOTATIONS;
				annotations = null;
				annotationTypes = null;
				return;
			}
			AnnotationAJ[] ret = new AnnotationAJ[len - 1];
			int p = 0;
			for (AnnotationAJ annotation : annotations) {
				if (!annotation.getType().equals(annotationType)) {
					ret[p++] = annotation;
				}
			}
			annotations = ret;

			ResolvedType[] newAnnotationTypes = new ResolvedType[len - 1];
			p = 0;
			for (AnnotationAJ annotation : annotations) {
				if (!annotation.getType().equals(annotationType)) {
					newAnnotationTypes[p++] = annotationType;
				}
			}
			annotationTypes = newAnnotationTypes;
		}
		bitflags |= HAS_ANNOTATIONS;
	}

	public void addParameterAnnotation(int param, AnnotationAJ anno) {
		ensureParameterAnnotationsRetrieved();
		if (parameterAnnotations == NO_PARAMETER_ANNOTATIONXS) {
			// First time we've added any, so lets set up the array
			parameterAnnotations = new AnnotationAJ[getArity()][];
			for (int i = 0; i < getArity(); i++) {
				parameterAnnotations[i] = AnnotationAJ.EMPTY_ARRAY;
			}
		}
		int existingCount = parameterAnnotations[param].length;
		if (existingCount == 0) {
			AnnotationAJ[] annoArray = new AnnotationAJ[1];
			annoArray[0] = anno;
			parameterAnnotations[param] = annoArray;
		} else {
			AnnotationAJ[] newAnnoArray = new AnnotationAJ[existingCount + 1];
			System.arraycopy(parameterAnnotations[param], 0, newAnnoArray, 0, existingCount);
			newAnnoArray[existingCount] = anno;
			parameterAnnotations[param] = newAnnoArray;
		}
	}

	private void ensureAnnotationsRetrieved() {
		if (method == null) {
			return; // must be ok, we have evicted it
		}
		if ((bitflags & HAVE_DETERMINED_ANNOTATIONS) != 0) {
			return;
		}
		bitflags |= HAVE_DETERMINED_ANNOTATIONS;
		AnnotationGen annos[] = method.getAnnotations();
		if (annos.length == 0) {
			annotationTypes = ResolvedType.NONE;
			annotations = AnnotationAJ.EMPTY_ARRAY;
		} else {
			int annoCount = annos.length;
			annotationTypes = new ResolvedType[annoCount];
			annotations = new AnnotationAJ[annoCount];
			for (int i = 0; i < annoCount; i++) {
				AnnotationGen annotation = annos[i];
				annotations[i] = new BcelAnnotation(annotation, bcelObjectType.getWorld());
				annotationTypes[i] = annotations[i].getType();
			}
			bitflags |= HAS_ANNOTATIONS;
		}
	}

	private void ensureParameterAnnotationsRetrieved() {
		if (method == null) {
			return; // must be ok, we have evicted it
		}
		AnnotationGen[][] pAnns = method.getParameterAnnotations();
		if (parameterAnnotationTypes == null || pAnns.length != parameterAnnotationTypes.length) {
			if (pAnns == Method.NO_PARAMETER_ANNOTATIONS) {
				parameterAnnotationTypes = BcelMethod.NO_PARAMETER_ANNOTATION_TYPES;
				parameterAnnotations = BcelMethod.NO_PARAMETER_ANNOTATIONXS;
			} else {
				AnnotationGen annos[][] = method.getParameterAnnotations();
				parameterAnnotations = new AnnotationAJ[annos.length][];
				parameterAnnotationTypes = new ResolvedType[annos.length][];
				for (int i = 0; i < annos.length; i++) {
					AnnotationGen[] annosOnThisParam = annos[i];
					if (annos[i].length == 0) {
						parameterAnnotations[i] = AnnotationAJ.EMPTY_ARRAY;
						parameterAnnotationTypes[i] = ResolvedType.NONE;
					} else {
						parameterAnnotations[i] = new AnnotationAJ[annosOnThisParam.length];
						parameterAnnotationTypes[i] = new ResolvedType[annosOnThisParam.length];
						for (int j = 0; j < annosOnThisParam.length; j++) {
							parameterAnnotations[i][j] = new BcelAnnotation(annosOnThisParam[j], bcelObjectType.getWorld());
							parameterAnnotationTypes[i][j] = bcelObjectType.getWorld().resolve(
									UnresolvedType.forSignature(annosOnThisParam[j].getTypeSignature()));
						}
					}
				}
			}
		}
	}

	@Override
	public AnnotationAJ[][] getParameterAnnotations() {
		ensureParameterAnnotationsRetrieved();
		return parameterAnnotations;
	}

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

	/**
	 * A method can be parameterized if it has one or more generic parameters. A generic parameter (type variable parameter) is
	 * identified by the prefix "T"
	 */
	@Override
	public boolean canBeParameterized() {
		unpackGenericSignature();
		return (bitflags & CAN_BE_PARAMETERIZED) != 0;
	}

	@Override
	public UnresolvedType[] getGenericParameterTypes() {
		unpackGenericSignature();
		return genericParameterTypes;
	}

	/**
	 * Return the parameterized/generic return type or the normal return type if the method is not generic.
	 */
	@Override
	public UnresolvedType getGenericReturnType() {
		unpackGenericSignature();
		return genericReturnType;
	}

	/** For testing only */
	public Method getMethod() {
		return method;
	}

	private void unpackGenericSignature() {
		if ((bitflags & UNPACKED_GENERIC_SIGNATURE) != 0) {
			return;
		}
		bitflags |= UNPACKED_GENERIC_SIGNATURE;
		if (!bcelObjectType.getWorld().isInJava5Mode()) {
			genericReturnType = getReturnType();
			genericParameterTypes = getParameterTypes();
			return;
		}
		String gSig = method.getGenericSignature();
		if (gSig != null) {
			GenericSignature.MethodTypeSignature mSig = new GenericSignatureParser().parseAsMethodSignature(gSig);// method
			// .
			// getGenericSignature
			// ());
			if (mSig.formalTypeParameters.length > 0) {
				// generic method declaration
				bitflags |= CAN_BE_PARAMETERIZED;
			}

			typeVariables = new TypeVariable[mSig.formalTypeParameters.length];
			for (int i = 0; i < typeVariables.length; i++) {
				GenericSignature.FormalTypeParameter methodFtp = mSig.formalTypeParameters[i];
				try {
					typeVariables[i] = BcelGenericSignatureToTypeXConverter.formalTypeParameter2TypeVariable(methodFtp,
							mSig.formalTypeParameters, bcelObjectType.getWorld());
				} catch (GenericSignatureFormatException e) {
					// this is a development bug, so fail fast with good info
					throw new IllegalStateException("While getting the type variables for method " + this.toString()
							+ " with generic signature " + mSig + " the following error condition was detected: " + e.getMessage());
				}
			}

			GenericSignature.FormalTypeParameter[] parentFormals = bcelObjectType.getAllFormals();
			GenericSignature.FormalTypeParameter[] formals = new GenericSignature.FormalTypeParameter[parentFormals.length
					+ mSig.formalTypeParameters.length];
			// put method formal in front of type formals for overriding in
			// lookup
			System.arraycopy(mSig.formalTypeParameters, 0, formals, 0, mSig.formalTypeParameters.length);
			System.arraycopy(parentFormals, 0, formals, mSig.formalTypeParameters.length, parentFormals.length);
			GenericSignature.TypeSignature returnTypeSignature = mSig.returnType;
			try {
				genericReturnType = BcelGenericSignatureToTypeXConverter.typeSignature2TypeX(returnTypeSignature, formals,
						bcelObjectType.getWorld());
			} catch (GenericSignatureFormatException e) {
				// development bug, fail fast with good info
				throw new IllegalStateException("While determing the generic return type of " + this.toString()
						+ " with generic signature " + gSig + " the following error was detected: " + e.getMessage());
			}
			GenericSignature.TypeSignature[] paramTypeSigs = mSig.parameters;
			if (paramTypeSigs.length == 0) {
				genericParameterTypes = UnresolvedType.NONE;
			} else {
				genericParameterTypes = new UnresolvedType[paramTypeSigs.length];
			}
			for (int i = 0; i < paramTypeSigs.length; i++) {
				try {
					genericParameterTypes[i] = BcelGenericSignatureToTypeXConverter.typeSignature2TypeX(paramTypeSigs[i], formals,
							bcelObjectType.getWorld());
				} catch (GenericSignatureFormatException e) {
					// development bug, fail fast with good info
					throw new IllegalStateException("While determining the generic parameter types of " + this.toString()
							+ " with generic signature " + gSig + " the following error was detected: " + e.getMessage());
				}
				if (paramTypeSigs[i] instanceof TypeVariableSignature) {
					bitflags |= CAN_BE_PARAMETERIZED;
				}
			}
		} else {
			genericReturnType = getReturnType();
			genericParameterTypes = getParameterTypes();
		}
	}

	@Override
	public void evictWeavingState() {
		if (method != null) {
			unpackGenericSignature();
			unpackJavaAttributes();
			ensureAnnotationsRetrieved();
			ensureParameterAnnotationsRetrieved();
			determineParameterNames();
			// this.sourceContext = SourceContextImpl.UNKNOWN_SOURCE_CONTEXT;
			method = null;
		}
	}

	@Override
	public boolean isSynthetic() {
		if ((bitflags & KNOW_IF_SYNTHETIC) == 0) {
			workOutIfSynthetic();
		}
		return (bitflags & IS_SYNTHETIC) != 0;// isSynthetic;
	}

	// Pre Java5 synthetic is an attribute 'Synthetic', post Java5 it is a
	// modifier (4096 or 0x1000)
	private void workOutIfSynthetic() {
		if ((bitflags & KNOW_IF_SYNTHETIC) != 0) {
			return;
		}
		bitflags |= KNOW_IF_SYNTHETIC;
		JavaClass jc = bcelObjectType.getJavaClass();
		bitflags &= IS_SYNTHETIC_INVERSE; // unset the bit
		if (jc == null) {
			return; // what the hell has gone wrong?
		}
		if (jc.getMajor() < 49/* Java5 */) {
			// synthetic is an attribute
			String[] synthetics = getAttributeNames(false);
			if (synthetics != null) {
				for (String synthetic : synthetics) {
					if (synthetic.equals("Synthetic")) {
						bitflags |= IS_SYNTHETIC;
						break;
					}
				}
			}
		} else {
			// synthetic is a modifier (4096)
			if ((modifiers & 4096) != 0) {
				bitflags |= IS_SYNTHETIC;
			}
		}
	}

	/**
	 * Returns whether or not the given object is equivalent to the current one. Returns true if
	 * getMethod().getCode().getCodeString() are equal. Allows for different line number tables.
	 */
	// bug 154054: is similar to equals(Object) however
	// doesn't require implementing equals in Method and Code
	// which proved expensive. Currently used within
	// CrosscuttingMembers.replaceWith() to decide if we need
	// to do a full build
	@Override
	public boolean isEquivalentTo(Object other) {
		if (!(other instanceof BcelMethod)) {
			return false;
		}
		BcelMethod o = (BcelMethod) other;
		return getMethod().getCode().getCodeString().equals(o.getMethod().getCode().getCodeString());
	}

	/**
	 * Return true if the method represents the default constructor. Hard to determine this from bytecode, but the existence of the
	 * MethodDeclarationLineNumber attribute should tell us.
	 *
	 * @return true if this BcelMethod represents the default constructor
	 */
	@Override
	public boolean isDefaultConstructor() {
		boolean mightBe = !hasDeclarationLineNumberInfo() && name.equals("<init>") && parameterTypes.length == 0;
		if (mightBe) {
			// TODO would be nice to do a check to see if the file was compiled with javac or ajc?
			// maybe by checking the constant pool for aspectj strings?
			return true;
		} else {
			return false;
		}
	}

}
