boolean (DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) { JvmClass changedClass = change.getPast(); JvmClass.Diff classDiff = change.getDiff(); if (classDiff.superClassChanged() || classDiff.signatureChanged() || !classDiff.interfaces().unchanged()) { boolean extendsChanged = classDiff.superClassChanged() && !classDiff.extendsAdded(); boolean affectUsages = classDiff.signatureChanged() || extendsChanged || !Iterators.isEmpty(classDiff.interfaces().removed()); affectSubclasses(context, future, change.getNow().getReferenceID(), affectUsages); if (extendsChanged) { TypeRepr.ClassType exClass = new TypeRepr.ClassType(changedClass.getName()); for (JvmClass depClass : Iterators.flat(Iterators.map(context.getGraph().getDependingNodes(changedClass.getReferenceID()), dep -> present.getNodes(dep, JvmClass.class)))) { for (JvmMethod method : depClass.getMethods()) { if (Iterators.contains(method.getExceptions(), exClass)) { context.affectUsage(method.createUsage(depClass.getReferenceID())); debug("Affecting usages of methods throwing ", exClass.getJvmName(), " exception; class ", depClass.getName()); } } } } if (!changedClass.isAnonymous()) { Set<JvmNodeReferenceID> parents = Iterators.collect(present.allSupertypes(changedClass.getReferenceID()), new HashSet<>()); parents.removeAll(Iterators.collect(future.allSupertypes(changedClass.getReferenceID()), new HashSet<>())); for (JvmNodeReferenceID parent : parents) { debug("Affecting usages in generic type parameter bounds of class: ", parent); context.affectUsage(new ClassAsGenericBoundUsage(parent)); // todo: need support of file-filter usage constraint? } } } JVMFlags addedFlags = classDiff.getAddedFlags(); if (addedFlags.isInterface() || classDiff.getRemovedFlags().isInterface()) { debug("Class-to-interface or interface-to-class conversion detected, added class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); } if (changedClass.isAnnotation() && changedClass.getRetentionPolicy() == RetentionPolicy.SOURCE) { debug("Annotation, retention policy = SOURCE => a switch to non-incremental mode requested"); if (!affectOnNonIncrementalChange(context, changedClass.getReferenceID(), changedClass, present)) { debug("End of Differentiate, returning false"); return false; } } if (addedFlags.isProtected()) { debug("Introduction of 'protected' modifier detected, adding class usage + inheritance constraint to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID()), new InheritanceConstraint(future, changedClass)); } if (!changedClass.getFlags().isPackageLocal() && change.getNow().getFlags().isPackageLocal()) { debug("Introduction of 'package-private' access detected, adding class usage + package constraint to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID()), new PackageConstraint(changedClass.getPackageName())); } if (addedFlags.isFinal() || addedFlags.isPrivate()) { debug("Introduction of 'private' or 'final' modifier(s) detected, adding class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); } if (addedFlags.isAbstract() || addedFlags.isStatic()) { debug("Introduction of 'abstract' or 'static' modifier(s) detected, adding class new usage to affected usages"); context.affectUsage(new ClassNewUsage(changedClass.getReferenceID())); } if (!changedClass.isAnonymous() && !changedClass.isPrivate() && classDiff.flagsChanged() && changedClass.isInnerClass()) { debug("Some modifiers (access flags) were changed for non-private inner class, adding class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); } if (changedClass.isAnnotation()) { debug("Class is annotation, performing annotation-specific analysis"); if (classDiff.retentionPolicyChanged()) { debug("Retention policy change detected, adding class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); } else if (classDiff.targetAttributeCategoryMightChange()) { debug("Annotation's attribute category in bytecode might be affected because of TYPE_USE or RECORD_COMPONENT target, adding class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); } else { Difference.Specifier<ElemType, ?> targetsDiff = classDiff.annotationTargets(); Set<ElemType> removedTargets = Iterators.collect(targetsDiff.removed(), EnumSet.noneOf(ElemType.class)); if (removedTargets.contains(ElemType.LOCAL_VARIABLE)) { debug("Removed target contains LOCAL_VARIABLE => a switch to non-incremental mode requested"); if (!affectOnNonIncrementalChange(context, changedClass.getReferenceID(), changedClass, present)) { debug("End of Differentiate, returning false"); return false; } } if (!removedTargets.isEmpty()) { debug("Removed some annotation targets, adding annotation query"); TypeRepr.ClassType classType = new TypeRepr.ClassType(changedClass.getName()); context.affectUsage((node, usage) -> { if (usage instanceof AnnotationUsage) { AnnotationUsage annotUsage = (AnnotationUsage)usage; if (classType.equals(annotUsage.getClassType())) { for (ElemType target : annotUsage.getTargets()) { if (removedTargets.contains(target)) { return true; } } } } return false; }); } for (JvmMethod m : classDiff.methods().added()) { if (m.getValue() == null) { debug("Added method with no default value: ", m.getName()); debug("Adding class usage to affected usages"); context.affectUsage(new ClassUsage(changedClass.getReferenceID())); break; } } } debug("End of annotation-specific analysis"); } if (!processMethodChanges(context, change, future, present)) { return false; } if (!processFieldChanges(context, change, future, present)) { return false; } return true; }