package japicmp.model;
import com.google.common.base.Optional;
import japicmp.cmp.JarArchiveComparator;
import japicmp.cmp.JarArchiveComparatorOptions;
import japicmp.util.AnnotationHelper;
import japicmp.util.Constants;
import japicmp.util.ModifierHelper;
import japicmp.util.OptionalHelper;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ExceptionsAttribute;
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.*;
public class JApiBehavior implements JApiHasModifiers, JApiHasChangeStatus, JApiHasAccessModifier, JApiHasStaticModifier,
JApiHasFinalModifier, JApiHasAbstractModifier, JApiCompatibility, JApiHasAnnotations, JApiHasBridgeModifier,
JApiCanBeSynthetic, JApiHasLineNumber {
private final JApiClass jApiClass;
private final String name;
private final JarArchiveComparator jarArchiveComparator;
private final List<JApiParameter> parameters = new LinkedList<>();
private final List<JApiAnnotation> annotations = new LinkedList<>();
private final JApiModifier<AccessModifier> accessModifier;
private final JApiModifier<FinalModifier> finalModifier;
private final JApiModifier<StaticModifier> staticModifier;
private final JApiModifier<AbstractModifier> abstractModifier;
private final JApiModifier<BridgeModifier> bridgeModifier;
private final JApiModifier<SyntheticModifier> syntheticModifier;
private final JApiAttribute<SyntheticAttribute> syntheticAttribute;
private final List<JApiException> exceptions;
protected JApiChangeStatus changeStatus;
private final Optional<Integer> oldLineNumber;
private final Optional<Integer> newLineNumber;
private final List<JApiCompatibilityChange> compatibilityChanges = new ArrayList<>();
public JApiBehavior(JApiClass jApiClass, String name, Optional<? extends CtBehavior> oldBehavior, Optional<? extends CtBehavior> newBehavior, JApiChangeStatus changeStatus, JarArchiveComparator jarArchiveComparator) {
this.jApiClass = jApiClass;
this.name = name;
this.jarArchiveComparator = jarArchiveComparator;
computeAnnotationChanges(annotations, oldBehavior, newBehavior, jarArchiveComparator.getJarArchiveComparatorOptions());
this.accessModifier = extractAccessModifier(oldBehavior, newBehavior);
this.finalModifier = extractFinalModifier(oldBehavior, newBehavior);
this.staticModifier = extractStaticModifier(oldBehavior, newBehavior);
this.abstractModifier = extractAbstractModifier(oldBehavior, newBehavior);
this.bridgeModifier = extractBridgeModifier(oldBehavior, newBehavior);
this.syntheticModifier = extractSyntheticModifier(oldBehavior, newBehavior);
this.syntheticAttribute = extractSyntheticAttribute(oldBehavior, newBehavior);
this.exceptions = computeExceptionChanges(oldBehavior, newBehavior);
this.changeStatus = evaluateChangeStatus(changeStatus);
this.oldLineNumber = getLineNumber(oldBehavior);
this.newLineNumber = getLineNumber(newBehavior);
}
private List<JApiException> computeExceptionChanges(Optional<? extends CtBehavior> oldMethodOptional, Optional<? extends CtBehavior> newMethodOptional) {
List<JApiException> exceptionList = new ArrayList<>();
if (oldMethodOptional.isPresent() && newMethodOptional.isPresent()) {
List<String> oldExceptions = extractExceptions(oldMethodOptional);
List<String> newExceptions = extractExceptions(newMethodOptional);
for (String oldException : oldExceptions) {
if (newExceptions.contains(oldException)) {
exceptionList.add(new JApiException(jarArchiveComparator, oldException, jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.NEW, oldException), JApiChangeStatus.UNCHANGED));
newExceptions.remove(oldException);
} else {
exceptionList.add(new JApiException(jarArchiveComparator, oldException, jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.OLD, oldException), JApiChangeStatus.REMOVED));
}
}
for (String newException : newExceptions) {
exceptionList.add(new JApiException(jarArchiveComparator, newException, jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.NEW, newException), JApiChangeStatus.NEW));
}
} else if (oldMethodOptional.isPresent()) {
List<String> exceptions = extractExceptions(oldMethodOptional);
for (String exception : exceptions) {
exceptionList.add(new JApiException(jarArchiveComparator, exception, jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.OLD, exception), JApiChangeStatus.REMOVED));
}
} else if (newMethodOptional.isPresent()) {
List<String> exceptions = extractExceptions(newMethodOptional);
for (String exception : exceptions) {
exceptionList.add(new JApiException(jarArchiveComparator, exception, jarArchiveComparator.loadClass(JarArchiveComparator.ArchiveType.NEW, exception), JApiChangeStatus.NEW));
}
}
return exceptionList;
}
private List<String> extractExceptions(Optional<? extends CtBehavior> methodOptional) {
if (methodOptional.isPresent()) {
ExceptionsAttribute exceptionsAttribute = methodOptional.get().getMethodInfo().getExceptionsAttribute();
String[] exceptions;
if (exceptionsAttribute != null) {
exceptions = exceptionsAttribute.getExceptions();
} else {
exceptions = new String[0];
}
List<String> list = new ArrayList<>(exceptions.length);
Collections.addAll(list, exceptions);
return list;
} else {
return Collections.emptyList();
}
}
private Optional<Integer> getLineNumber(Optional<? extends CtBehavior> methodOptional) {
Optional<Integer> lineNumberOptional = Optional.absent();
if (methodOptional.isPresent()) {
CtBehavior ctMethod = methodOptional.get();
int lineNumber = ctMethod.getMethodInfo().getLineNumber(0);
if (lineNumber >= 0) {
lineNumberOptional = Optional.of(lineNumber);
}
}
return lineNumberOptional;
}
@SuppressWarnings("unchecked")
private void computeAnnotationChanges(List<JApiAnnotation> annotations, Optional<? extends CtBehavior> oldBehavior, Optional<? extends CtBehavior> newBehavior, JarArchiveComparatorOptions options) {
if (oldBehavior.isPresent()) {
CtBehavior ctBehavior = oldBehavior.get();
if (ctBehavior instanceof CtMethod) {
computeAnnotationChangesMethod(annotations, (Optional<CtMethod>) oldBehavior, (Optional<CtMethod>) newBehavior, options);
} else if (ctBehavior instanceof CtConstructor) {
computeAnnotationChangesConstructor(annotations, (Optional<CtConstructor>) oldBehavior, (Optional<CtConstructor>) newBehavior, options);
}
} else if (newBehavior.isPresent()) {
CtBehavior ctBehavior = newBehavior.get();
if (ctBehavior instanceof CtMethod) {
computeAnnotationChangesMethod(annotations, (Optional<CtMethod>) oldBehavior, (Optional<CtMethod>) newBehavior, options);
} else if (ctBehavior instanceof CtConstructor) {
computeAnnotationChangesConstructor(annotations, (Optional<CtConstructor>) oldBehavior, (Optional<CtConstructor>) newBehavior, options);
}
}
}
private void computeAnnotationChangesMethod(List<JApiAnnotation> annotations, Optional<CtMethod> oldBehavior, Optional<CtMethod> newBehavior, JarArchiveComparatorOptions options) {
AnnotationHelper.computeAnnotationChanges(annotations, oldBehavior, newBehavior, options, new AnnotationHelper.AnnotationsAttributeCallback<CtMethod>() {
@Override
public AnnotationsAttribute getAnnotationsAttribute(CtMethod method) {
return (AnnotationsAttribute) method.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag);
}
});
}
private void computeAnnotationChangesConstructor(List<JApiAnnotation> annotations, Optional<CtConstructor> oldBehavior, Optional<CtConstructor> newBehavior, JarArchiveComparatorOptions options) {
AnnotationHelper.computeAnnotationChanges(annotations, oldBehavior, newBehavior, options, new AnnotationHelper.AnnotationsAttributeCallback<CtConstructor>() {
@Override
public AnnotationsAttribute getAnnotationsAttribute(CtConstructor method) {
return (AnnotationsAttribute) method.getMethodInfo().getAttribute(AnnotationsAttribute.visibleTag);
}
});
}
private JApiChangeStatus evaluateChangeStatus(JApiChangeStatus changeStatus) {
if (changeStatus == JApiChangeStatus.UNCHANGED) {
if (this.staticModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.finalModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.accessModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.abstractModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.syntheticAttribute.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
for (JApiException jApiException : exceptions) {
if (jApiException.getChangeStatus() == JApiChangeStatus.NEW || jApiException.getChangeStatus() == JApiChangeStatus.REMOVED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
}
}
return changeStatus;
}
protected JApiAttribute<SyntheticAttribute> extractSyntheticAttribute(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
JApiAttribute<SyntheticAttribute> jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
if (oldBehaviorOptional.isPresent() && newBehaviorOptional.isPresent()) {
CtBehavior oldBehavior = oldBehaviorOptional.get();
CtBehavior newBehavior = newBehaviorOptional.get();
byte[] attributeOldBehavior = oldBehavior.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
byte[] attributeNewBehavior = newBehavior.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
if (attributeOldBehavior != null && attributeNewBehavior != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
} else if (attributeOldBehavior != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.MODIFIED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.NON_SYNTHETIC));
} else if (attributeNewBehavior != 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 (oldBehaviorOptional.isPresent()) {
CtBehavior ctBehavior = oldBehaviorOptional.get();
byte[] attribute = ctBehavior.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 (newBehaviorOptional.isPresent()) {
CtBehavior ctBehavior = newBehaviorOptional.get();
byte[] attribute = ctBehavior.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;
}
public boolean hasSameParameter(JApiMethod method) {
boolean hasSameParameter = true;
List<JApiParameter> parameters1 = getParameters();
List<JApiParameter> parameters2 = method.getParameters();
if (parameters1.size() != parameters2.size()) {
hasSameParameter = false;
}
if (hasSameParameter) {
for (int i = 0; i < parameters1.size(); i++) {
if (!parameters1.get(i).getType().equals(parameters2.get(i).getType())) {
hasSameParameter = false;
}
}
}
return hasSameParameter;
}
private JApiModifier<StaticModifier> extractStaticModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<StaticModifier>() {
@Override
public StaticModifier getModifierForOld(CtBehavior oldBehavior) {
return Modifier.isStatic(oldBehavior.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
@Override
public StaticModifier getModifierForNew(CtBehavior newBehavior) {
return Modifier.isStatic(newBehavior.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
});
}
private JApiModifier<FinalModifier> extractFinalModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<FinalModifier>() {
@Override
public FinalModifier getModifierForOld(CtBehavior oldBehavior) {
return Modifier.isFinal(oldBehavior.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
@Override
public FinalModifier getModifierForNew(CtBehavior newBehavior) {
return Modifier.isFinal(newBehavior.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
});
}
private JApiModifier<AccessModifier> extractAccessModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<AccessModifier>() {
@Override
public AccessModifier getModifierForOld(CtBehavior oldBehavior) {
return ModifierHelper.translateToModifierLevel(oldBehavior.getModifiers());
}
@Override
public AccessModifier getModifierForNew(CtBehavior newBehavior) {
return ModifierHelper.translateToModifierLevel(newBehavior.getModifiers());
}
});
}
private JApiModifier<AbstractModifier> extractAbstractModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<AbstractModifier>() {
@Override
public AbstractModifier getModifierForOld(CtBehavior oldBehavior) {
return Modifier.isAbstract(oldBehavior.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
@Override
public AbstractModifier getModifierForNew(CtBehavior newBehavior) {
return Modifier.isAbstract(newBehavior.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
});
}
private JApiModifier<BridgeModifier> extractBridgeModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<BridgeModifier>() {
@Override
public BridgeModifier getModifierForOld(CtBehavior oldBehavior) {
return ModifierHelper.isBridge(oldBehavior.getModifiers()) ? BridgeModifier.BRIDGE : BridgeModifier.NON_BRIDGE;
}
@Override
public BridgeModifier getModifierForNew(CtBehavior newBehavior) {
return ModifierHelper.isBridge(newBehavior.getModifiers()) ? BridgeModifier.BRIDGE : BridgeModifier.NON_BRIDGE;
}
});
}
private JApiModifier<SyntheticModifier> extractSyntheticModifier(Optional<? extends CtBehavior> oldBehaviorOptional, Optional<? extends CtBehavior> newBehaviorOptional) {
return ModifierHelper.extractModifierFromBehavior(oldBehaviorOptional, newBehaviorOptional, new ModifierHelper.ExtractModifierFromBehaviorCallback<SyntheticModifier>() {
@Override
public SyntheticModifier getModifierForOld(CtBehavior oldBehavior) {
return ModifierHelper.isSynthetic(oldBehavior.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
@Override
public SyntheticModifier getModifierForNew(CtBehavior newBehavior) {
return ModifierHelper.isSynthetic(newBehavior.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
});
}
@XmlElementWrapper(name = "modifiers")
@XmlElement(name = "modifier")
public List<? extends JApiModifier<? extends Enum<? extends Enum<?>>>> getModifiers() {
return Arrays.asList(this.finalModifier, this.staticModifier, this.accessModifier, this.abstractModifier, this.bridgeModifier, this.syntheticModifier);
}
@XmlAttribute
public String getName() {
return name;
}
@XmlAttribute
public JApiChangeStatus getChangeStatus() {
return changeStatus;
}
@XmlElementWrapper(name = "parameters")
@XmlElement(name = "parameter")
public List<JApiParameter> getParameters() {
return parameters;
}
public void addParameter(JApiParameter jApiParameter) {
parameters.add(jApiParameter);
}
@XmlTransient
public JApiModifier<AccessModifier> getAccessModifier() {
return accessModifier;
}
@XmlTransient
public JApiModifier<FinalModifier> getFinalModifier() {
return finalModifier;
}
@XmlTransient
public JApiModifier<StaticModifier> getStaticModifier() {
return staticModifier;
}
public JApiModifier<AbstractModifier> getAbstractModifier() {
return this.abstractModifier;
}
@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<BridgeModifier> getBridgeModifier() {
return this.bridgeModifier;
}
@XmlTransient
public JApiModifier<SyntheticModifier> getSyntheticModifier() {
return this.syntheticModifier;
}
@XmlTransient
public JApiAttribute<SyntheticAttribute> getSyntheticAttribute() {
return syntheticAttribute;
}
@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;
}
@Override
public Optional<Integer> getOldLineNumber() {
return this.oldLineNumber;
}
@Override
public Optional<Integer> geNewLineNumber() {
return this.newLineNumber;
}
@XmlAttribute(name = "oldLineNumber")
public String getOldLineNumberAsString() {
return OptionalHelper.optionalToString(this.oldLineNumber);
}
@XmlAttribute(name = "newLineNumber")
public String getNewLineNumberAsString() {
return OptionalHelper.optionalToString(this.newLineNumber);
}
@XmlElementWrapper(name = "exceptions")
@XmlElement(name = "exception")
public List<JApiException> getExceptions() {
return exceptions;
}
@XmlTransient
public JApiClass getjApiClass() {
return this.jApiClass;
}
}