/* *******************************************************************
 * Copyright (c) 2002 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
 *     RonBodkin/AndyClement optimizations for memory consumption/speed
 * ******************************************************************/

package org.aspectj.weaver.bcel;

import java.io.PrintStream;
import java.lang.ref.WeakReference;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.aspectj.apache.bcel.classfile.Attribute;
import org.aspectj.apache.bcel.classfile.AttributeUtils;
import org.aspectj.apache.bcel.classfile.ConstantClass;
import org.aspectj.apache.bcel.classfile.ConstantPool;
import org.aspectj.apache.bcel.classfile.EnclosingMethod;
import org.aspectj.apache.bcel.classfile.Field;
import org.aspectj.apache.bcel.classfile.InnerClass;
import org.aspectj.apache.bcel.classfile.InnerClasses;
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.annotation.AnnotationGen;
import org.aspectj.apache.bcel.classfile.annotation.EnumElementValue;
import org.aspectj.apache.bcel.classfile.annotation.NameValuePair;
import org.aspectj.asm.AsmManager;
import org.aspectj.bridge.IMessageHandler;
import org.aspectj.bridge.MessageUtil;
import org.aspectj.util.GenericSignature;
import org.aspectj.util.GenericSignature.FormalTypeParameter;
import org.aspectj.weaver.AbstractReferenceTypeDelegate;
import org.aspectj.weaver.AjAttribute;
import org.aspectj.weaver.AjcMemberMaker;
import org.aspectj.weaver.AnnotationAJ;
import org.aspectj.weaver.AnnotationTargetKind;
import org.aspectj.weaver.BCException;
import org.aspectj.weaver.BindingScope;
import org.aspectj.weaver.ConcreteTypeMunger;
import org.aspectj.weaver.ISourceContext;
import org.aspectj.weaver.ReferenceType;
import org.aspectj.weaver.ResolvedMember;
import org.aspectj.weaver.ResolvedPointcutDefinition;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.SourceContextImpl;
import org.aspectj.weaver.TypeVariable;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.WeaverStateInfo;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.BcelGenericSignatureToTypeXConverter.GenericSignatureFormatException;
import org.aspectj.weaver.patterns.Declare;
import org.aspectj.weaver.patterns.DeclareErrorOrWarning;
import org.aspectj.weaver.patterns.DeclarePrecedence;
import org.aspectj.weaver.patterns.FormalBinding;
import org.aspectj.weaver.patterns.IScope;
import org.aspectj.weaver.patterns.PerClause;

public class BcelObjectType extends AbstractReferenceTypeDelegate {
	public JavaClass javaClass;
	private boolean artificial; // Was the BcelObject built from an artificial set of bytes? Or from the real ondisk stuff?
	private LazyClassGen lazyClassGen = null; // set lazily if it's an aspect

	private int modifiers;
	private String className;

	private String superclassSignature;
	private String superclassName;
	private String[] interfaceSignatures;

	private ResolvedMember[] fields = null;
	private ResolvedMember[] methods = null;
	private ResolvedType[] annotationTypes = null;
	private AnnotationAJ[] annotations = null;
	private TypeVariable[] typeVars = null;
	private String retentionPolicy;
	private AnnotationTargetKind[] annotationTargetKinds;

	// Aspect related stuff (pointcuts *could* be in a java class)
	private AjAttribute.WeaverVersionInfo wvInfo = AjAttribute.WeaverVersionInfo.UNKNOWN;
	private ResolvedPointcutDefinition[] pointcuts = null;
	private ResolvedMember[] privilegedAccess = null;
	private WeaverStateInfo weaverState = null;
	private PerClause perClause = null;
	private List<ConcreteTypeMunger> typeMungers = Collections.emptyList();
	private List<Declare> declares = Collections.emptyList();

	private GenericSignature.FormalTypeParameter[] formalsForResolution = null;
	private String declaredSignature = null;

	private boolean hasBeenWoven = false;
	private boolean isGenericType = false;
	private boolean isInterface;
	private boolean isEnum;
	private boolean isAnnotation;
	private boolean isAnonymous;
	private boolean isNested;
	private boolean isObject = false; // set upon construction
	private boolean isAnnotationStyleAspect = false;// set upon construction
	private boolean isCodeStyleAspect = false; // not redundant with field
	// above!

	private WeakReference<ResolvedType> superTypeReference = new WeakReference<>(null);
	private WeakReference<ResolvedType[]> superInterfaceReferences = new WeakReference<>(null);

	private int bitflag = 0x0000;

