/* *******************************************************************
 * 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.util.List;

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.Synthetic;
import org.aspectj.apache.bcel.classfile.annotation.AnnotationGen;
import org.aspectj.apache.bcel.generic.FieldGen;
import org.aspectj.util.GenericSignature;
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.ISourceContext;
import org.aspectj.weaver.ResolvedMemberImpl;
import org.aspectj.weaver.ResolvedType;
import org.aspectj.weaver.UnresolvedType;
import org.aspectj.weaver.World;
import org.aspectj.weaver.bcel.BcelGenericSignatureToTypeXConverter.GenericSignatureFormatException;

/**
 * An AspectJ Field object that is backed by a Bcel Field object.
 *
 * @author PARC
 * @author Andy Clement
 */
final class BcelField extends ResolvedMemberImpl {

	public static int AccSynthetic = 0x1000;

	private Field field;
	private boolean isAjSynthetic;
	private boolean isSynthetic = false;
	private AnnotationAJ[] annotations;
	private final World world;
	private final BcelObjectType bcelObjectType;
	private UnresolvedType genericFieldType = null;
	private boolean unpackedGenericSignature = false;
	private boolean annotationsOnFieldObjectAreOutOfDate = false;

	BcelField(BcelObjectType declaringType, Field field) {
		super(FIELD, declaringType.getResolvedTypeX(), field.getModifiers(), field.getName(), field.getSignature());
		this.field = field;
		this.world = declaringType.getResolvedTypeX().getWorld();
		this.bcelObjectType = declaringType;
		unpackAttributes(world);
		checkedExceptions = UnresolvedType.NONE;
	}

	/**
	 * Constructs an instance that wrappers a Field object, but where we do not (yet) have a BcelObjectType - usually because the
	 * containing type (and this field) are being constructed at runtime (so there is no .class file to retrieve).
	 */
	BcelField(String declaringTypeName, Field field, World world) {
		super(FIELD, UnresolvedType.forName(declaringTypeName), field.getModifiers(), field.getName(), field.getSignature());
		this.field = field;
		this.world = world;
		this.bcelObjectType = null;
		unpackAttributes(world);
		checkedExceptions = UnresolvedType.NONE;
	}

	private void unpackAttributes(World world) {
		Attribute[] attrs = field.getAttributes();
		if (attrs != null && attrs.length > 0) {
			ISourceContext sourceContext = getSourceContext(world);
			List<AjAttribute> as = Utility.readAjAttributes(getDeclaringType().getClassName(), attrs, sourceContext, world,
					(bcelObjectType != null ? bcelObjectType.getWeaverVersionAttribute() : WeaverVersionInfo.CURRENT),
					new BcelConstantPoolReader(field.getConstantPool()));
			as.addAll(AtAjAttributes.readAj5FieldAttributes(field, this, world.resolve(getDeclaringType()), sourceContext,
					world.getMessageHandler()));

			// FIXME this code has no effect!!!??? it is set to false immediately after the block
			// for (AjAttribute a : as) {
			// if (a instanceof AjAttribute.AjSynthetic) {
			// isAjSynthetic = true;
			// } else {
			// throw new BCException("weird field attribute " + a);
			// }
			// }
		}
		isAjSynthetic = false;

		for (int i = attrs.length - 1; i >= 0; i--) {
			if (attrs[i] instanceof Synthetic) {
				isSynthetic = true;
			}
		}
		// in 1.5, synthetic is a modifier, not an attribute
		if ((field.getModifiers() & AccSynthetic) != 0) {
			isSynthetic = true;
		}

	}

	@Override
	public boolean isAjSynthetic() {
		return isAjSynthetic;
	}

	@Override
	public boolean isSynthetic() {
		return isSynthetic;
	}

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

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

	@Override
	public AnnotationAJ[] getAnnotations() {
		ensureAnnotationTypesRetrieved();
		return annotations;
	}

	@Override
	public AnnotationAJ getAnnotationOfType(UnresolvedType ofType) {
		ensureAnnotationTypesRetrieved();
		for (AnnotationAJ annotation : annotations) {
			if (annotation.getTypeName().equals(ofType.getName())) {
				return annotation;
			}
		}
		return null;
	}

	private void ensureAnnotationTypesRetrieved() {
		if (annotationTypes == null) {
			AnnotationGen annos[] = field.getAnnotations();
			if (annos.length == 0) {
				annotationTypes = ResolvedType.EMPTY_ARRAY;
				annotations = AnnotationAJ.EMPTY_ARRAY;
			} else {
				int annosCount = annos.length;
				annotationTypes = new ResolvedType[annosCount];
				annotations = new AnnotationAJ[annosCount];
				for (int i = 0; i < annosCount; i++) {
					AnnotationGen anno = annos[i];
					annotations[i] = new BcelAnnotation(anno, world);
					annotationTypes[i] = annotations[i].getType();
				}
			}
		}
	}

