package japicmp.output.stdout; import com.google.common.base.Optional; import japicmp.cli.JApiCli; import japicmp.config.Options; import japicmp.model.*; import japicmp.model.JApiAnnotationElementValue.Type; import japicmp.output.OutputFilter; import japicmp.output.OutputGenerator; import javassist.bytecode.annotation.MemberValue; import java.util.List; public class StdoutOutputGenerator extends OutputGenerator<String> { static final String NO_CHANGES = "No changes."; static final String WARNING = "WARNING"; public StdoutOutputGenerator(Options options, List<JApiClass> jApiClasses) { super(options, jApiClasses); } @Override public String generate() { OutputFilter outputFilter = new OutputFilter(options); outputFilter.filter(jApiClasses); StringBuilder sb = new StringBuilder(); sb.append(options.getDifferenceDescription()).append('\n'); if (options.getIgnoreMissingClasses().isIgnoreAllMissingClasses()) { sb.append(WARNING + ": You are using the option '" + JApiCli.IGNORE_MISSING_CLASSES + "', i.e. superclasses and interfaces that could not " + "be found on the classpath are ignored. Hence changes caused by these superclasses and interfaces are not reflected in the output.\n"); } else if (options.getIgnoreMissingClasses().getIgnoreMissingClassRegularExpression().size() > 0) { sb.append(WARNING + ": You have ignored certain classes, i.e. superclasses and interfaces that could not " + "be found on the classpath are ignored. Hence changes caused by these superclasses and interfaces are not reflected in the output.\n"); } if (jApiClasses.size() > 0) { for (JApiClass jApiClass : jApiClasses) { processClass(sb, jApiClass); processConstructors(sb, jApiClass); processMethods(sb, jApiClass); processAnnotations(sb, jApiClass, 1); } } else { sb.append(NO_CHANGES); } return sb.toString(); } private void processAnnotations(StringBuilder sb, JApiHasAnnotations jApiClass, int numberofTabs) { List<JApiAnnotation> annotations = jApiClass.getAnnotations(); for (JApiAnnotation jApiAnnotation : annotations) { appendAnnotation(sb, signs(jApiAnnotation), jApiAnnotation, numberofTabs); List<JApiAnnotationElement> elements = jApiAnnotation.getElements(); for (JApiAnnotationElement jApiAnnotationElement : elements) { appendAnnotationElement(sb, signs(jApiAnnotationElement), jApiAnnotationElement, numberofTabs + 1); } } } private void processConstructors(StringBuilder sb, JApiClass jApiClass) { List<JApiConstructor> constructors = jApiClass.getConstructors(); for (JApiConstructor jApiConstructor : constructors) { appendMethod(sb, signs(jApiConstructor), jApiConstructor, "CONSTRUCTOR:"); processAnnotations(sb, jApiConstructor, 2); processExceptions(sb, jApiConstructor, 2); } } private void processMethods(StringBuilder sb, JApiClass jApiClass) { List<JApiMethod> methods = jApiClass.getMethods(); for (JApiMethod jApiMethod : methods) { appendMethod(sb, signs(jApiMethod), jApiMethod, "METHOD:"); processAnnotations(sb, jApiMethod, 2); processExceptions(sb, jApiMethod, 2); } } private void processExceptions(StringBuilder sb, JApiBehavior jApiBehavior, int indent) { for (JApiException exception : jApiBehavior.getExceptions()) { appendException(sb, signs(exception), exception, indent); } } private void appendException(StringBuilder sb, String signs, JApiException jApiException, int indent) { sb.append(tabs(indent)).append(signs).append(" ").append(jApiException.getChangeStatus()).append(" EXCEPTION: ").append(jApiException.getName()).append("\n"); } private void processClass(StringBuilder sb, JApiClass jApiClass) { appendClass(sb, signs(jApiClass), jApiClass); } private String signs(JApiHasChangeStatus hasChangeStatus) { JApiChangeStatus changeStatus = hasChangeStatus.getChangeStatus(); String retVal = "???"; switch (changeStatus) { case UNCHANGED: retVal = "==="; break; case NEW: retVal = "+++"; break; case REMOVED: retVal = "---"; break; case MODIFIED: retVal = "***"; break; } boolean binaryCompatible = true; boolean sourceCompatible = true; if (hasChangeStatus instanceof JApiCompatibility) { JApiCompatibility jApiCompatibility = (JApiCompatibility) hasChangeStatus; binaryCompatible = jApiCompatibility.isBinaryCompatible(); sourceCompatible = jApiCompatibility.isSourceCompatible(); } if (binaryCompatible) { if (sourceCompatible) { retVal += " "; } else { retVal += "*"; } } else { retVal += "!"; } return retVal; } private void appendMethod(StringBuilder sb, String signs, JApiBehavior jApiBehavior, String classMemberType) { sb.append("\t").append(signs).append(" ").append(jApiBehavior.getChangeStatus()).append(" ").append(classMemberType).append(" ") .append(accessModifierAsString(jApiBehavior)).append(abstractModifierAsString(jApiBehavior)).append(staticModifierAsString(jApiBehavior)) .append(finalModifierAsString(jApiBehavior)).append(syntheticModifierAsString(jApiBehavior)).append(bridgeModifierAsString(jApiBehavior)) .append(returnType(jApiBehavior)).append(jApiBehavior.getName()).append("("); int paramCount = 0; for (JApiParameter jApiParameter : jApiBehavior.getParameters()) { if (paramCount > 0) { sb.append(", "); } sb.append(jApiParameter.getType()); paramCount++; } sb.append(")\n"); } private String returnType(JApiBehavior jApiBehavior) { String returnTypeAsString = ""; if (jApiBehavior instanceof JApiMethod) { JApiMethod method = (JApiMethod) jApiBehavior; JApiReturnType jApiReturnType = method.getReturnType(); if (jApiReturnType.getChangeStatus() == JApiChangeStatus.UNCHANGED) { returnTypeAsString = jApiReturnType.getNewReturnType() + " "; } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.MODIFIED) { returnTypeAsString = jApiReturnType.getNewReturnType() + " (<-" + jApiReturnType.getOldReturnType() + ") "; } else if (jApiReturnType.getChangeStatus() == JApiChangeStatus.NEW) { returnTypeAsString = jApiReturnType.getNewReturnType() + " "; } else { returnTypeAsString = jApiReturnType.getOldReturnType() + " "; } } return returnTypeAsString; } private void appendAnnotation(StringBuilder sb, String signs, JApiAnnotation jApiAnnotation, int numberOfTabs) { sb.append(tabs(numberOfTabs) + signs + " " + jApiAnnotation.getChangeStatus() + " ANNOTATION: " + jApiAnnotation.getFullyQualifiedName() + "\n"); } private void appendAnnotationElement(StringBuilder sb, String signs, JApiAnnotationElement jApiAnnotationElement, int numberOfTabs) { sb.append(tabs(numberOfTabs) + signs + " " + jApiAnnotationElement.getChangeStatus() + " ELEMENT: " + jApiAnnotationElement.getName() + "="); Optional<MemberValue> oldValue = jApiAnnotationElement.getOldValue(); Optional<MemberValue> newValue = jApiAnnotationElement.getNewValue(); if (oldValue.isPresent() && newValue.isPresent()) { if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.UNCHANGED) { sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues())); } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.REMOVED) { sb.append(elementValueList2String(jApiAnnotationElement.getOldElementValues()) + " (-)"); } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.NEW) { sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues()) + " (+)"); } else if (jApiAnnotationElement.getChangeStatus() == JApiChangeStatus.MODIFIED) { sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues()) + " (<- " + elementValueList2String(jApiAnnotationElement.getOldElementValues()) + ")"); } } else if (!oldValue.isPresent() && newValue.isPresent()) { sb.append(elementValueList2String(jApiAnnotationElement.getNewElementValues()) + " (+)"); } else if (oldValue.isPresent() && !newValue.isPresent()) { sb.append(elementValueList2String(jApiAnnotationElement.getOldElementValues()) + " (-)"); } else { sb.append(" n.a."); } sb.append("\n"); } private String elementValueList2String(List<JApiAnnotationElementValue> values) { StringBuilder sb = new StringBuilder(); for (JApiAnnotationElementValue value : values) { if (sb.length() > 0) { sb.append(","); } if (value.getName().isPresent()) { sb.append(value.getName().get() + "="); } if (value.getType() != Type.Array && value.getType() != Type.Annotation) { if (value.getType() == Type.Enum) { sb.append(value.getFullyQualifiedName() + "." + value.getValueString()); } else { sb.append(value.getValueString()); } } else { if (value.getType() == Type.Array) { sb.append("{" + elementValueList2String(value.getValues()) + "}"); } else if (value.getType() == Type.Annotation) { sb.append("@" + value.getFullyQualifiedName() + "(" + elementValueList2String(value.getValues()) + ")"); } } } return sb.toString(); } private String tabs(int numberOfTabs) { if (numberOfTabs <= 0) { return ""; } else if (numberOfTabs == 1) { return "\t"; } else if (numberOfTabs == 2) { return "\t\t"; } else { StringBuilder sb = new StringBuilder(); for (int i = 0; i < numberOfTabs; i++) { sb.append("\t"); } return sb.toString(); } } private void appendClass(StringBuilder sb, String signs, JApiClass jApiClass) { sb.append(signs + " " + jApiClass.getChangeStatus() + " " + processClassType(jApiClass) + ": " + accessModifierAsString(jApiClass) + abstractModifierAsString(jApiClass) + staticModifierAsString(jApiClass) + finalModifierAsString(jApiClass) + syntheticModifierAsString(jApiClass) + jApiClass.getFullyQualifiedName() + " " + javaObjectSerializationStatus(jApiClass) + "\n"); processInterfaceChanges(sb, jApiClass); processSuperclassChanges(sb, jApiClass); processFieldChanges(sb, jApiClass); } private String processClassType(JApiClass jApiClass) { JApiClassType classType = jApiClass.getClassType(); switch (classType.getChangeStatus()) { case NEW: return classType.getNewType(); case REMOVED: return classType.getOldType(); case MODIFIED: return classType.getNewType() + " (<- " + classType.getOldType() + ") "; case UNCHANGED: return classType.getOldType(); } return "n.a."; } private String javaObjectSerializationStatus(JApiClass jApiClass) { return " (" + jApiClass.getJavaObjectSerializationCompatible().getDescription() + ")"; } private void processFieldChanges(StringBuilder sb, JApiClass jApiClass) { List<JApiField> jApiFields = jApiClass.getFields(); for (JApiField jApiField : jApiFields) { sb.append(tabs(1)).append(signs(jApiField)).append(" ").append(jApiField.getChangeStatus()).append(" FIELD: ") .append(accessModifierAsString(jApiField)).append(staticModifierAsString(jApiField)) .append(finalModifierAsString(jApiField)).append(syntheticModifierAsString(jApiField)) .append(fieldTypeChangeAsString(jApiField)).append(jApiField.getName()).append("\n"); processAnnotations(sb, jApiField, 2); } } private String abstractModifierAsString(JApiHasAbstractModifier hasAbstractModifier) { JApiModifier<AbstractModifier> modifier = hasAbstractModifier.getAbstractModifier(); return modifierAsString(modifier, AbstractModifier.NON_ABSTRACT); } private String finalModifierAsString(JApiHasFinalModifier hasFinalModifier) { JApiModifier<FinalModifier> modifier = hasFinalModifier.getFinalModifier(); return modifierAsString(modifier, FinalModifier.NON_FINAL); } private String staticModifierAsString(JApiHasStaticModifier hasStaticModifier) { JApiModifier<StaticModifier> modifier = hasStaticModifier.getStaticModifier(); return modifierAsString(modifier, StaticModifier.NON_STATIC); } private String accessModifierAsString(JApiHasAccessModifier modifier) { JApiModifier<AccessModifier> accessModifier = modifier.getAccessModifier(); return modifierAsString(accessModifier, AccessModifier.PACKAGE_PROTECTED); } private String syntheticModifierAsString(JApiHasSyntheticModifier modifier) { JApiModifier<SyntheticModifier> syntheticModifier = modifier.getSyntheticModifier(); return modifierAsString(syntheticModifier, SyntheticModifier.NON_SYNTHETIC); } private String bridgeModifierAsString(JApiHasBridgeModifier modifier) { JApiModifier<BridgeModifier> bridgeModifier = modifier.getBridgeModifier(); return modifierAsString(bridgeModifier, BridgeModifier.NON_BRIDGE); } private <T> String modifierAsString(JApiModifier<T> modifier, T notPrintValue) { if (modifier.getOldModifier().isPresent() && modifier.getNewModifier().isPresent()) { if (modifier.getChangeStatus() == JApiChangeStatus.MODIFIED) { return modifier.getNewModifier().get() + " (<- " + modifier.getOldModifier().get() + ") "; } else if (modifier.getChangeStatus() == JApiChangeStatus.NEW) { if (modifier.getNewModifier().get() != notPrintValue) { return modifier.getNewModifier().get() + "(+) "; } } else if (modifier.getChangeStatus() == JApiChangeStatus.REMOVED) { if (modifier.getOldModifier().get() != notPrintValue) { return modifier.getOldModifier().get() + "(-) "; } } else { if (modifier.getNewModifier().get() != notPrintValue) { return modifier.getNewModifier().get() + " "; } } } else if (modifier.getOldModifier().isPresent()) { if (modifier.getOldModifier().get() != notPrintValue) { return modifier.getOldModifier().get() + "(-) "; } } else if (modifier.getNewModifier().isPresent()) { if (modifier.getNewModifier().get() != notPrintValue) { return modifier.getNewModifier().get() + "(+) "; } } return ""; } private String fieldTypeChangeAsString(JApiField field) { JApiType type = field.getType(); if (type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) { if (type.getChangeStatus() == JApiChangeStatus.MODIFIED) { return type.getNewTypeOptional().get() + " (<- " + type.getOldTypeOptional().get() + ") "; } else if (type.getChangeStatus() == JApiChangeStatus.NEW) { return type.getNewTypeOptional().get() + "(+) "; } else if (type.getChangeStatus() == JApiChangeStatus.REMOVED) { return type.getOldTypeOptional().get() + "(-) "; } else { return type.getNewTypeOptional().get() + " "; } } else if (type.getOldTypeOptional().isPresent() && !type.getNewTypeOptional().isPresent()) { return type.getOldTypeOptional().get() + " "; } else if (!type.getOldTypeOptional().isPresent() && type.getNewTypeOptional().isPresent()) { return type.getNewTypeOptional().get() + " "; } return "n.a."; } private void processSuperclassChanges(StringBuilder sb, JApiClass jApiClass) { JApiSuperclass jApiSuperclass = jApiClass.getSuperclass(); if (options.isOutputOnlyModifications() && jApiSuperclass.getChangeStatus() != JApiChangeStatus.UNCHANGED) { sb.append(tabs(1) + signs(jApiSuperclass) + " " + jApiSuperclass.getChangeStatus() + " SUPERCLASS: " + superclassChangeAsString(jApiSuperclass) + "\n"); } } private String superclassChangeAsString(JApiSuperclass jApiSuperclass) { if (jApiSuperclass.getOldSuperclassName().isPresent() && jApiSuperclass.getNewSuperclassName().isPresent()) { return jApiSuperclass.getNewSuperclassName().get() + " (<- " + jApiSuperclass.getOldSuperclassName().get() + ")"; } else if (jApiSuperclass.getOldSuperclassName().isPresent() && !jApiSuperclass.getNewSuperclassName().isPresent()) { return jApiSuperclass.getOldSuperclassName().get(); } else if (!jApiSuperclass.getOldSuperclassName().isPresent() && jApiSuperclass.getNewSuperclassName().isPresent()) { return jApiSuperclass.getNewSuperclassName().get(); } return "n.a."; } private void processInterfaceChanges(StringBuilder sb, JApiClass jApiClass) { List<JApiImplementedInterface> interfaces = jApiClass.getInterfaces(); for (JApiImplementedInterface implementedInterface : interfaces) { sb.append(tabs(1) + signs(implementedInterface) + " " + implementedInterface.getChangeStatus() + " INTERFACE: " + implementedInterface.getFullyQualifiedName() + "\n"); } } }