	// discovery bits
	private static final int DISCOVERED_ANNOTATION_RETENTION_POLICY = 0x0001;
	private static final int UNPACKED_GENERIC_SIGNATURE = 0x0002;
	private static final int UNPACKED_AJATTRIBUTES = 0x0004; // see note(1)
	// below
	private static final int DISCOVERED_ANNOTATION_TARGET_KINDS = 0x0008;
	private static final int DISCOVERED_DECLARED_SIGNATURE = 0x0010;
	private static final int DISCOVERED_WHETHER_ANNOTATION_STYLE = 0x0020;

	private static final int ANNOTATION_UNPACK_IN_PROGRESS = 0x0100;

	private static final String[] NO_INTERFACE_SIGS = new String[] {};

	/*
	 * Notes: note(1): in some cases (perclause inheritance) we encounter unpacked state when calling getPerClause
	 *
	 * note(2): A BcelObjectType is 'damaged' if it has been modified from what was original constructed from the bytecode. This
	 * currently happens if the parents are modified or an annotation is added - ideally BcelObjectType should be immutable but
	 * that's a bigger piece of work. XXX
	 */

	BcelObjectType(ReferenceType resolvedTypeX, JavaClass javaClass, boolean artificial, boolean exposedToWeaver) {
		super(resolvedTypeX, exposedToWeaver);
		this.javaClass = javaClass;
		this.artificial = artificial;
		initializeFromJavaclass();

		// ATAJ: set the delegate right now for @AJ pointcut, else it is done
		// too late to lookup
		// @AJ pc refs annotation in class hierarchy
		resolvedTypeX.setDelegate(this);

		ISourceContext sourceContext = resolvedTypeX.getSourceContext();
		if (sourceContext == SourceContextImpl.UNKNOWN_SOURCE_CONTEXT) {
			sourceContext = new SourceContextImpl(this);
			setSourceContext(sourceContext);
		}

		// this should only ever be java.lang.Object which is
		// the only class in Java-1.4 with no superclasses
		isObject = (javaClass.getSuperclassNameIndex() == 0);
		ensureAspectJAttributesUnpacked();

		// Experimental code leading to undesired ripple effects elsewhere, requiring more rework
		/*
		final String fileName = javaClass.getFileName();
		final String sourceFileName = javaClass.getSourceFileName();
		if (fileName == null || !fileName.endsWith(".class"))
			setSourcefilename(sourceFileName);
		else if (sourceFileName == null || sourceFileName.isEmpty() || sourceFileName.endsWith(".class"))
			setSourcefilename(fileName);
		else
			setSourcefilename(fileName + " (from " + sourceFileName + ")");
		*/

		setSourcefilename(javaClass.getSourceFileName());
	}

	// repeat initialization
	public void setJavaClass(JavaClass newclass, boolean artificial) {
		this.javaClass = newclass;
		this.artificial = artificial;
		resetState();
		initializeFromJavaclass();
	}

	@Override
	public boolean isCacheable() {
		return true;
	}

	private void initializeFromJavaclass() {
		isInterface = javaClass.isInterface();
		isEnum = javaClass.isEnum();
		isAnnotation = javaClass.isAnnotation();
		isAnonymous = javaClass.isAnonymous();
		isNested = javaClass.isNested();
		modifiers = javaClass.getModifiers();
		superclassName = javaClass.getSuperclassName();
		className = javaClass.getClassName();
		cachedGenericClassTypeSignature = null;
	}

	// --- getters

	// Java related
	public boolean isInterface() {
		return isInterface;
	}

	public boolean isEnum() {
		return isEnum;
	}

	public boolean isAnnotation() {
		return isAnnotation;
	}

	public boolean isAnonymous() {
		return isAnonymous;
	}

	public boolean isNested() {
		return isNested;
	}

	public int getModifiers() {
		return modifiers;
	}

	/**
	 * Must take into account generic signature
	 */
	public ResolvedType getSuperclass() {
		if (isObject) {
			return null;
		}
		ResolvedType supertype = superTypeReference.get();
		if (supertype == null) {
			ensureGenericSignatureUnpacked();
			if (superclassSignature == null) {
				if (superclassName == null) {
					superclassName = javaClass.getSuperclassName();
				}
				superclassSignature = getResolvedTypeX().getWorld().resolve(UnresolvedType.forName(superclassName)).getSignature();
			}
			World world = getResolvedTypeX().getWorld();
			supertype = world.resolve(UnresolvedType.forSignature(superclassSignature));
			superTypeReference = new WeakReference<>(supertype);
		}
		return supertype;
	}

	public World getWorld() {
		return getResolvedTypeX().getWorld();
	}

