void (DifferentiateContext context, JvmClass changedClass, Iterable<JvmMethod> removed, Utils future, Utils present) { debug("Processing removed methods: "); boolean extendsLibraryClass = future.inheritsFromLibraryClass(changedClass); // todo: lazy? for (JvmMethod removedMethod : removed) { debug("Method ", removedMethod.getName()); Iterable<JvmNodeReferenceID> propagated = Iterators.lazy(() -> future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), removedMethod)); if (!removedMethod.isPrivate() && removedMethod.isStatic()) { debug("The method was static --- affecting static method import usages"); affectStaticMemberImportUsages(context, changedClass.getReferenceID(), removedMethod.getName(), propagated); } if (removedMethod.isPackageLocal()) { // Sometimes javac cannot find an overridden package local method in superclasses, when superclasses are defined in different packages. // This results in compilation error when the code is compiled from the very beginning. // So even if we correctly find a corresponding overridden method and the bytecode compatibility remains, // we still need to affect package local method usages to behave similar to javac. debug("Removed method is package-local, affecting method usages"); affectMemberUsages(context, changedClass.getReferenceID(), removedMethod, propagated); } else { Iterable<Pair<JvmClass, JvmMethod>> overridden = Iterators.lazy(() -> removedMethod.isConstructor()? Collections.emptyList() : future.getOverriddenMethods(changedClass, removedMethod::isSameByJavaRules)); boolean isClearlyOverridden = removedMethod.getSignature().isEmpty() && !extendsLibraryClass && !Iterators.isEmpty(overridden) && Iterators.isEmpty( Iterators.filter(overridden, p -> !p.getSecond().getType().equals(removedMethod.getType()) || !p.getSecond().getSignature().isEmpty() || removedMethod.isMoreAccessibleThan(p.getSecond())) ); if (!isClearlyOverridden) { debug("No overridden methods found, affecting method usages"); affectMemberUsages(context, changedClass.getReferenceID(), removedMethod, propagated); } } for (Pair<JvmClass, JvmMethod> overriding : future.getOverridingMethods(changedClass, removedMethod, removedMethod::isSameByJavaRules)) { affectNodeSources(context, overriding.getFirst().getReferenceID(), "Affecting file by overriding: "); } if (!removedMethod.isConstructor() && !removedMethod.isAbstract() && !removedMethod.isStatic()) { for (JvmNodeReferenceID id : propagated) { for (JvmClass subClass : future.getNodes(id, JvmClass.class)) { Iterable<Pair<JvmClass, JvmMethod>> overriddenForSubclass = Iterators.filter(future.getOverriddenMethods(subClass, removedMethod::isSameByJavaRules), p -> p.getSecond().isAbstract() || removedMethod.isSame(p.getSecond())); boolean allOverriddenAbstract = !Iterators.isEmpty(overriddenForSubclass) && Iterators.isEmpty(Iterators.filter(overriddenForSubclass, p -> !p.getSecond().isAbstract())); if (allOverriddenAbstract || future.inheritsFromLibraryClass(subClass)) { debug("Removed method is not abstract & overrides some abstract method which is not then over-overridden in subclass ", subClass.getName()); affectNodeSources(context, subClass.getReferenceID(), "Affecting subclass source file: "); } break; } } } } debug("End of removed methods processing"); }