package japicmp.model;
import com.google.common.base.Optional;
import japicmp.cmp.JarArchiveComparatorOptions;
import japicmp.util.AnnotationHelper;
import japicmp.util.Constants;
import japicmp.util.MethodDescriptorParser;
import japicmp.util.ModifierHelper;
import javassist.CtField;
import javassist.Modifier;
import javassist.bytecode.AnnotationsAttribute;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;
import javax.xml.bind.annotation.XmlTransient;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
public class JApiField implements JApiHasChangeStatus, JApiHasModifiers, JApiHasAccessModifier, JApiHasStaticModifier,
JApiHasFinalModifier, JApiHasTransientModifier, JApiCompatibility, JApiHasAnnotations, JApiCanBeSynthetic {
private final JApiChangeStatus changeStatus;
private final JApiClass jApiClass;
private final Optional<CtField> oldFieldOptional;
private final Optional<CtField> newFieldOptional;
private final List<JApiAnnotation> annotations = new LinkedList<>();
private final JApiModifier<AccessModifier> accessModifier;
private final JApiModifier<StaticModifier> staticModifier;
private final JApiModifier<FinalModifier> finalModifier;
private final JApiModifier<TransientModifier> transientModifier;
private final JApiModifier<SyntheticModifier> syntheticModifier;
private final JApiAttribute<SyntheticAttribute> syntheticAttribute;
private final List<JApiCompatibilityChange> compatibilityChanges = new ArrayList<>();
private final JApiType type;
public JApiField(JApiClass jApiClass, JApiChangeStatus changeStatus, Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional, JarArchiveComparatorOptions options) {
this.jApiClass = jApiClass;
this.oldFieldOptional = oldFieldOptional;
this.newFieldOptional = newFieldOptional;
computeAnnotationChanges(this.annotations, oldFieldOptional, newFieldOptional, options);
this.accessModifier = extractAccessModifier(oldFieldOptional, newFieldOptional);
this.staticModifier = extractStaticModifier(oldFieldOptional, newFieldOptional);
this.finalModifier = extractFinalModifier(oldFieldOptional, newFieldOptional);
this.transientModifier = extractTransientModifier(oldFieldOptional, newFieldOptional);
this.syntheticModifier = extractSyntheticModifier(oldFieldOptional, newFieldOptional);
this.syntheticAttribute = extractSyntheticAttribute(oldFieldOptional, newFieldOptional);
this.type = extractType(oldFieldOptional, newFieldOptional);
this.changeStatus = evaluateChangeStatus(changeStatus);
}
private void computeAnnotationChanges(List<JApiAnnotation> annotations, Optional<CtField> oldBehavior, Optional<CtField> newBehavior, JarArchiveComparatorOptions options) {
AnnotationHelper.computeAnnotationChanges(annotations, oldBehavior, newBehavior, options, new AnnotationHelper.AnnotationsAttributeCallback<CtField>() {
@Override
public AnnotationsAttribute getAnnotationsAttribute(CtField field) {
return (AnnotationsAttribute) field.getFieldInfo().getAttribute(AnnotationsAttribute.visibleTag);
}
});
}
private JApiType extractType(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
JApiType jApiType = new JApiType(Optional.<String>absent(), Optional.<String>absent(), JApiChangeStatus.UNCHANGED);
if (oldFieldOptional.isPresent() && newFieldOptional.isPresent()) {
CtField oldField = oldFieldOptional.get();
CtField newField = newFieldOptional.get();
String oldType = signatureToType(oldField.getSignature());
String newType = signatureToType(newField.getSignature());
if (oldType.equals(newType)) {
jApiType = new JApiType(Optional.of(oldType), Optional.of(newType), JApiChangeStatus.UNCHANGED);
} else {
jApiType = new JApiType(Optional.of(oldType), Optional.of(newType), JApiChangeStatus.MODIFIED);
}
} else {
if (oldFieldOptional.isPresent()) {
CtField oldField = oldFieldOptional.get();
String oldType = signatureToType(oldField.getSignature());
jApiType = new JApiType(Optional.of(oldType), Optional.<String>absent(), JApiChangeStatus.REMOVED);
} else if (newFieldOptional.isPresent()) {
CtField newField = newFieldOptional.get();
String newType = signatureToType(newField.getSignature());
jApiType = new JApiType(Optional.<String>absent(), Optional.of(newType), JApiChangeStatus.NEW);
}
}
return jApiType;
}
private String signatureToType(String signature) {
MethodDescriptorParser methodDescriptorParser = new MethodDescriptorParser();
List<String> types = methodDescriptorParser.parseTypes(signature);
if (types.size() > 0) {
return types.get(0);
}
return "n.a.";
}
private JApiChangeStatus evaluateChangeStatus(JApiChangeStatus changeStatus) {
if (changeStatus == JApiChangeStatus.UNCHANGED) {
if (this.accessModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.staticModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.finalModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.transientModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.syntheticAttribute.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.type.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
}
return changeStatus;
}
private JApiAttribute<SyntheticAttribute> extractSyntheticAttribute(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
JApiAttribute<SyntheticAttribute> jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
if (oldFieldOptional.isPresent() && newFieldOptional.isPresent()) {
CtField oldField = oldFieldOptional.get();
CtField newField = newFieldOptional.get();
byte[] attributeOldField = oldField.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
byte[] attributeNewField = newField.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
if (attributeOldField != null && attributeNewField != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
} else if (attributeOldField != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.MODIFIED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.NON_SYNTHETIC));
} else if (attributeNewField != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.MODIFIED, Optional.of(SyntheticAttribute.NON_SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
} else {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.NON_SYNTHETIC), Optional.of(SyntheticAttribute.NON_SYNTHETIC));
}
} else {
if (oldFieldOptional.isPresent()) {
CtField ctField = oldFieldOptional.get();
byte[] attribute = ctField.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
if (attribute != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.REMOVED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.<SyntheticAttribute>absent());
} else {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.REMOVED, Optional.of(SyntheticAttribute.NON_SYNTHETIC), Optional.<SyntheticAttribute>absent());
}
}
if (newFieldOptional.isPresent()) {
CtField ctField = newFieldOptional.get();
byte[] attribute = ctField.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
if (attribute != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.NEW, Optional.<SyntheticAttribute>absent(), Optional.of(SyntheticAttribute.SYNTHETIC));
} else {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.NEW, Optional.<SyntheticAttribute>absent(), Optional.of(SyntheticAttribute.NON_SYNTHETIC));
}
}
}
return jApiAttribute;
}
private JApiModifier<StaticModifier> extractStaticModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<StaticModifier>() {
@Override
public StaticModifier getModifierForOld(CtField oldField) {
return Modifier.isStatic(oldField.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
@Override
public StaticModifier getModifierForNew(CtField newField) {
return Modifier.isStatic(newField.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
});
}
private JApiModifier<FinalModifier> extractFinalModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<FinalModifier>() {
@Override
public FinalModifier getModifierForOld(CtField oldField) {
return Modifier.isFinal(oldField.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
@Override
public FinalModifier getModifierForNew(CtField newField) {
return Modifier.isFinal(newField.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
});
}
private JApiModifier<AccessModifier> extractAccessModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<AccessModifier>() {
@Override
public AccessModifier getModifierForOld(CtField oldField) {
return ModifierHelper.translateToModifierLevel(oldField.getModifiers());
}
@Override
public AccessModifier getModifierForNew(CtField newField) {
return ModifierHelper.translateToModifierLevel(newField.getModifiers());
}
});
}
private JApiModifier<AbstractModifier> extractAbstractModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<AbstractModifier>() {
@Override
public AbstractModifier getModifierForOld(CtField oldField) {
return Modifier.isAbstract(oldField.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
@Override
public AbstractModifier getModifierForNew(CtField newField) {
return Modifier.isAbstract(newField.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
});
}
private JApiModifier<TransientModifier> extractTransientModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<TransientModifier>() {
@Override
public TransientModifier getModifierForOld(CtField oldField) {
return Modifier.isTransient(oldField.getModifiers()) ? TransientModifier.TRANSIENT : TransientModifier.NON_TRANSIENT;
}
@Override
public TransientModifier getModifierForNew(CtField newField) {
return Modifier.isTransient(newField.getModifiers()) ? TransientModifier.TRANSIENT : TransientModifier.NON_TRANSIENT;
}
});
}
private JApiModifier<SyntheticModifier> extractSyntheticModifier(Optional<CtField> oldFieldOptional, Optional<CtField> newFieldOptional) {
return ModifierHelper.extractModifierFromField(oldFieldOptional, newFieldOptional, new ModifierHelper.ExtractModifierFromFieldCallback<SyntheticModifier>() {
@Override
public SyntheticModifier getModifierForOld(CtField oldField) {
return ModifierHelper.isSynthetic(oldField.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
@Override
public SyntheticModifier getModifierForNew(CtField newField) {
return ModifierHelper.isSynthetic(newField.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
});
}
@XmlAttribute(name = "changeStatus")
public JApiChangeStatus getChangeStatus() {
return changeStatus;
}
@XmlAttribute(name = "name")
public String getName() {
String name = "n.a.";
if (oldFieldOptional.isPresent()) {
name = oldFieldOptional.get().getName();
}
if (newFieldOptional.isPresent()) {
name = newFieldOptional.get().getName();
}
return name;
}
@XmlTransient
public Optional<CtField> getOldFieldOptional() {
return oldFieldOptional;
}
@XmlTransient
public Optional<CtField> getNewFieldOptional() {
return newFieldOptional;
}
@XmlElementWrapper(name = "modifiers")
@XmlElement(name = "modifier")
public List<? extends JApiModifier<? extends Enum<? extends Enum<?>>>> getModifiers() {
return Arrays.asList(this.accessModifier, this.staticModifier, this.finalModifier, this.syntheticModifier);
}
@XmlTransient
public JApiModifier<StaticModifier> getStaticModifier() {
return staticModifier;
}
@XmlTransient
public JApiModifier<FinalModifier> getFinalModifier() {
return finalModifier;
}
@XmlTransient
public JApiModifier<TransientModifier> getTransientModifier() {
return transientModifier;
}
@XmlTransient
public JApiModifier<AccessModifier> getAccessModifier() {
return accessModifier;
}
@XmlElementWrapper(name = "attributes")
@XmlElement(name = "attribute")
public List<JApiAttribute<? extends Enum<?>>> getAttributes() {
List<JApiAttribute<? extends Enum<?>>> list = new ArrayList<>();
list.add(this.syntheticAttribute);
return list;
}
@XmlTransient
public JApiModifier<SyntheticModifier> getSyntheticModifier() {
return this.syntheticModifier;
}
@XmlTransient
public JApiAttribute<SyntheticAttribute> getSyntheticAttribute() {
return syntheticAttribute;
}
@XmlElement(name = "type")
public JApiType getType() {
return type;
}
@Override
@XmlAttribute
public boolean isBinaryCompatible() {
boolean binaryCompatible = true;
for (JApiCompatibilityChange compatibilityChange : compatibilityChanges) {
if (!compatibilityChange.isBinaryCompatible()) {
binaryCompatible = false;
}
}
return binaryCompatible;
}
@Override
@XmlAttribute
public boolean isSourceCompatible() {
boolean sourceCompatible = true;
for (JApiCompatibilityChange compatibilityChange : compatibilityChanges) {
if (!compatibilityChange.isSourceCompatible()) {
sourceCompatible = false;
}
}
return sourceCompatible;
}
@XmlElementWrapper(name = "compatibilityChanges")
@XmlElement(name = "compatibilityChange")
public List<JApiCompatibilityChange> getCompatibilityChanges() {
return compatibilityChanges;
}
@XmlElementWrapper(name = "annotations")
@XmlElement(name = "annotation")
public List<JApiAnnotation> getAnnotations() {
return annotations;
}
@XmlTransient
public JApiClass getjApiClass() {
return jApiClass;
}
}