	/**
	 * Retrieves the declared interfaces - this allows for the generic signature on a type. If specified then the generic signature
	 * is used to work out the types - this gets around the results of erasure when the class was originally compiled.
	 */
	public ResolvedType[] getDeclaredInterfaces() {

		ResolvedType[] cachedInterfaceTypes = superInterfaceReferences.get();
		if (cachedInterfaceTypes == null) {
			ensureGenericSignatureUnpacked();
			ResolvedType[] interfaceTypes = null;
			if (interfaceSignatures == null) {
				String[] names = javaClass.getInterfaceNames();
				if (names.length == 0) {
					interfaceSignatures = NO_INTERFACE_SIGS;
					interfaceTypes = ResolvedType.NONE;
				} else {
					interfaceSignatures = new String[names.length];
					interfaceTypes = new ResolvedType[names.length];
					for (int i = 0, len = names.length; i < len; i++) {
						interfaceTypes[i] = getResolvedTypeX().getWorld().resolve(UnresolvedType.forName(names[i]));
						interfaceSignatures[i] = interfaceTypes[i].getSignature();
					}
				}
			} else {
				interfaceTypes = new ResolvedType[interfaceSignatures.length];
				for (int i = 0, len = interfaceSignatures.length; i < len; i++) {
					interfaceTypes[i] = getResolvedTypeX().getWorld().resolve(UnresolvedType.forSignature(interfaceSignatures[i]));
				}
			}
			superInterfaceReferences = new WeakReference<>(interfaceTypes);
			return interfaceTypes;
		} else {
			return cachedInterfaceTypes;
		}
	}

	public ResolvedMember[] getDeclaredMethods() {
		ensureGenericSignatureUnpacked();
		if (methods == null) {
			Method[] ms = javaClass.getMethods();
			ResolvedMember[] newMethods = new ResolvedMember[ms.length];
			for (int i = ms.length - 1; i >= 0; i--) {
				newMethods[i] = new BcelMethod(this, ms[i]);
			}
			methods = newMethods;
		}
		return methods;
	}

	public ResolvedMember[] getDeclaredFields() {
		ensureGenericSignatureUnpacked();
		if (fields == null) {
			Field[] fs = javaClass.getFields();
			ResolvedMember[] newfields = new ResolvedMember[fs.length];
			for (int i = 0, len = fs.length; i < len; i++) {
				newfields[i] = new BcelField(this, fs[i]);
			}
			fields = newfields;
		}
		return fields;
	}

