package japicmp.model;
import com.google.common.base.Optional;
import japicmp.cmp.JarArchiveComparator;
import japicmp.cmp.JarArchiveComparatorOptions;
import japicmp.exception.JApiCmpException;
import japicmp.util.*;
import javassist.*;
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.*;
public class JApiClass implements JApiHasModifiers, JApiHasChangeStatus, JApiHasAccessModifier, JApiHasStaticModifier, JApiHasFinalModifier, JApiHasAbstractModifier,
JApiCompatibility, JApiHasAnnotations, JApiJavaObjectSerializationCompatibility, JApiCanBeSynthetic {
private final JarArchiveComparator jarArchiveComparator;
private final String fullyQualifiedName;
private final JApiClassType classType;
private final JarArchiveComparatorOptions options;
private final Optional<CtClass> oldClass;
private final Optional<CtClass> newClass;
private final JApiChangeStatus changeStatus;
private final JApiSuperclass superclass;
private final List<JApiImplementedInterface> interfaces = new LinkedList<>();
private final List<JApiField> fields = new LinkedList<>();
private final List<JApiConstructor> constructors = new LinkedList<>();
private final List<JApiMethod> methods = 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<SyntheticModifier> syntheticModifier;
private final JApiAttribute<SyntheticAttribute> syntheticAttribute;
private final List<JApiCompatibilityChange> compatibilityChanges = new LinkedList<>();
private final JApiSerialVersionUid jApiSerialVersionUid;
private JApiJavaObjectSerializationChangeStatus jApiJavaObjectSerializationChangeStatus = JApiJavaObjectSerializationChangeStatus.NOT_SERIALIZABLE;
private boolean changeCausedByClassElement = false;
public JApiClass(JarArchiveComparator jarArchiveComparator, String fullyQualifiedName, Optional<CtClass> oldClass, Optional<CtClass> newClass, JApiChangeStatus changeStatus, JApiClassType classType) {
this.jarArchiveComparator = jarArchiveComparator;
this.options = this.jarArchiveComparator.getJarArchiveComparatorOptions();
this.fullyQualifiedName = fullyQualifiedName;
this.newClass = newClass;
this.oldClass = oldClass;
this.classType = classType;
this.superclass = extractSuperclass(oldClass, newClass);
computeMethodChanges(this, oldClass, newClass);
computeInterfaceChanges(this.interfaces, oldClass, newClass);
computeFieldChanges(this.fields, oldClass, newClass);
computeAnnotationChanges(this.annotations, oldClass, newClass);
this.accessModifier = extractAccessModifier(oldClass, newClass);
this.finalModifier = extractFinalModifier(oldClass, newClass);
this.staticModifier = extractStaticModifier(oldClass, newClass);
this.abstractModifier = extractAbstractModifier(oldClass, newClass);
this.syntheticModifier = extractSyntheticModifier(oldClass, newClass);
this.syntheticAttribute = extractSyntheticAttribute(oldClass, newClass);
this.jApiSerialVersionUid = JavaObjectSerializationCompatibility.extractSerialVersionUid(options, jarArchiveComparator, oldClass, newClass);
this.changeStatus = evaluateChangeStatus(changeStatus);
}
private void computeAnnotationChanges(List<JApiAnnotation> annotations, Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
AnnotationHelper.computeAnnotationChanges(annotations, oldClassOptional, newClassOptional, options, new AnnotationHelper.AnnotationsAttributeCallback<CtClass>() {
@Override
public AnnotationsAttribute getAnnotationsAttribute(CtClass ctClass) {
return (AnnotationsAttribute) ctClass.getClassFile().getAttribute(AnnotationsAttribute.visibleTag);
}
});
}
private JApiAttribute<SyntheticAttribute> extractSyntheticAttribute(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
JApiAttribute<SyntheticAttribute> jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
if (oldClassOptional.isPresent() && newClassOptional.isPresent()) {
CtClass oldClass = oldClassOptional.get();
CtClass newClass = newClassOptional.get();
byte[] attributeOldClass = oldClass.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
byte[] attributeNewClass = newClass.getAttribute(Constants.JAVA_CONSTPOOL_ATTRIBUTE_SYNTHETIC);
if (attributeOldClass != null && attributeNewClass != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.UNCHANGED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.SYNTHETIC));
} else if (attributeOldClass != null) {
jApiAttribute = new JApiAttribute<>(JApiChangeStatus.MODIFIED, Optional.of(SyntheticAttribute.SYNTHETIC), Optional.of(SyntheticAttribute.NON_SYNTHETIC));
} else if (attributeNewClass != 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 (oldClassOptional.isPresent()) {
CtClass ctClass = oldClassOptional.get();
byte[] attribute = ctClass.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 (newClassOptional.isPresent()) {
CtClass ctClass = newClassOptional.get();
byte[] attribute = ctClass.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 void computeFieldChanges(List<JApiField> fields, Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
if (oldClassOptional.isPresent() && newClassOptional.isPresent()) {
CtClass oldClass = oldClassOptional.get();
CtClass newClass = newClassOptional.get();
Map<String, CtField> oldFieldsMap = buildFieldMap(oldClass);
Map<String, CtField> newFieldsMap = buildFieldMap(newClass);
for (CtField oldField : oldFieldsMap.values()) {
String oldFieldName = oldField.getName();
CtField newField = newFieldsMap.get(oldFieldName);
if (newField != null) {
JApiField jApiField = new JApiField(this, JApiChangeStatus.UNCHANGED, Optional.of(oldField), Optional.of(newField), options);
if (includeField(jApiField)) {
fields.add(jApiField);
}
} else {
JApiField jApiField = new JApiField(this, JApiChangeStatus.REMOVED, Optional.of(oldField), Optional.<CtField>absent(), options);
if (includeField(jApiField)) {
fields.add(jApiField);
}
}
}
for (CtField newField : newFieldsMap.values()) {
CtField oldField = oldFieldsMap.get(newField.getName());
if (oldField == null) {
JApiField jApiField = new JApiField(this, JApiChangeStatus.NEW, Optional.<CtField>absent(), Optional.of(newField), options);
if (includeField(jApiField)) {
fields.add(jApiField);
}
}
}
} else {
if (oldClassOptional.isPresent()) {
Map<String, CtField> fieldMap = buildFieldMap(oldClassOptional.get());
for (CtField field : fieldMap.values()) {
JApiField jApiField = new JApiField(this, JApiChangeStatus.REMOVED, Optional.of(field), Optional.<CtField>absent(), options);
if (includeField(jApiField)) {
fields.add(jApiField);
}
}
}
if (newClassOptional.isPresent()) {
Map<String, CtField> fieldMap = buildFieldMap(newClassOptional.get());
for (CtField field : fieldMap.values()) {
JApiField jApiField = new JApiField(this, JApiChangeStatus.NEW, Optional.<CtField>absent(), Optional.of(field), options);
if (includeField(jApiField)) {
fields.add(jApiField);
}
}
}
}
}
private boolean includeField(JApiField jApiField) {
return ModifierHelper.matchesModifierLevel(jApiField, options.getAccessModifier());
}
private Map<String, CtField> buildFieldMap(CtClass ctClass) {
Map<String, CtField> fieldMap = new HashMap<>();
CtField[] declaredFields = ctClass.getDeclaredFields();
for (CtField field : declaredFields) {
if (options.getFilters().includeField(field)) {
String name = field.getName();
fieldMap.put(name, field);
}
}
return fieldMap;
}
private JApiSuperclass extractSuperclass(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
JApiSuperclass retVal = new JApiSuperclass(this, Optional.<CtClass>absent(), Optional.<CtClass>absent(), JApiChangeStatus.UNCHANGED, jarArchiveComparator);
if (oldClassOptional.isPresent() && newClassOptional.isPresent()) {
CtClass oldClass = oldClassOptional.get();
CtClass newClass = newClassOptional.get();
Optional<CtClass> superclassOldOptional = getSuperclass(oldClass);
Optional<CtClass> superclassNewOptional = getSuperclass(newClass);
if (superclassOldOptional.isPresent() && superclassNewOptional.isPresent()) {
String nameOld = superclassOldOptional.get().getName();
String nameNew = superclassNewOptional.get().getName();
retVal = new JApiSuperclass(this, superclassOldOptional, superclassNewOptional, nameOld.equals(nameNew) ? JApiChangeStatus.UNCHANGED : JApiChangeStatus.MODIFIED, jarArchiveComparator);
} else if (superclassOldOptional.isPresent()) {
retVal = new JApiSuperclass(this, superclassOldOptional, superclassNewOptional, JApiChangeStatus.REMOVED, jarArchiveComparator);
} else if (superclassNewOptional.isPresent()) {
retVal = new JApiSuperclass(this, superclassOldOptional, superclassNewOptional, JApiChangeStatus.NEW, jarArchiveComparator);
} else {
retVal = new JApiSuperclass(this, superclassOldOptional, superclassNewOptional, JApiChangeStatus.UNCHANGED, jarArchiveComparator);
}
} else {
if (oldClassOptional.isPresent()) {
Optional<CtClass> superclassOldOptional = getSuperclass(oldClassOptional.get());
if (superclassOldOptional.isPresent()) {
retVal = new JApiSuperclass(this, superclassOldOptional, Optional.<CtClass>absent(), JApiChangeStatus.REMOVED, jarArchiveComparator);
} else {
retVal = new JApiSuperclass(this, Optional.<CtClass>absent(), Optional.<CtClass>absent(), JApiChangeStatus.UNCHANGED, jarArchiveComparator);
}
} else if (newClassOptional.isPresent()) {
Optional<CtClass> superclassNewOptional = getSuperclass(newClassOptional.get());
if (superclassNewOptional.isPresent()) {
retVal = new JApiSuperclass(this, Optional.<CtClass>absent(), superclassNewOptional, JApiChangeStatus.NEW, jarArchiveComparator);
} else {
retVal = new JApiSuperclass(this, Optional.<CtClass>absent(), Optional.<CtClass>absent(), JApiChangeStatus.UNCHANGED, jarArchiveComparator);
}
}
}
retVal.setJApiClass(this);
return retVal;
}
private Optional<CtClass> getSuperclass(CtClass ctClass) {
try {
CtClass superClass = ctClass.getSuperclass();
return Optional.fromNullable(superClass);
} catch (NotFoundException e) {
if (options.getIgnoreMissingClasses().ignoreClass(e.getMessage())) {
return Optional.absent();
} else {
throw JApiCmpException.forClassLoading(e, e.getMessage(), jarArchiveComparator);
}
}
}
private void computeInterfaceChanges(List<JApiImplementedInterface> interfacesArg, Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
if (oldClassOptional.isPresent() && newClassOptional.isPresent()) {
CtClass oldClass = oldClassOptional.get();
CtClass newClass = newClassOptional.get();
Map<String, CtClass> interfaceMapOldClass = buildInterfaceMap(oldClass, JarArchiveComparator.ArchiveType.OLD);
Map<String, CtClass> interfaceMapNewClass = buildInterfaceMap(newClass, JarArchiveComparator.ArchiveType.NEW);
for (CtClass oldInterface : interfaceMapOldClass.values()) {
CtClass ctClassFound = interfaceMapNewClass.get(oldInterface.getName());
if (ctClassFound != null) {
JApiImplementedInterface jApiClass = new JApiImplementedInterface(oldInterface, oldInterface.getName(), JApiChangeStatus.UNCHANGED);
interfacesArg.add(jApiClass);
} else {
JApiImplementedInterface jApiClass = new JApiImplementedInterface(oldInterface, oldInterface.getName(), JApiChangeStatus.REMOVED);
interfacesArg.add(jApiClass);
}
}
for (CtClass newInterface : interfaceMapNewClass.values()) {
CtClass ctClassFound = interfaceMapOldClass.get(newInterface.getName());
if (ctClassFound == null) {
JApiImplementedInterface jApiClass = new JApiImplementedInterface(newInterface, newInterface.getName(), JApiChangeStatus.NEW);
interfacesArg.add(jApiClass);
}
}
} else {
if (oldClassOptional.isPresent()) {
Map<String, CtClass> interfaceMap = buildInterfaceMap(oldClassOptional.get(), JarArchiveComparator.ArchiveType.OLD);
for (CtClass ctClass : interfaceMap.values()) {
JApiImplementedInterface jApiClass = new JApiImplementedInterface(ctClass, ctClass.getName(), JApiChangeStatus.REMOVED);
interfacesArg.add(jApiClass);
}
} else if (newClassOptional.isPresent()) {
Map<String, CtClass> interfaceMap = buildInterfaceMap(newClassOptional.get(), JarArchiveComparator.ArchiveType.NEW);
for (CtClass ctClass : interfaceMap.values()) {
JApiImplementedInterface jApiClass = new JApiImplementedInterface(ctClass, ctClass.getName(), JApiChangeStatus.NEW);
interfacesArg.add(jApiClass);
}
}
}
}
private Map<String, CtClass> buildInterfaceMap(CtClass ctClass, JarArchiveComparator.ArchiveType archiveType) {
Map<String, CtClass> map = new HashMap<>();
buildInterfaceMap(ctClass, archiveType, map);
return map;
}
private void buildInterfaceMap(CtClass ctClass, JarArchiveComparator.ArchiveType archiveType, Map<String, CtClass> map) {
try {
CtClass[] interfaces = ctClass.getInterfaces();
for (CtClass ctInterface : interfaces) {
map.put(ctInterface.getName(), ctInterface);
buildInterfaceMap(archiveType, map, ctInterface);
}
Optional<CtClass> superClassOptional = getSuperclass(ctClass);
if (superClassOptional.isPresent()) {
buildInterfaceMap(superClassOptional.get(), archiveType, map);
}
} catch (NotFoundException e) {
if (!options.getIgnoreMissingClasses().ignoreClass(e.getMessage())) {
throw JApiCmpException.forClassLoading(e, "Class not found: " + e.getMessage(), jarArchiveComparator);
}
}
}
private void buildInterfaceMap(JarArchiveComparator.ArchiveType archiveType, Map<String, CtClass> map, CtClass ctInterface) throws NotFoundException {
Optional<CtClass> loadedInterfaceOptional = this.jarArchiveComparator.loadClass(archiveType, ctInterface.getName());
if (loadedInterfaceOptional.isPresent()) {
CtClass loadedInterface = loadedInterfaceOptional.get();
CtClass[] loadedInterfaceInterfaces = loadedInterface.getInterfaces();
for (CtClass additionalInterface : loadedInterfaceInterfaces) {
map.put(additionalInterface.getName(), additionalInterface);
buildInterfaceMap(archiveType, map, additionalInterface);
}
}
}
private void computeMethodChanges(JApiClass jApiClass, Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
Map<String, List<CtMethod>> oldMethodsMap = createMethodMap(oldClassOptional);
Map<String, List<CtMethod>> newMethodsMap = createMethodMap(newClassOptional);
sortMethodsIntoLists(jApiClass, oldMethodsMap, newMethodsMap);
Map<String, CtConstructor> oldConstructorsMap = createConstructorMap(oldClassOptional);
Map<String, CtConstructor> newConstructorsMap = createConstructorMap(newClassOptional);
sortConstructorsIntoLists(jApiClass, oldConstructorsMap, newConstructorsMap);
}
private void sortMethodsIntoLists(JApiClass jApiClass, Map<String, List<CtMethod>> oldMethodsMap, Map<String, List<CtMethod>> newMethodsMap) {
MethodDescriptorParser methodDescriptorParser = new MethodDescriptorParser();
for (String methodName : oldMethodsMap.keySet()) {
List<CtMethod> oldMethodsWithSameName = oldMethodsMap.get(methodName);
Iterator<CtMethod> oldMethodsWithSameNameIterator = oldMethodsWithSameName.iterator();
while (oldMethodsWithSameNameIterator.hasNext()) {
CtMethod oldMethod = oldMethodsWithSameNameIterator.next();
methodDescriptorParser.parse(oldMethod.getSignature());
List<CtMethod> newMethodsWithSameName = newMethodsMap.get(methodName);
if (newMethodsWithSameName == null) {
JApiMethod jApiMethod = new JApiMethod(jApiClass, oldMethod.getName(), JApiChangeStatus.REMOVED, Optional.of(oldMethod), Optional.<CtMethod>absent(), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
} else {
Optional<CtMethod> matchingMethodOptional = findMatchingMethod(oldMethod, newMethodsWithSameName);
if (matchingMethodOptional.isPresent()) {
CtMethod matchingMethod = matchingMethodOptional.get();
JApiMethod jApiMethod = new JApiMethod(jApiClass, oldMethod.getName(), JApiChangeStatus.UNCHANGED, Optional.of(oldMethod), Optional.of(matchingMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
oldMethodsWithSameNameIterator.remove();
newMethodsWithSameName.remove(matchingMethod);
} else {
JApiMethod jApiMethod = new JApiMethod(jApiClass, oldMethod.getName(), JApiChangeStatus.REMOVED, Optional.of(oldMethod), Optional.<CtMethod>absent(), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
}
}
}
}
for (String methodName : newMethodsMap.keySet()) {
List<CtMethod> ctMethods = newMethodsMap.get(methodName);
for (CtMethod ctMethod : ctMethods) {
methodDescriptorParser.parse(ctMethod.getSignature());
List<CtMethod> methodsWithSameName = oldMethodsMap.get(ctMethod.getName());
if (methodsWithSameName == null) {
JApiMethod jApiMethod = new JApiMethod(jApiClass, ctMethod.getName(), JApiChangeStatus.NEW, Optional.<CtMethod>absent(), Optional.of(ctMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
} else {
Optional<CtMethod> matchingMethodOptional = findMatchingMethod(ctMethod, methodsWithSameName);
if (matchingMethodOptional.isPresent()) {
CtMethod matchingMethod = matchingMethodOptional.get();
JApiMethod jApiMethod = new JApiMethod(jApiClass, ctMethod.getName(), JApiChangeStatus.UNCHANGED, Optional.of(ctMethod), Optional.of(matchingMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
} else {
JApiMethod jApiMethod = new JApiMethod(jApiClass, ctMethod.getName(), JApiChangeStatus.NEW, Optional.<CtMethod>absent(), Optional.of(ctMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiMethod);
if (includeMethod(jApiMethod)) {
methods.add(jApiMethod);
}
}
}
}
}
}
private Optional<CtMethod> findMatchingMethod(CtMethod method, List<CtMethod> candidates) {
Optional<CtMethod> found = Optional.absent();
SignatureParser methodSignatureParser = new SignatureParser();
methodSignatureParser.parse(method.getSignature());
List<CtMethod> methodsWithSameParameters = new ArrayList<>();
for (CtMethod candidate : candidates) {
boolean parameterListsEqual = true;
List<String> probeParameters = methodSignatureParser.getParameters();
SignatureParser candidateSignatureParser = new SignatureParser();
candidateSignatureParser.parse(candidate.getSignature());
List<String> candidateParameters = candidateSignatureParser.getParameters();
if (probeParameters.size() != candidateParameters.size()) {
parameterListsEqual = false;
}
if (parameterListsEqual) {
for (int i = 0; i < probeParameters.size(); i++) {
String probeParameter = probeParameters.get(i);
String candidateParameter = candidateParameters.get(i);
if (!probeParameter.equals(candidateParameter)) {
parameterListsEqual = false;
}
}
}
if (parameterListsEqual) {
methodsWithSameParameters.add(candidate);
}
}
if (methodsWithSameParameters.size() == 1) {
found = Optional.of(methodsWithSameParameters.get(0));
} else if (methodsWithSameParameters.size() > 1) {
CtMethod methodWithSameReturnType = null;
String probeReturnType = methodSignatureParser.getReturnType();
for (CtMethod candidate : methodsWithSameParameters) {
SignatureParser candidateSignatureParser = new SignatureParser();
candidateSignatureParser.parse(candidate.getSignature());
String candidateReturnType = candidateSignatureParser.getReturnType();
if (probeReturnType.equals(candidateReturnType)) {
methodWithSameReturnType = candidate;
}
}
if (methodWithSameReturnType != null) {
found = Optional.of(methodWithSameReturnType);
} else {
found = Optional.of(methodsWithSameParameters.get(0));
}
}
return found;
}
private boolean includeMethod(JApiMethod jApiMethod) {
return ModifierHelper.matchesModifierLevel(jApiMethod, options.getAccessModifier());
}
private void sortConstructorsIntoLists(JApiClass jApiClass, Map<String, CtConstructor> oldConstructorsMap, Map<String, CtConstructor> newConstructorsMap) {
MethodDescriptorParser methodDescriptorParser = new MethodDescriptorParser();
for (CtConstructor ctMethod : oldConstructorsMap.values()) {
String longName = ctMethod.getLongName();
methodDescriptorParser.parse(ctMethod.getSignature());
CtConstructor foundMethod = newConstructorsMap.get(longName);
if (foundMethod == null) {
JApiConstructor jApiConstructor = new JApiConstructor(jApiClass, ctMethod.getName(), JApiChangeStatus.REMOVED, Optional.of(ctMethod), Optional.<CtConstructor>absent(), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiConstructor);
if (includeConstructor(jApiConstructor)) {
constructors.add(jApiConstructor);
}
} else {
JApiConstructor jApiConstructor = new JApiConstructor(jApiClass, ctMethod.getName(), JApiChangeStatus.UNCHANGED, Optional.of(ctMethod), Optional.of(foundMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiConstructor);
if (includeConstructor(jApiConstructor)) {
constructors.add(jApiConstructor);
}
}
}
for (CtConstructor ctMethod : newConstructorsMap.values()) {
String longName = ctMethod.getLongName();
methodDescriptorParser.parse(ctMethod.getSignature());
CtConstructor foundMethod = oldConstructorsMap.get(longName);
if (foundMethod == null) {
JApiConstructor jApiConstructor = new JApiConstructor(jApiClass, ctMethod.getName(), JApiChangeStatus.NEW, Optional.<CtConstructor>absent(), Optional.of(ctMethod), jarArchiveComparator);
addParametersToMethod(methodDescriptorParser, jApiConstructor);
if (includeConstructor(jApiConstructor)) {
constructors.add(jApiConstructor);
}
}
}
}
private boolean includeConstructor(JApiConstructor jApiConstructor) {
return ModifierHelper.matchesModifierLevel(jApiConstructor, options.getAccessModifier());
}
private void addParametersToMethod(MethodDescriptorParser methodDescriptorParser, JApiBehavior jApiMethod) {
for (String param : methodDescriptorParser.getParameters()) {
jApiMethod.addParameter(new JApiParameter(param));
}
}
private Map<String, List<CtMethod>> createMethodMap(Optional<CtClass> ctClassOptional) {
Map<String, List<CtMethod>> methods = new HashMap<>();
if (ctClassOptional.isPresent()) {
CtClass ctClass = ctClassOptional.get();
for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
if (options.getFilters().includeBehavior(ctMethod)) {
String name = ctMethod.getName();
List<CtMethod> ctMethods = methods.get(name);
if (ctMethods == null) {
ctMethods = new ArrayList<>();
methods.put(name, ctMethods);
}
ctMethods.add(ctMethod);
}
}
}
return methods;
}
private Map<String, CtConstructor> createConstructorMap(Optional<CtClass> ctClass) {
Map<String, CtConstructor> methods = new HashMap<>();
if (ctClass.isPresent()) {
for (CtConstructor ctConstructor : ctClass.get().getDeclaredConstructors()) {
if (options.getFilters().includeBehavior(ctConstructor)) {
methods.put(ctConstructor.getLongName(), ctConstructor);
}
}
}
return methods;
}
private JApiChangeStatus evaluateChangeStatus(JApiChangeStatus changeStatus) {
if (changeStatus == JApiChangeStatus.UNCHANGED) {
if (staticModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (finalModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (accessModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (abstractModifier.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
if (this.syntheticAttribute.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
for (JApiImplementedInterface implementedInterface : interfaces) {
if (implementedInterface.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
}
if (superclass.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
}
for (JApiField field : fields) {
if (field.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
changeCausedByClassElement = true;
}
}
for (JApiMethod method : methods) {
if (method.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
changeCausedByClassElement = true;
}
}
for (JApiConstructor constructor : constructors) {
if (constructor.getChangeStatus() != JApiChangeStatus.UNCHANGED) {
changeStatus = JApiChangeStatus.MODIFIED;
changeCausedByClassElement = true;
}
}
}
return changeStatus;
}
private JApiModifier<StaticModifier> extractStaticModifier(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
return ModifierHelper.extractModifierFromClass(oldClassOptional, newClassOptional, new ModifierHelper.ExtractModifierFromClassCallback<StaticModifier>() {
@Override
public StaticModifier getModifierForOld(CtClass oldClass) {
return Modifier.isStatic(oldClass.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
@Override
public StaticModifier getModifierForNew(CtClass newClass) {
return Modifier.isStatic(newClass.getModifiers()) ? StaticModifier.STATIC : StaticModifier.NON_STATIC;
}
});
}
private JApiModifier<FinalModifier> extractFinalModifier(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
return ModifierHelper.extractModifierFromClass(oldClassOptional, newClassOptional, new ModifierHelper.ExtractModifierFromClassCallback<FinalModifier>() {
@Override
public FinalModifier getModifierForOld(CtClass oldClass) {
return Modifier.isFinal(oldClass.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
@Override
public FinalModifier getModifierForNew(CtClass newClass) {
return Modifier.isFinal(newClass.getModifiers()) ? FinalModifier.FINAL : FinalModifier.NON_FINAL;
}
});
}
private JApiModifier<AccessModifier> extractAccessModifier(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
return ModifierHelper.extractModifierFromClass(oldClassOptional, newClassOptional, new ModifierHelper.ExtractModifierFromClassCallback<AccessModifier>() {
@Override
public AccessModifier getModifierForOld(CtClass oldClass) {
return ModifierHelper.translateToModifierLevel(oldClass.getModifiers());
}
@Override
public AccessModifier getModifierForNew(CtClass newClass) {
return ModifierHelper.translateToModifierLevel(newClass.getModifiers());
}
});
}
private JApiModifier<AbstractModifier> extractAbstractModifier(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
return ModifierHelper.extractModifierFromClass(oldClassOptional, newClassOptional, new ModifierHelper.ExtractModifierFromClassCallback<AbstractModifier>() {
@Override
public AbstractModifier getModifierForOld(CtClass oldClass) {
return Modifier.isAbstract(oldClass.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
@Override
public AbstractModifier getModifierForNew(CtClass newClass) {
return Modifier.isAbstract(newClass.getModifiers()) ? AbstractModifier.ABSTRACT : AbstractModifier.NON_ABSTRACT;
}
});
}
private JApiModifier<SyntheticModifier> extractSyntheticModifier(Optional<CtClass> oldClassOptional, Optional<CtClass> newClassOptional) {
return ModifierHelper.extractModifierFromClass(oldClassOptional, newClassOptional, new ModifierHelper.ExtractModifierFromClassCallback<SyntheticModifier>() {
@Override
public SyntheticModifier getModifierForOld(CtClass oldClass) {
return ModifierHelper.isSynthetic(oldClass.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
@Override
public SyntheticModifier getModifierForNew(CtClass newClass) {
return ModifierHelper.isSynthetic(newClass.getModifiers()) ? SyntheticModifier.SYNTHETIC : SyntheticModifier.NON_SYNTHETIC;
}
});
}
@XmlAttribute
@Override
public JApiJavaObjectSerializationChangeStatus getJavaObjectSerializationCompatible() {
return jApiJavaObjectSerializationChangeStatus;
}
@XmlAttribute
public String getJavaObjectSerializationCompatibleAsString() {
return jApiJavaObjectSerializationChangeStatus.getDescription();
}
@XmlElement
@Override
public JApiSerialVersionUid getSerialVersionUid() {
return this.jApiSerialVersionUid;
}
void setJavaObjectSerializationCompatible(JApiJavaObjectSerializationChangeStatus jApiJavaObjectSerializationChangeStatus) {
this.jApiJavaObjectSerializationChangeStatus = jApiJavaObjectSerializationChangeStatus;
}
@XmlAttribute
public JApiChangeStatus getChangeStatus() {
return changeStatus;
}
@XmlAttribute
public String getFullyQualifiedName() {
return fullyQualifiedName;
}
@XmlTransient
public Optional<CtClass> getNewClass() {
return newClass;
}
@XmlTransient
public Optional<CtClass> getOldClass() {
return oldClass;
}
@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.syntheticModifier);
}
@XmlElement(name = "superclass")
public JApiSuperclass getSuperclass() {
return superclass;
}
@XmlElementWrapper(name = "interfaces")
@XmlElement(name = "interface")
public List<JApiImplementedInterface> getInterfaces() {
return interfaces;
}
@XmlElementWrapper(name = "constructors")
@XmlElement(name = "constructor")
public List<JApiConstructor> getConstructors() {
return constructors;
}
@XmlElementWrapper(name = "methods")
@XmlElement(name = "method")
public List<JApiMethod> getMethods() {
return methods;
}
@XmlElementWrapper(name = "fields")
@XmlElement(name = "field")
public List<JApiField> getFields() {
return fields;
}
@XmlElement(name = "classType")
public JApiClassType getClassType() {
return classType;
}
@XmlTransient
public JApiModifier<FinalModifier> getFinalModifier() {
return this.finalModifier;
}
@XmlTransient
public JApiModifier<StaticModifier> getStaticModifier() {
return staticModifier;
}
@XmlTransient
public JApiModifier<AccessModifier> getAccessModifier() {
return this.accessModifier;
}
@XmlTransient
public JApiModifier<AbstractModifier> getAbstractModifier() {
return this.abstractModifier;
}
@XmlTransient
public JApiModifier<SyntheticModifier> getSyntheticModifier() {
return this.syntheticModifier;
}
@XmlTransient
public JApiAttribute<SyntheticAttribute> getSyntheticAttribute() {
return syntheticAttribute;
}
@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;
}
@Override
@XmlAttribute
public boolean isBinaryCompatible() {
boolean binaryCompatible = true;
for (JApiCompatibilityChange compatibilityChange : compatibilityChanges) {
if (!compatibilityChange.isBinaryCompatible()) {
binaryCompatible = false;
break;
}
}
if (binaryCompatible) {
for (JApiField field : fields) {
if (!field.isBinaryCompatible()) {
binaryCompatible = false;
break;
}
}
}
if (binaryCompatible) {
for (JApiMethod method : methods) {
if (!method.isBinaryCompatible()) {
binaryCompatible = false;
break;
}
}
}
if (binaryCompatible) {
for (JApiConstructor constructor : constructors) {
if (!constructor.isBinaryCompatible()) {
binaryCompatible = false;
break;
}
}
}
if (binaryCompatible) {
if (!superclass.isBinaryCompatible()) {
binaryCompatible = false;
}
}
if (binaryCompatible) {
for (JApiImplementedInterface anInterface : interfaces) {
// don't use JApiImplementedInterface.isBinaryCompatible(), since that checks the corresponding source
// without checking if this class still provides the equivalent methods from some other source
for (JApiCompatibilityChange change : anInterface.getCompatibilityChanges()) {
if (!change.isBinaryCompatible()) {
binaryCompatible = false;
break;
}
}
if (!binaryCompatible) {
break;
}
}
}
return binaryCompatible;
}
@Override
@XmlAttribute
public boolean isSourceCompatible() {
boolean sourceCompatible = true;
for (JApiCompatibilityChange compatibilityChange : compatibilityChanges) {
if (!compatibilityChange.isSourceCompatible()) {
sourceCompatible = false;
break;
}
}
if (sourceCompatible) {
for (JApiField field : fields) {
if (!field.isSourceCompatible()) {
sourceCompatible = false;
break;
}
}
}
if (sourceCompatible) {
for (JApiMethod method : methods) {
if (!method.isSourceCompatible()) {
sourceCompatible = false;
break;
}
}
}
if (sourceCompatible) {
for (JApiConstructor constructor : constructors) {
if (!constructor.isSourceCompatible()) {
sourceCompatible = false;
break;
}
}
}
if (sourceCompatible) {
if (!superclass.isSourceCompatible()) {
sourceCompatible = false;
}
}
if (sourceCompatible) {
for (JApiImplementedInterface anInterface : interfaces) {
// don't use JApiImplementedInterface.isSourceCompatible(), since that checks the corresponding source
// without checking if this class still provides the equivalent methods from some other source
for (JApiCompatibilityChange change : anInterface.getCompatibilityChanges()) {
if (!change.isSourceCompatible()) {
sourceCompatible = false;
break;
}
}
if (!sourceCompatible) {
break;
}
}
}
return sourceCompatible;
}
@XmlElementWrapper(name = "annotations")
@XmlElement(name = "annotation")
public List<JApiAnnotation> getAnnotations() {
return annotations;
}
@XmlTransient
public boolean isChangeCausedByClassElement() {
return changeCausedByClassElement;
}
@XmlElementWrapper(name = "compatibilityChanges")
@XmlElement(name = "compatibilityChange")
public List<JApiCompatibilityChange> getCompatibilityChanges() {
return this.compatibilityChanges;
}
}