	@Override
	public void addAnnotation(AnnotationAJ annotation) {
		ensureAnnotationTypesRetrieved();
		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;

		annotationsOnFieldObjectAreOutOfDate = true;
	}

	public void removeAnnotation(AnnotationAJ annotation) {
		ensureAnnotationTypesRetrieved();

		int len = annotations.length;
		AnnotationAJ[] ret = new AnnotationAJ[len - 1];
		int p = 0;
		for (AnnotationAJ anno : annotations) {
			if (!anno.getType().equals(annotation.getType())) {
				ret[p++] = anno;
			}
		}
		annotations = ret;

		ResolvedType[] newAnnotationTypes = new ResolvedType[len - 1];
		p = 0;
		for (ResolvedType anno : annotationTypes) {
			if (!anno.equals(annotation.getType())) {
				newAnnotationTypes[p++] = anno;
			}
		}
		annotationTypes = newAnnotationTypes;

		annotationsOnFieldObjectAreOutOfDate = true;
	}

	/**
	 * Unpack the generic signature attribute if there is one and we haven't already done so, then find the true field type of this
	 * field (eg. List<String>).
	 */
	@Override
	public UnresolvedType getGenericReturnType() {
		unpackGenericSignature();
		return genericFieldType;
	}

	public Field getFieldAsIs() {
		return field;
	}

	public Field getField(ConstantPool cpool) {
		if (!annotationsOnFieldObjectAreOutOfDate) {
			return field;
		}
		FieldGen newFieldGen = new FieldGen(field, cpool);
		newFieldGen.removeAnnotations();
		// List<AnnotationGen> alreadyHas = fg.getAnnotations();
		// if (annotations != null) {
		// fg.removeAnnotations();
		for (AnnotationAJ annotation : annotations) {
			newFieldGen.addAnnotation(new AnnotationGen(((BcelAnnotation) annotation).getBcelAnnotation(), cpool, true));
		}
		// for (int i = 0; i < annotations.length; i++) {
		// AnnotationAJ array_element = annotations[i];
		// boolean alreadyHasIt = false;
		// for (AnnotationGen gen : alreadyHas) {
		// if (gen.getTypeName().equals(array_element.getTypeName())) {
		// alreadyHasIt = true;
		// break;
		// }
		// }
		// if (!alreadyHasIt) {
		// fg.addAnnotation(new AnnotationGen(((BcelAnnotation) array_element).getBcelAnnotation(), cpg, true));
		// // }
		// // }
		// }
		field = newFieldGen.getField();
		annotationsOnFieldObjectAreOutOfDate = false; // we are now correct again
		return field;
	}

	private void unpackGenericSignature() {
		if (unpackedGenericSignature) {
			return;
		}
		if (!world.isInJava5Mode()) {
			this.genericFieldType = getReturnType();
			return;
		}
		unpackedGenericSignature = true;
		String gSig = field.getGenericSignature();
		if (gSig != null) {
			// get from generic
			GenericSignature.FieldTypeSignature fts = new GenericSignatureParser().parseAsFieldSignature(gSig);
			GenericSignature.ClassSignature genericTypeSig = bcelObjectType.getGenericClassTypeSignature();

			GenericSignature.FormalTypeParameter[] parentFormals = bcelObjectType.getAllFormals();
			GenericSignature.FormalTypeParameter[] typeVars = ((genericTypeSig == null) ? GenericSignature.FormalTypeParameter.NONE
					: genericTypeSig.formalTypeParameters);
			GenericSignature.FormalTypeParameter[] formals = new GenericSignature.FormalTypeParameter[parentFormals.length
					+ typeVars.length];
			// put method formal in front of type formals for overriding in
			// lookup
			System.arraycopy(typeVars, 0, formals, 0, typeVars.length);
			System.arraycopy(parentFormals, 0, formals, typeVars.length, parentFormals.length);

			try {
				genericFieldType = BcelGenericSignatureToTypeXConverter.fieldTypeSignature2TypeX(fts, formals, world);
			} catch (GenericSignatureFormatException e) {
				// development bug, fail fast with good info
				throw new IllegalStateException("While determing the generic field type of " + this.toString()
						+ " with generic signature " + gSig + " the following error was detected: " + e.getMessage());
			}
		} else {
			genericFieldType = getReturnType();
		}
	}

	@Override
	public void evictWeavingState() {
		if (field != null) {
			unpackGenericSignature();
			unpackAttributes(world);
			ensureAnnotationTypesRetrieved();
			// this.sourceContext = SourceContextImpl.UNKNOWN_SOURCE_CONTEXT;
			field = null;
		}
	}
}
