@Override
  public boolean processChangedMethods(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Iterable<Difference.Change<JvmMethod, JvmMethod.Diff>> methodChanges, Utils future, Utils present) {
    JvmClass changedClass = clsChange.getPast();
    debug("Processing changed methods: ");

    for (Difference.Change<JvmMethod, JvmMethod.Diff> change : methodChanges) {
      JvmMethod changedMethod = change.getPast();
      JvmMethod.Diff diff = change.getDiff();

      debug("Method: ", changedMethod.getName());

      if (changedClass.isAnnotation()) {
        if (diff.valueRemoved())  {
          debug("Class is annotation, default value is removed => adding annotation query");
          String argName = changedMethod.getName();
          TypeRepr.ClassType annotType = new TypeRepr.ClassType(changedClass.getName());
          context.affectUsage(asIterable(changedClass.getReferenceID()), node -> {
            for (Usage usage : node.getUsages()) {
              if (usage instanceof AnnotationUsage) {
                // need to find annotation usages that do not use arguments this annotation uses
                AnnotationUsage au = (AnnotationUsage)usage;
                return annotType.equals(au.getClassType()) && isEmpty(filter(au.getUsedArgNames(), argName::equals));
              }
            }
            return false;
          });
        }
        continue;
      }

      Iterable<JvmNodeReferenceID> propagated = lazy(() -> {
        return future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), changedMethod);
      });

      if (diff.becamePackageLocal()) {
        debug("Method became package-private, affecting method usages outside the package");
        affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated, new PackageConstraint(changedClass.getPackageName()));
      }

      if (diff.typeChanged() || diff.signatureChanged() || !diff.exceptions().unchanged()) {
        debug("Return type, throws list or signature changed --- affecting method usages");
        affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated);

        if (!changedMethod.isPrivate() && !changedMethod.isConstructor() && !changedMethod.isStatic()) {
          if (!changedMethod.isFinal()) {
            for (JvmNodeReferenceID subClass : unique(map(future.getOverridingMethods(changedClass, changedMethod, changedMethod::isSameByJavaRules), p -> p.getFirst().getReferenceID()))) {
              affectNodeSources(context, subClass, "Affect source file of a class which overrides the changed method: ", future);
            }
          }
          for (JvmNodeReferenceID id : propagated) {
            for (JvmClass subClass : future.getNodes(id, JvmClass.class)) {
              Iterable<Pair<JvmClass, JvmMethod>> overriddenInSubclass = filter(future.getOverriddenMethods(subClass, changedMethod::isSameByJavaRules), p -> !Objects.equals(p.getFirst().getReferenceID(), id));
              if (!isEmpty(overriddenInSubclass)) {
                debug("Changed method is inherited in some subclass & overrides/implements some interface method which this subclass implements. ", subClass.getName());
                affectNodeSources(context, subClass.getReferenceID(), "Affecting subclass source file: ", future);
                break;
              }
            }
          }
        }
      }
      else if (diff.flagsChanged()) {
        JVMFlags addedFlags = diff.getAddedFlags();
        JVMFlags removedFlags = diff.getRemovedFlags();
        if (addedFlags.isStatic() || addedFlags.isPrivate() || addedFlags.isSynthetic() || addedFlags.isBridge() || removedFlags.isStatic()) {

          // When synthetic or bridge flags are added, this effectively means that explicitly written in the code
          // method with the same signature and return type has been removed and a bridge method has been generated instead.
          // In some cases (e.g. using raw types) the presence of such synthetic methods in the bytecode is ignored by the compiler
          // so that the code that called such method via raw type reference might not compile anymore => to be on the safe side
          // we should recompile all places where the method was used

          debug("Added {static | private | synthetic | bridge} specifier or removed static specifier --- affecting method usages");
          affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated);

          if (addedFlags.isStatic()) {
            debug("Added static specifier --- affecting subclasses");
            affectSubclasses(context, future, changedClass.getReferenceID(), false);
            if (!changedMethod.isPrivate()) {
              debug("Added static modifier --- affecting static member on-demand import usages");
              affectStaticMemberOnDemandUsages(context, changedClass.getReferenceID(), propagated);
            }
          }
          else if (removedFlags.isStatic()) {
            if (!changedMethod.isPrivate()) {
              debug("Removed static modifier --- affecting static method import usages");
              affectStaticMemberImportUsages(context, changedClass.getReferenceID(), changedMethod.getName(), propagated);
            }
          }
        }
        else {
          if (addedFlags.isFinal() || addedFlags.isPublic() || addedFlags.isAbstract()) {
            debug("Added final, public or abstract specifier --- affecting subclasses");
            affectSubclasses(context, future, changedClass.getReferenceID(), false);
          }

          if (addedFlags.isProtected() && !removedFlags.isPrivate()) {
            debug("Added public or package-private method became protected --- affect method usages with protected constraint");
            affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated, new InheritanceConstraint(future, changedClass));
          }
        }
      }

      Difference.Specifier<ElementAnnotation, ElementAnnotation.Diff> annotationsDiff = diff.annotations();
      Difference.Specifier<ParamAnnotation, ParamAnnotation.Diff> paramAnnotationsDiff = diff.paramAnnotations();
      if (!annotationsDiff.unchanged() || !paramAnnotationsDiff.unchanged()) {
        EnumSet<AnnotationChangesTracker.Recompile> toRecompile = EnumSet.noneOf(AnnotationChangesTracker.Recompile.class);
        for (AnnotationChangesTracker tracker : ourAnnotationChangeTrackers) {
          if (toRecompile.containsAll(AnnotationChangesTracker.RECOMPILE_ALL)) {
            break;
          }
          Set<AnnotationChangesTracker.Recompile> result = tracker.methodAnnotationsChanged(changedMethod, annotationsDiff, paramAnnotationsDiff);
          if (result.contains(AnnotationChangesTracker.Recompile.USAGES)) {
            debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in annotations list --- affecting method usages");
          }
          if (result.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
            debug("Extension ", tracker.getClass().getName(), " requested recompilation because of changes in method annotations or method parameter annotations list --- affecting subclasses");
          }
          toRecompile.addAll(result);
        }
        if (toRecompile.contains(AnnotationChangesTracker.Recompile.USAGES)) {
          affectMemberUsages(context, changedClass.getReferenceID(), changedMethod, propagated);
          if (changedMethod.isAbstract() || toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
            for (Pair<JvmClass, JvmMethod> pair : recurse(Pair.create(changedClass, changedMethod), p -> p.second.isOverridable()? future.getOverridingMethods(p.first, p.second, p.second::isSameByJavaRules) : Collections.emptyList(), false)) {
              JvmNodeReferenceID clsId = pair.first.getReferenceID();
              JvmMethod meth = pair.getSecond();
              affectMemberUsages(context, clsId, meth, future.collectSubclassesWithoutMethod(clsId, meth));
            }
          }
        }
        if (toRecompile.contains(AnnotationChangesTracker.Recompile.SUBCLASSES)) {
          affectSubclasses(context, future, changedClass.getReferenceID(), false);
        }
      }
    }

    Iterable<Difference.Change<JvmMethod, JvmMethod.Diff>> moreAccessible = collect(filter(methodChanges, ch -> ch.getDiff().accessExpanded()), new SmartList<>());
    if (!isEmpty(moreAccessible)) {
      Iterable<Utils.OverloadDescriptor> overloaded = future.findAllOverloads(changedClass, method -> {
        JVMFlags mostAccessible = null;
        for (var change : moreAccessible) {
          JvmMethod m = change.getNow();
          if (Objects.equals(m.getName(), method.getName()) && !m.isSame(method)) {
            if (mostAccessible == null || mostAccessible.isWeakerAccess(m.getFlags())) {
              mostAccessible = m.getFlags();
            }
          }
        }
        return mostAccessible;
      });
      for (Utils.OverloadDescriptor descr : overloaded) {
        debug("Method became more accessible --- affect usages of overloading methods: ", descr.overloadMethod.getName());
        Predicate<Node<?, ?>> constr;
        if (descr.accessScope.isPackageLocal()) {
          constr = new PackageConstraint(changedClass.getPackageName()).negate();
        }
        else {
          if (descr.accessScope.isProtected()) {
            constr = new InheritanceConstraint(future, changedClass).negate();
          }
          else {
            constr = null;
          }
        }

        affectMemberUsages(context, descr.owner.getReferenceID(), descr.overloadMethod, future.collectSubclassesWithoutMethod(descr.owner.getReferenceID(), descr.overloadMethod), constr);
      }
    }

    debug("End of changed methods processing");
    return true;
  }
@Override
  public boolean processChangedMethods(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> clsChange, Iterable<Difference.Change<JvmMethod, JvmMethod.Diff>> methodChanges, Utils future, Utils present)