	public TypeVariable[] getTypeVariables() {
		if (!isGeneric()) {
			return TypeVariable.NONE;
		}

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

	public Collection<ConcreteTypeMunger> getTypeMungers() {
		return typeMungers;
	}

	public Collection<Declare> getDeclares() {
		return declares;
	}

	public Collection<ResolvedMember> getPrivilegedAccesses() {
		if (privilegedAccess == null) {
			return Collections.emptyList();
		}
		return Arrays.asList(privilegedAccess);
	}

	public ResolvedMember[] getDeclaredPointcuts() {
		return pointcuts;
	}

	public boolean isAspect() {
		return perClause != null;
	}

	/**
	 * Check if the type is an @AJ aspect (no matter if used from an LTW point of view). Such aspects are annotated with @Aspect
	 *
	 * @return true for @AJ aspect
	 */
	public boolean isAnnotationStyleAspect() {
		if ((bitflag & DISCOVERED_WHETHER_ANNOTATION_STYLE) == 0) {
			bitflag |= DISCOVERED_WHETHER_ANNOTATION_STYLE;
			isAnnotationStyleAspect = !isCodeStyleAspect && hasAnnotation(AjcMemberMaker.ASPECT_ANNOTATION);
		}
		return isAnnotationStyleAspect;
	}

	/**
	 * Process any org.aspectj.weaver attributes stored against the class.
	 */
	private void ensureAspectJAttributesUnpacked() {
		if ((bitflag & UNPACKED_AJATTRIBUTES) != 0) {
			return;
		}
		bitflag |= UNPACKED_AJATTRIBUTES;
		IMessageHandler msgHandler = getResolvedTypeX().getWorld().getMessageHandler();
		// Pass in empty list that can store things for readAj5 to process
		List<AjAttribute> l = null;
		try {
			l = Utility.readAjAttributes(className, javaClass.getAttributes(), getResolvedTypeX().getSourceContext(),
					getResolvedTypeX().getWorld(), AjAttribute.WeaverVersionInfo.UNKNOWN,
					new BcelConstantPoolReader(javaClass.getConstantPool()));
		} catch (RuntimeException re) {
			throw new RuntimeException("Problem processing attributes in " + javaClass.getFileName(), re);
		}
		List<ResolvedPointcutDefinition> pointcuts = new ArrayList<>();
		typeMungers = new ArrayList<>();
		declares = new ArrayList<>();
		processAttributes(l, pointcuts, false);
		ReferenceType type = getResolvedTypeX();
		AsmManager asmManager = ((BcelWorld) type.getWorld()).getModelAsAsmManager();
		l = AtAjAttributes.readAj5ClassAttributes(asmManager, javaClass, type, type.getSourceContext(), msgHandler,
				isCodeStyleAspect);
		AjAttribute.Aspect deferredAspectAttribute = processAttributes(l, pointcuts, true);

		if (pointcuts.size() == 0) {
			this.pointcuts = ResolvedPointcutDefinition.NO_POINTCUTS;
		} else {
			this.pointcuts = pointcuts.toArray(ResolvedPointcutDefinition.NO_POINTCUTS);
		}

		resolveAnnotationDeclares(l);

		if (deferredAspectAttribute != null) {
			// we can finally process the aspect and its associated perclause...
			perClause = deferredAspectAttribute.reifyFromAtAspectJ(this.getResolvedTypeX());
		}
		if (isAspect() && !Modifier.isAbstract(getModifiers()) && isGeneric()) {
			msgHandler.handleMessage(MessageUtil.error("The generic aspect '" + getResolvedTypeX().getName()
					+ "' must be declared abstract", getResolvedTypeX().getSourceLocation()));
		}

	}

	private AjAttribute.Aspect processAttributes(List<AjAttribute> attributeList, List<ResolvedPointcutDefinition> pointcuts,
			boolean fromAnnotations) {
		AjAttribute.Aspect deferredAspectAttribute = null;
		for (AjAttribute a : attributeList) {
			if (a instanceof AjAttribute.Aspect) {
				if (fromAnnotations) {
					deferredAspectAttribute = (AjAttribute.Aspect) a;
				} else {
					perClause = ((AjAttribute.Aspect) a).reify(this.getResolvedTypeX());
					isCodeStyleAspect = true;
				}
			} else if (a instanceof AjAttribute.PointcutDeclarationAttribute) {
				pointcuts.add(((AjAttribute.PointcutDeclarationAttribute) a).reify());
			} else if (a instanceof AjAttribute.WeaverState) {
				weaverState = ((AjAttribute.WeaverState) a).reify();
			} else if (a instanceof AjAttribute.TypeMunger) {
				typeMungers.add(((AjAttribute.TypeMunger) a).reify(getResolvedTypeX().getWorld(), getResolvedTypeX()));
			} else if (a instanceof AjAttribute.DeclareAttribute) {
				declares.add(((AjAttribute.DeclareAttribute) a).getDeclare());
			} else if (a instanceof AjAttribute.PrivilegedAttribute) {
				AjAttribute.PrivilegedAttribute privAttribute = (AjAttribute.PrivilegedAttribute) a;
				privilegedAccess = privAttribute.getAccessedMembers();
			} else if (a instanceof AjAttribute.SourceContextAttribute) {
				if (getResolvedTypeX().getSourceContext() instanceof SourceContextImpl) {
					AjAttribute.SourceContextAttribute sca = (AjAttribute.SourceContextAttribute) a;
					((SourceContextImpl) getResolvedTypeX().getSourceContext()).configureFromAttribute(sca.getSourceFileName(),
							sca.getLineBreaks());

					setSourcefilename(sca.getSourceFileName());
				}
			} else if (a instanceof AjAttribute.WeaverVersionInfo) {
				// Set the weaver version used to build this type
				wvInfo = (AjAttribute.WeaverVersionInfo) a;
			} else {
				throw new BCException("bad attribute " + a);
			}
		}
		return deferredAspectAttribute;
	}

	/**
	 * Extra processing step needed because declares that come from annotations are not pre-resolved. We can't do the resolution
	 * until *after* the pointcuts have been resolved.
	 */
	private void resolveAnnotationDeclares(List<AjAttribute> attributeList) {
		FormalBinding[] bindings = FormalBinding.NONE;
		IScope bindingScope = new BindingScope(getResolvedTypeX(), getResolvedTypeX().getSourceContext(), bindings);
		for (AjAttribute a : attributeList) {
			if (a instanceof AjAttribute.DeclareAttribute) {
				Declare decl = (((AjAttribute.DeclareAttribute) a).getDeclare());
				if (decl instanceof DeclareErrorOrWarning) {
					decl.resolve(bindingScope);
				} else if (decl instanceof DeclarePrecedence) {
					((DeclarePrecedence) decl).setScopeForResolution(bindingScope);
				}
			}
		}
	}

	public PerClause getPerClause() {
		ensureAspectJAttributesUnpacked();
		return perClause;
	}

	public JavaClass getJavaClass() {
		return javaClass;
	}

	/**
	 * @return true if built from bytes obtained from somewhere. False if built from bytes retrieved from disk.
	 */
	public boolean isArtificial() {
		return artificial;
	}

	public void resetState() {
		if (javaClass == null) {
			// we might store the classname and allow reloading?
			// At this point we are relying on the world to not evict if it
			// might want to reweave multiple times
			throw new BCException("can't weave evicted type");
		}

		bitflag = 0x0000;

		this.annotationTypes = null;
		this.annotations = null;
		this.interfaceSignatures = null;
		this.superclassSignature = null;
		this.superclassName = null;
		this.fields = null;
		this.methods = null;
		this.pointcuts = null;
		this.perClause = null;
		this.weaverState = null;
		this.lazyClassGen = null;
		hasBeenWoven = false;

		isObject = (javaClass.getSuperclassNameIndex() == 0);
		isAnnotationStyleAspect = false;
		ensureAspectJAttributesUnpacked();
	}

	public void finishedWith() {
		// memory usage experiments....
		// this.interfaces = null;
		// this.superClass = null;
		// this.fields = null;
		// this.methods = null;
		// this.pointcuts = null;
		// this.perClause = null;
		// this.weaverState = null;
		// this.lazyClassGen = null;
		// this next line frees up memory, but need to understand incremental
		// implications
		// before leaving it in.
		// getResolvedTypeX().setSourceContext(null);
	}

	public WeaverStateInfo getWeaverState() {
		return weaverState;
	}

	void setWeaverState(WeaverStateInfo weaverState) {
		this.weaverState = weaverState;
	}

	public void printWackyStuff(PrintStream out) {
		if (typeMungers.size() > 0) {
			out.println("  TypeMungers: " + typeMungers);
		}
		if (declares.size() > 0) {
			out.println("     declares: " + declares);
		}
	}

	/**
	 * Return the lazyClassGen associated with this type. For aspect types, this value will be cached, since it is used to inline
	 * advice. For non-aspect types, this lazyClassGen is always newly constructed.
	 */
	public LazyClassGen getLazyClassGen() {
		LazyClassGen ret = lazyClassGen;
		if (ret == null) {
			// System.err.println("creating lazy class gen for: " + this);
			ret = new LazyClassGen(this);
			// ret.print(System.err);
			// System.err.println("made LCG from : " +
			// this.getJavaClass().getSuperclassName );
			if (isAspect()) {
				lazyClassGen = ret;
			}
		}
		return ret;
	}

	public boolean isSynthetic() {
		return getResolvedTypeX().isSynthetic();
	}

	public AjAttribute.WeaverVersionInfo getWeaverVersionAttribute() {
		return wvInfo;
	}

	// -- annotation related

	public ResolvedType[] getAnnotationTypes() {
		ensureAnnotationsUnpacked();
		return annotationTypes;
	}

	public AnnotationAJ[] getAnnotations() {
		ensureAnnotationsUnpacked();
		return annotations;
	}

	public boolean hasAnnotations() {
		ensureAnnotationsUnpacked();
		return annotations.length != 0;
	}

	public boolean hasAnnotation(UnresolvedType ofType) {
		// Due to re-entrancy we may be in the middle of unpacking the annotations already... in which case use this slow
		// alternative until the stack unwinds itself
		if (isUnpackingAnnotations()) {
			AnnotationGen annos[] = javaClass.getAnnotations();
			if (annos == null || annos.length == 0) {
				return false;
			} else {
				String lookingForSignature = ofType.getSignature();
				for (AnnotationGen annotation : annos) {
					if (lookingForSignature.equals(annotation.getTypeSignature())) {
						return true;
					}
				}
			}
			return false;
		}
		ensureAnnotationsUnpacked();
		for (int i = 0, max = annotationTypes.length; i < max; i++) {
			UnresolvedType ax = annotationTypes[i];
			if (ax == null) {
				throw new RuntimeException("Annotation entry " + i + " on type " + this.getResolvedTypeX().getName() + " is null!");
			}
			if (ax.equals(ofType)) {
				return true;
			}
		}
		return false;
	}

	public boolean isAnnotationWithRuntimeRetention() {
		return (getRetentionPolicy() == null ? false : getRetentionPolicy().equals("RUNTIME"));
	}

	public String getRetentionPolicy() {
		if ((bitflag & DISCOVERED_ANNOTATION_RETENTION_POLICY) == 0) {
			bitflag |= DISCOVERED_ANNOTATION_RETENTION_POLICY;
			retentionPolicy = null; // null means we have no idea
			if (isAnnotation()) {
				ensureAnnotationsUnpacked();
				for (int i = annotations.length - 1; i >= 0; i--) {
					AnnotationAJ ax = annotations[i];
					if (ax.getTypeName().equals(UnresolvedType.AT_RETENTION.getName())) {
						List<NameValuePair> values = ((BcelAnnotation) ax).getBcelAnnotation().getValues();
						for (NameValuePair element : values) {
							EnumElementValue v = (EnumElementValue) element.getValue();
							retentionPolicy = v.getEnumValueString();
							return retentionPolicy;
						}
					}
				}
			}
		}
		return retentionPolicy;
	}

	public boolean canAnnotationTargetType() {
		AnnotationTargetKind[] targetKinds = getAnnotationTargetKinds();
		if (targetKinds == null) {
			return true;
		}
		for (AnnotationTargetKind targetKind : targetKinds) {
			if (targetKind.equals(AnnotationTargetKind.TYPE)) {
				return true;
			}
		}
		return false;
	}

	public AnnotationTargetKind[] getAnnotationTargetKinds() {
		if ((bitflag & DISCOVERED_ANNOTATION_TARGET_KINDS) != 0) {
			return annotationTargetKinds;
		}
		bitflag |= DISCOVERED_ANNOTATION_TARGET_KINDS;
		annotationTargetKinds = null; // null means we have no idea or the
		// @Target annotation hasn't been used
		List<AnnotationTargetKind> targetKinds = new ArrayList<>();
		if (isAnnotation()) {
			AnnotationAJ[] annotationsOnThisType = getAnnotations();
			for (AnnotationAJ a : annotationsOnThisType) {
				if (a.getTypeName().equals(UnresolvedType.AT_TARGET.getName())) {
					Set<String> targets = a.getTargets();
					if (targets != null) {
						for (String targetKind : targets) {
							if (targetKind.equals("ANNOTATION_TYPE")) {
								targetKinds.add(AnnotationTargetKind.ANNOTATION_TYPE);
							} else if (targetKind.equals("CONSTRUCTOR")) {
								targetKinds.add(AnnotationTargetKind.CONSTRUCTOR);
							} else if (targetKind.equals("FIELD")) {
								targetKinds.add(AnnotationTargetKind.FIELD);
							} else if (targetKind.equals("LOCAL_VARIABLE")) {
								targetKinds.add(AnnotationTargetKind.LOCAL_VARIABLE);
							} else if (targetKind.equals("METHOD")) {
								targetKinds.add(AnnotationTargetKind.METHOD);
							} else if (targetKind.equals("PACKAGE")) {
								targetKinds.add(AnnotationTargetKind.PACKAGE);
							} else if (targetKind.equals("PARAMETER")) {
								targetKinds.add(AnnotationTargetKind.PARAMETER);
							} else if (targetKind.equals("TYPE")) {
								targetKinds.add(AnnotationTargetKind.TYPE);
							}
						}
					}
				}
			}
			if (!targetKinds.isEmpty()) {
				annotationTargetKinds = new AnnotationTargetKind[targetKinds.size()];
				return targetKinds.toArray(annotationTargetKinds);
			}
		}
		return annotationTargetKinds;
	}

	// --- unpacking methods

	private boolean isUnpackingAnnotations() {
		return (bitflag & ANNOTATION_UNPACK_IN_PROGRESS) != 0;
	}

	private void ensureAnnotationsUnpacked() {
		if (isUnpackingAnnotations()) {
			throw new BCException("Re-entered weaver instance whilst unpacking annotations on " + this.className);
		}
		if (annotationTypes == null) {
			try {
				bitflag |= ANNOTATION_UNPACK_IN_PROGRESS;
				AnnotationGen annos[] = javaClass.getAnnotations();
				if (annos == null || annos.length == 0) {
					annotationTypes = ResolvedType.NONE;
					annotations = AnnotationAJ.EMPTY_ARRAY;
				} else {
					World w = getResolvedTypeX().getWorld();
					annotationTypes = new ResolvedType[annos.length];
					annotations = new AnnotationAJ[annos.length];
					for (int i = 0; i < annos.length; i++) {
						AnnotationGen annotation = annos[i];
						String typeSignature = annotation.getTypeSignature();
						ResolvedType rType = w.resolve(UnresolvedType.forSignature(typeSignature));
						if (rType == null) {
							throw new RuntimeException("Whilst unpacking annotations on '" + getResolvedTypeX().getName()
									+ "', failed to resolve type '" + typeSignature + "'");
						}
						annotationTypes[i] = rType;
						annotations[i] = new BcelAnnotation(annotation, rType);
					}
				}
			} finally {
				bitflag &= ~ANNOTATION_UNPACK_IN_PROGRESS;
			}
		}
	}

	// ---

	public String getDeclaredGenericSignature() {
		ensureGenericInfoProcessed();
		return declaredSignature;
	}

	private void ensureGenericSignatureUnpacked() {
		if ((bitflag & UNPACKED_GENERIC_SIGNATURE) != 0) {
			return;
		}
		bitflag |= UNPACKED_GENERIC_SIGNATURE;
		if (!getResolvedTypeX().getWorld().isInJava5Mode()) {
			return;
		}
		GenericSignature.ClassSignature cSig = getGenericClassTypeSignature();
		if (cSig != null) {
			formalsForResolution = cSig.formalTypeParameters;
			if (isNested()) {
				// we have to find any type variables from the outer type before
				// proceeding with resolution.
				GenericSignature.FormalTypeParameter[] extraFormals = getFormalTypeParametersFromOuterClass();
				if (extraFormals.length > 0) {
					List<FormalTypeParameter> allFormals = new ArrayList<>();
					Collections.addAll(allFormals, formalsForResolution);
					Collections.addAll(allFormals, extraFormals);
					formalsForResolution = new GenericSignature.FormalTypeParameter[allFormals.size()];
					allFormals.toArray(formalsForResolution);
				}
			}
			GenericSignature.ClassTypeSignature superSig = cSig.superclassSignature;
			try {
				// this.superClass =
				// BcelGenericSignatureToTypeXConverter.classTypeSignature2TypeX(
				// superSig, formalsForResolution,
				// getResolvedTypeX().getWorld());

				ResolvedType rt = BcelGenericSignatureToTypeXConverter.classTypeSignature2TypeX(superSig, formalsForResolution,
						getResolvedTypeX().getWorld());
				this.superclassSignature = rt.getSignature();
				this.superclassName = rt.getName();

			} catch (GenericSignatureFormatException e) {
				// development bug, fail fast with good info
				throw new IllegalStateException("While determining the generic superclass of " + this.className
						+ " with generic signature " + getDeclaredGenericSignature() + " the following error was detected: "
						+ e.getMessage());
			}
			// this.interfaces = new
			// ResolvedType[cSig.superInterfaceSignatures.length];
			if (cSig.superInterfaceSignatures.length == 0) {
				this.interfaceSignatures = NO_INTERFACE_SIGS;
			} else {
				this.interfaceSignatures = new String[cSig.superInterfaceSignatures.length];
				for (int i = 0; i < cSig.superInterfaceSignatures.length; i++) {
					try {
						// this.interfaces[i] =
						// BcelGenericSignatureToTypeXConverter.
						// classTypeSignature2TypeX(
						// cSig.superInterfaceSignatures[i],
						// formalsForResolution,
						// getResolvedTypeX().getWorld());
						this.interfaceSignatures[i] = BcelGenericSignatureToTypeXConverter.classTypeSignature2TypeX(
								cSig.superInterfaceSignatures[i], formalsForResolution, getResolvedTypeX().getWorld())
								.getSignature();
					} catch (GenericSignatureFormatException e) {
						// development bug, fail fast with good info
						throw new IllegalStateException("While determing the generic superinterfaces of " + this.className
								+ " with generic signature " + getDeclaredGenericSignature()
								+ " the following error was detected: " + e.getMessage());
					}
				}
			}
		}
		if (isGeneric()) {
			// update resolved typex to point at generic type not raw type.
			ReferenceType genericType = this.resolvedTypeX.getGenericType();
			// genericType.setSourceContext(this.resolvedTypeX.getSourceContext());
			// Can be null if unpacking whilst building the bcel delegate (in call hierarchy from BcelWorld.addSourceObjectType()
			// line 453) - see 317139
			if (genericType != null) {
				genericType.setStartPos(this.resolvedTypeX.getStartPos());
				this.resolvedTypeX = genericType;
			}
		}
	}

	public GenericSignature.FormalTypeParameter[] getAllFormals() {
		ensureGenericSignatureUnpacked();
		if (formalsForResolution == null) {
			return FormalTypeParameter.NONE;
		} else {
			return formalsForResolution;
		}
	}

	public ResolvedType getOuterClass() {
		if (!isNested()) {
			throw new IllegalStateException("Can't get the outer class of non-nested type: " + className);
		}

		// try finding outer class name from InnerClasses attribute assigned to this class
		for (Attribute attr : javaClass.getAttributes()) {
			if (attr instanceof InnerClasses) {
				// search for InnerClass entry that has current class as inner and some other class as outer
				InnerClass[] innerClss = ((InnerClasses) attr).getInnerClasses();
				ConstantPool cpool = javaClass.getConstantPool();
				for (InnerClass innerCls : innerClss) {

					// skip entries that miss any necessary component, 0 index means "undefined", from JVM Spec 2nd ed. par. 4.7.5
					if (innerCls.getInnerClassIndex() == 0 || innerCls.getOuterClassIndex() == 0) {
						continue;
					}

					// resolve inner class name, check if it matches current class name
					ConstantClass innerClsInfo = (ConstantClass) cpool.getConstant(innerCls.getInnerClassIndex());

					// class names in constant pool use '/' instead of '.', from JVM Spec 2nd ed. par. 4.2
					String innerClsName = cpool.getConstantUtf8(innerClsInfo.getNameIndex()).getValue().replace('/', '.');

					if (innerClsName.compareTo(className) == 0) {
						// resolve outer class name
						ConstantClass outerClsInfo = (ConstantClass) cpool.getConstant(innerCls.getOuterClassIndex());

						// class names in constant pool use '/' instead of '.', from JVM Spec 2nd ed. par. 4.2
						String outerClsName = cpool.getConstantUtf8(outerClsInfo.getNameIndex()).getValue().replace('/', '.');

						UnresolvedType outer = UnresolvedType.forName(outerClsName);
						return outer.resolve(getResolvedTypeX().getWorld());
					}
				}
			}
		}

		for (Attribute attr : javaClass.getAttributes()) { // bug339300
			ConstantPool cpool = javaClass.getConstantPool();
			if (attr instanceof EnclosingMethod) {
				EnclosingMethod enclosingMethodAttribute = (EnclosingMethod) attr;
				if (enclosingMethodAttribute.getEnclosingClassIndex() != 0) {
					ConstantClass outerClassInfo = enclosingMethodAttribute.getEnclosingClass();
					String outerClassName = cpool.getConstantUtf8(outerClassInfo.getNameIndex()).getValue().replace('/', '.');
					UnresolvedType outer = UnresolvedType.forName(outerClassName);
					return outer.resolve(getResolvedTypeX().getWorld());
				}
			}
		}

		// try finding outer class name by assuming standard class name mangling convention of javac for this class
		int lastDollar = className.lastIndexOf('$');
		if (lastDollar == -1) {
			// Is this class damaged/obfuscated? Why did we think it was nested but couldn't find the parent using
			// the attributes above. For now just ignore it... I wonder when ignoring this will come back to bite!
			return null;
		}
		String superClassName = className.substring(0, lastDollar);
		UnresolvedType outer = UnresolvedType.forName(superClassName);
		return outer.resolve(getResolvedTypeX().getWorld());
	}

	private void ensureGenericInfoProcessed() {
		if ((bitflag & DISCOVERED_DECLARED_SIGNATURE) != 0) {
			return;
		}
		bitflag |= DISCOVERED_DECLARED_SIGNATURE;
		Signature sigAttr = AttributeUtils.getSignatureAttribute(javaClass.getAttributes());
		declaredSignature = (sigAttr == null ? null : sigAttr.getSignature());
		if (declaredSignature != null) {
			isGenericType = (declaredSignature.charAt(0) == '<');
		}
	}

	public boolean isGeneric() {
		ensureGenericInfoProcessed();
		return isGenericType;
	}

	@Override
	public String toString() {
		return (javaClass == null ? "BcelObjectType" : "BcelObjectTypeFor:" + className);
	}

	// --- state management

	public void evictWeavingState() {
		// Can't chuck all this away
		if (getResolvedTypeX().getWorld().couldIncrementalCompileFollow()) {
			return;
		}

		if (javaClass != null) {
			// Force retrieval of any lazy information
			ensureAnnotationsUnpacked();
			ensureGenericInfoProcessed();

			getDeclaredInterfaces();
			getDeclaredFields();
			getDeclaredMethods();
			// The lazyClassGen is preserved for aspects - it exists to enable
			// around advice
			// inlining since the method will need 'injecting' into the affected
			// class. If
			// XnoInline is on, we can chuck away the lazyClassGen since it
			// won't be required
			// later.
			if (getResolvedTypeX().getWorld().isXnoInline()) {
				lazyClassGen = null;
			}

			// discard expensive bytecode array containing reweavable info
			if (weaverState != null) {
				weaverState.setReweavable(false);
				weaverState.setUnwovenClassFileData(null);
			}
			for (int i = methods.length - 1; i >= 0; i--) {
				methods[i].evictWeavingState();
			}
			for (int i = fields.length - 1; i >= 0; i--) {
				fields[i].evictWeavingState();
			}
			javaClass = null;
			this.artificial = true;
			// setSourceContext(SourceContextImpl.UNKNOWN_SOURCE_CONTEXT); //
			// bit naughty
			// interfaces=null; // force reinit - may get us the right
			// instances!
			// superClass=null;
		}
	}

	public void weavingCompleted() {
		hasBeenWoven = true;
		if (getResolvedTypeX().getWorld().isRunMinimalMemory()) {
			evictWeavingState();
		}
		if (getSourceContext() != null && !getResolvedTypeX().isAspect()) {
			getSourceContext().tidy();
		}
	}

	public boolean hasBeenWoven() {
		return hasBeenWoven;
	}

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

	public void setExposedToWeaver(boolean b) {
		exposedToWeaver = b;
	}

	@Override
	public int getCompilerVersion() {
		return wvInfo.getMajorVersion();
	}

	public void ensureConsistent() {
		superTypeReference.clear();
		superInterfaceReferences.clear();
	}

	public boolean isWeavable() {
		return true;
	}
}
