package japicmp.output; import com.google.common.collect.ImmutableList; import japicmp.config.Options; import japicmp.model.*; import japicmp.util.ModifierHelper; import java.util.*; public class OutputFilter extends Filter { private final Options options; public OutputFilter(Options options) { this.options = options; } public void filter(List<JApiClass> jApiClasses) { filter(jApiClasses, new FilterVisitor() { @Override public void visit(Iterator<JApiField> iterator, JApiField element) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (element.getChangeStatus() == JApiChangeStatus.UNCHANGED) { if (hasOnlyUnchangedAnnotations(element) && element.isSourceCompatible()) { remove = true; } } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (element.isBinaryCompatible()) { remove = true; } } if (!ModifierHelper.matchesModifierLevel(element, OutputFilter.this.options.getAccessModifier())) { remove = true; } if (!ModifierHelper.includeSynthetic(element, options)) { remove = true; } if (remove) { iterator.remove(); } } private boolean hasOnlyUnchangedAnnotations(JApiHasAnnotations jApiHasAnnotations) { boolean hasOnlyUnchangedAnnotations = true; for (JApiAnnotation jApiAnnotation : jApiHasAnnotations.getAnnotations()) { if (jApiAnnotation.getChangeStatus() != JApiChangeStatus.UNCHANGED) { hasOnlyUnchangedAnnotations = false; break; } } return hasOnlyUnchangedAnnotations; } @Override public void visit(Iterator<JApiAnnotation> iterator, JApiAnnotation element) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (element.getChangeStatus() == JApiChangeStatus.UNCHANGED) { remove = true; } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (element.isBinaryCompatible()) { remove = true; } } if (remove) { iterator.remove(); } } @Override public void visit(JApiSuperclass jApiSuperclass) { // cannot remove superclass } @Override public void visit(Iterator<JApiImplementedInterface> iterator, JApiImplementedInterface element) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (element.getChangeStatus() == JApiChangeStatus.UNCHANGED && element.isSourceCompatible()) { remove = true; } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (element.isBinaryCompatible()) { remove = true; } } if (remove) { iterator.remove(); } } @Override public void visit(Iterator<JApiConstructor> iterator, JApiConstructor element) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (element.getChangeStatus() == JApiChangeStatus.UNCHANGED && element.isSourceCompatible()) { if (hasOnlyUnchangedAnnotations(element)) { remove = true; } } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (element.isBinaryCompatible()) { remove = true; } } if (!ModifierHelper.matchesModifierLevel(element, OutputFilter.this.options.getAccessModifier())) { remove = true; } if (!ModifierHelper.includeSynthetic(element, options)) { remove = true; } if (remove) { iterator.remove(); } } @Override public void visit(Iterator<JApiMethod> iterator, JApiMethod element) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (element.getChangeStatus() == JApiChangeStatus.UNCHANGED && element.isSourceCompatible()) { if (hasOnlyUnchangedAnnotations(element)) { remove = true; } } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (element.isBinaryCompatible()) { remove = true; } } if (!ModifierHelper.matchesModifierLevel(element, OutputFilter.this.options.getAccessModifier())) { remove = true; } if (!ModifierHelper.includeSynthetic(element, options)) { remove = true; } if (remove) { iterator.remove(); } } @Override public void visit(Iterator<JApiClass> iterator, JApiClass jApiClass) { boolean remove = false; if (options.isOutputOnlyModifications()) { if (jApiClass.getChangeStatus() == JApiChangeStatus.UNCHANGED && jApiClass.isSourceCompatible()) { ImmutableList<Boolean> list = findOneChangedElement(jApiClass); if (list.isEmpty()) { //filter out this class if it does not have any changed element (e.g. annotations) remove = true; } } } if (options.isOutputOnlyBinaryIncompatibleModifications()) { if (jApiClass.isBinaryCompatible()) { remove = true; } } if (!ModifierHelper.matchesModifierLevel(jApiClass, OutputFilter.this.options.getAccessModifier())) { remove = true; } if (jApiClass.getChangeStatus() == JApiChangeStatus.MODIFIED) { if (options.getAccessModifier().getLevel() > AccessModifier.PRIVATE.getLevel() && options.isOutputOnlyModifications()) { ImmutableList<Boolean> list = findOneChangedElement(jApiClass); if (list.isEmpty()) { //filter out this class if it does not have any changed element at this filter level remove = true; } } } if (jApiClass.getJavaObjectSerializationCompatible().isIncompatible()) { remove = false; } if (!ModifierHelper.includeSynthetic(jApiClass, options)) { remove = true; } if (remove) { iterator.remove(); } } private ImmutableList<Boolean> findOneChangedElement(JApiClass jApiClass) { final ImmutableList.Builder<Boolean> builder = ImmutableList.builder(); Filter.filter(Arrays.asList(jApiClass), new FilterVisitor() { @Override public void visit(Iterator<JApiClass> iterator, JApiClass jApiClass) { evaluateAnnotations(jApiClass); } @Override public void visit(Iterator<JApiMethod> iterator, JApiMethod jApiMethod) { if (jApiMethod.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } else { evaluateAnnotations(jApiMethod); } } @Override public void visit(Iterator<JApiConstructor> iterator, JApiConstructor jApiConstructor) { if (jApiConstructor.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } else { evaluateAnnotations(jApiConstructor); } } @Override public void visit(Iterator<JApiImplementedInterface> iterator, JApiImplementedInterface jApiImplementedInterface) { if (jApiImplementedInterface.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } } @Override public void visit(Iterator<JApiField> iterator, JApiField jApiField) { if (jApiField.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } else { evaluateAnnotations(jApiField); } } private void evaluateAnnotations(JApiHasAnnotations jApiHasAnnotations) { for (JApiAnnotation jApiAnnotation : jApiHasAnnotations.getAnnotations()) { if (jApiAnnotation.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } } } @Override public void visit(Iterator<JApiAnnotation> iterator, JApiAnnotation jApiAnnotation) { if (jApiAnnotation.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } } @Override public void visit(JApiSuperclass jApiSuperclass) { if (jApiSuperclass.getChangeStatus() != JApiChangeStatus.UNCHANGED) { builder.add(Boolean.TRUE); } } }); return builder.build(); } }); } public static void sortClassesAndMethods(List<JApiClass> jApiClasses) { Collections.sort(jApiClasses, new Comparator<JApiClass>() { public int compare(JApiClass o1, JApiClass o2) { return o1.getFullyQualifiedName().compareToIgnoreCase(o2.getFullyQualifiedName()); } }); for (JApiClass jApiClass : jApiClasses) { Collections.sort(jApiClass.getMethods(), new Comparator<JApiMethod>() { public int compare(JApiMethod o1, JApiMethod o2) { return o1.getName().compareToIgnoreCase(o2.getName()); } }); } } }