@Override
  public boolean processChangedClass(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present) {
    JvmClass changedClass = change.getPast();
    JvmClass.Diff diff = change.getDiff();
    Iterable<JvmMethod> removedMethods = diff.methods().removed();
    Iterable<JvmField> addedNonPrivateFields = filter(diff.fields().added(), f -> !f.isPrivate());
    Iterable<JvmField> exposedFields = filter(map(diff.fields().changed(), ch -> ch.getDiff().accessExpanded()? ch.getPast() : null), Objects::nonNull);

    boolean hierarchyChanged = diff.superClassChanged() || !diff.interfaces().unchanged();
    if (hierarchyChanged) {
      boolean extendsChanged = diff.superClassChanged() && !diff.extendsAdded();
      if (extendsChanged || !isEmpty(diff.interfaces().removed())) {
        debug("Affecting class lookups due to changes in class hierarchy");
        for (JvmClass sub : flat(map(future.withAllSubclasses(change.getNow().getReferenceID()), id -> future.getNodes(id, JvmClass.class)))) {
          affectClassLookupUsages(context, sub);
        }
      }
    }
    
    if (KJvmUtils.isKotlinNode(changedClass)) {
      if (hierarchyChanged) {
        Difference.Specifier<JvmNodeReferenceID, ?> sealedDiff = Difference.diff(
          map(filter(present.allDirectSupertypes(change.getPast()), KJvmUtils::isSealed), JVMClassNode::getReferenceID),
          map(filter(future.allDirectSupertypes(change.getNow()), KJvmUtils::isSealed), JVMClassNode::getReferenceID)
        );
        for (JvmNodeReferenceID id : sealedDiff.added()) {
          affectSealedClass(context, id, "Subclass of a sealed class was added, affecting ", future, true /*affectUsages*/);
        }
        for (JvmNodeReferenceID id : sealedDiff.removed()) {
          affectSealedClass(context, id, "Subclass of a sealed class was removed, affecting ", future, true /*affectUsages*/);
        }
      }
      if (KJvmUtils.isSealed(change.getNow())) {
        // for sealed classes check if the list of direct subclasses found by the graph is the same as in the class' metadata
        KmClass kmClass = (KmClass)KJvmUtils.getDeclarationContainer(change.getNow());
        assert  kmClass != null;
        Difference.Specifier<String, ?> subclassesDiff = Difference.diff(
          filter(map(future.directSubclasses(changedClass.getReferenceID()), subId -> subId instanceof JvmNodeReferenceID? KJvmUtils.getKotlinName((JvmNodeReferenceID)subId, future) : null), Objects::nonNull),
          map(kmClass.getSealedSubclasses(), name -> name.replace('.', '/'))
        );
        if (!subclassesDiff.unchanged()) {
          affectSealedClass(
            context,
            changedClass.getReferenceID(),
            "Subclasses registered in the metadata of a sealed class differ from the list of subclasses found in the dependency graph. Recompiling the sealed class with its subclasses",
            future, false /*affectUsages*/
          );
        }
      }
      else if (KJvmUtils.isSealed(change.getPast())) { // was sealed, but not anymore
        for (ReferenceID id : present.directSubclasses(changedClass.getReferenceID())) {
          String nodeName = present.getNodeName(id);
          if (nodeName != null) {
            // to track uses in 'when' expressions
            debug("A sealed class is not sealed anymore, affecting its subclass usages ", nodeName);
            context.affectUsage(new ClassUsage(nodeName));
          }
        }
      }
    }

    if (!isEmpty(removedMethods) || !isEmpty(addedNonPrivateFields) || !isEmpty(exposedFields)) {
      for (PropertyDescriptor property : findProperties(changedClass)) {

        // KT-46743 Incremental compilation doesn't process usages of Java property in Kotlin code if getter is removed
        for (JvmMethod removedMethod : removedMethods) {
          if (removedMethod.isSame(property.getter) && property.setter != null) {
            debug("Kotlin interop: a property getter ", removedMethod.getName(), " was removed => affecting usages of corresponding setter ", property.setter.getName());
            affectMemberUsages(context, changedClass.getReferenceID(), property.setter, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), property.setter));
            break;
          }
        }

        // KT-55393 JPS: Java synthetic properties incremental compilation is broken
        for (JvmField field : flat(addedNonPrivateFields, exposedFields)) {
          if (Objects.equals(field.getName(), property.getter.getName()) || property.name.equalsIgnoreCase(field.getName())) {
            debug("Kotlin interop: a non-private field with name ", field.getName(), " was added, or the field became more accessible");
            debug(" => affecting usages of corresponding property getter ", property.getter.getName());
            affectMemberUsages(context, changedClass.getReferenceID(), property.getter, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), property.getter));
            if (property.setter != null) {
              debug(" => affecting usages of corresponding property setter ", property.setter.getName());
              affectMemberUsages(context, changedClass.getReferenceID(), property.setter, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), property.setter));
            }
            break;
          }
        }
      }
    }

    if (!present.isLambdaTarget(change.getPast()) && future.isLambdaTarget(change.getNow())) {
      // should affect lambda instantiations on overloads, because some calls may have become ambiguous
      TypeRepr.ClassType samType = new TypeRepr.ClassType(changedClass.getName());
      for (JvmClass depClass : flat(map(context.getGraph().getDependingNodes(changedClass.getReferenceID()), dep -> present.getNodes(dep, JvmClass.class)))) {
        for (JvmMethod methodWithSAMType : filter(depClass.getMethods(), m -> contains(m.getArgTypes(), samType))) {
          affectConflictingCallExpressions(context, depClass, methodWithSAMType, present, null);
        }
      }
    }

    for (Difference.Change<KotlinMeta, KotlinMeta.Diff> metaChange : diff.metadata(KotlinMeta.class).changed()) {
      KotlinMeta.Diff metaDiff = metaChange.getDiff();

      if (metaDiff.typeParametersVarianceChanged()) {
        debug("Kotlin class' type parameters' variance changed; affecting class usage ", changedClass.getName());
        affectSubclasses(context, future, change.getNow().getReferenceID(), true);
      }

      if (metaDiff.containerAccessRestricted()) {
        debug("Kotlin class' visibility restricted; affecting class lookup usage ", changedClass.getName());
        affectClassLookupUsages(context, changedClass);
      }

      for (KmFunction removedFunction : metaDiff.functions().removed()) {
        if (KJvmUtils.isPrivate(removedFunction)) {
          continue;
        }
        if (Attributes.isInline(removedFunction)) {
          debug("Removed function was inlineable, affecting function usages ", removedFunction.getName());
          affectMemberLookupUsages(context, changedClass, removedFunction.getName(), present);
        }

        JvmMethod method = getJvmMethod(change.getNow(), JvmExtensionsKt.getSignature(removedFunction));
        if (method != null) {
          // a function in kotlin code was replaced with a property, but at the bytecode level corresponding methods are preserved
          for (JvmClass subClass : filter(flat(map(future.allSubclasses(changedClass.getReferenceID()), id -> future.getNodes(id, JvmClass.class))), KJvmUtils::isKotlinNode)) {
            if (find(subClass.getMethods(), m -> !m.isPrivate() && method.isSameByJavaRules(m)) != null) {
              affectNodeSources(context, subClass.getReferenceID(), "Kotlin function " + removedFunction.getName() + " has been removed. Affecting corresponding method in subclasses: ", future);
            }
          }
        }
      }

      for (KmFunction func : flat(metaDiff.functions().removed(), metaDiff.functions().added())) {
        if (KJvmUtils.isPrivate(func) || find(func.getValueParameters(), Attributes::getDeclaresDefaultValue) == null) {
          continue;
        }
        debug("Removed or added function declares default values: ", changedClass.getName());
        affectMemberLookupUsages(context, changedClass, func.getName(), future);
      }

      for (Difference.Change<KmFunction, KotlinMeta.KmFunctionsDiff> funChange : metaDiff.functions().changed()) {
        KmFunction changedKmFunction = funChange.getPast();
        if (KJvmUtils.isPrivate(changedKmFunction)) {
          continue;
        }
        KotlinMeta.KmFunctionsDiff funDiff = funChange.getDiff();
        if (funDiff.accessRestricted() || funDiff.becameNullable() || funDiff.argsBecameNotNull() || funDiff.parameterArgumentsChanged()) {
          debug("One of function's parameters or return value has become non-nullable, or the function has become less accessible or type parameter's arguments changed ", changedKmFunction.getName());
          JvmMethod jvmMethod = getJvmMethod(changedClass, JvmExtensionsKt.getSignature(changedKmFunction));
          if (jvmMethod != null) {
            // this will affect all usages from both java and kotlin code
            for (JvmMethod method : withJvmOverloads(changedClass, jvmMethod)) {
              for (Pair<JvmClass, JvmMethod> pair : future.getOverridingMethods(changedClass, method, method::isSameByJavaRules)) {
                affectNodeSources(context, pair.getFirst().getReferenceID(), "Affect class where the function is overridden: ", future);
              }
              affectMemberUsages(context, changedClass.getReferenceID(), method, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), method));
            }
          }
          if (KJvmUtils.isDeclaresDefaultValue(changedKmFunction)) {
            // additionally: functions with default parameters produce several methods in bytecode, so need to affect by lookup usage
            debug("One of method's parameters or method's return value has become non-nullable; or function has become less accessible: ", changedKmFunction.getName());
            affectMemberLookupUsages(context, changedClass, changedKmFunction.getName(), future);
          }
        }
        if (funDiff.receiverParameterChanged() || funDiff.hasDefaultDeclarationChanges()) {
          debug("Function's receiver parameter changed or function has breaking changes in default value declarations: ", changedKmFunction.getName());
          affectMemberLookupUsages(context, changedClass, changedKmFunction.getName(), future);
        }
      }

      for (KmConstructor con : flat(metaDiff.constructors().removed(), metaDiff.constructors().added())) {
        if (KJvmUtils.isPrivate(Attributes.getVisibility(con)) || find(con.getValueParameters(), Attributes::getDeclaresDefaultValue) == null) {
          continue;
        }
        debug("Removed or added constructor declares default values: ", changedClass.getName());
        affectClassLookupUsages(context, changedClass);
      }
      
      for (Difference.Change<KmConstructor, KotlinMeta.KmConstructorsDiff> conChange : metaDiff.constructors().changed()) {
        KmConstructor changedCons = conChange.getPast();
        if (KJvmUtils.isPrivate(Attributes.getVisibility(changedCons))) {
          continue;
        }
        KotlinMeta.KmConstructorsDiff conDiff = conChange.getDiff();
        if (conDiff.argsBecameNotNull() || conDiff.accessRestricted() || conDiff.hasDefaultDeclarationChanges()) {
          debug("Constructor's args became non-nullable; or the constructor has become less accessible or has breaking changes in default value declarations: ", changedClass.getName());
          affectClassLookupUsages(context, changedClass);
        }
      }

      for (KmProperty removedProp : metaDiff.properties().removed()) {
        if (KJvmUtils.isPrivate(removedProp)) {
          continue;
        }
        if (Attributes.isInline(removedProp.getGetter()) || (removedProp.getSetter() != null && Attributes.isInline(removedProp.getSetter()))) {
          debug("Removed property was inlineable, affecting property usages ", removedProp.getName());
          affectMemberLookupUsages(context, changedClass, removedProp.getName(), present);
        }

        List<JvmMethodSignature> propertyAccessors = Arrays.asList(JvmExtensionsKt.getGetterSignature(removedProp), JvmExtensionsKt.getSetterSignature(removedProp));
        List<JvmMethod> accessorMethods = collect(filter(map(propertyAccessors, acc -> acc != null? getJvmMethod(change.getNow(), acc) : null), m -> m != null && !m.isPrivate()), new SmartList<>());

        if (!accessorMethods.isEmpty()) {
          // property in kotlin code was replaced with a function(s), but at the bytecode level corresponding methods are preserved
          for (JvmClass subClass : filter(flat(map(future.allSubclasses(changedClass.getReferenceID()), id -> future.getNodes(id, JvmClass.class))), KJvmUtils::isKotlinNode)) {
            if (find(subClass.getMethods(), m -> !m.isPrivate() && find(accessorMethods, m::isSameByJavaRules) != null) != null) {
              affectNodeSources(context, subClass.getReferenceID(), "Kotlin property " + removedProp.getName() + " has been removed. Affecting corresponding accessor method(s) in subclasses: ", future);
            }
          }
        }
      }

      for (Difference.Change<KmProperty, KotlinMeta.KmPropertiesDiff> propChange : metaDiff.properties().changed()) {
        KmProperty changedProp = propChange.getPast();
        KotlinMeta.KmPropertiesDiff propDiff = propChange.getDiff();
        if (propDiff.accessRestricted() || propDiff.customAccessorAdded()) {
          debug("A property has become less accessible or got custom accessors; affecting its lookup usages ", changedProp.getName());
          affectMemberLookupUsages(context, changedClass, changedProp.getName(), future);
        }

        if (!KJvmUtils.isPrivate(Attributes.getVisibility(changedProp.getGetter()))) {
          if (propDiff.becameNullable() || propDiff.getterAccessRestricted()) {
            JvmMethod getter = getJvmMethod(changedClass, JvmExtensionsKt.getGetterSignature(changedProp));
            if (getter != null && !getter.getFlags().isPrivate()) {
              debug("A property has become nullable or its getter has become less accessible; affecting getter usages ", getter);
              affectMemberUsages(context, changedClass.getReferenceID(), getter, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), getter));
            }
          }
        }

        KmPropertyAccessorAttributes propSetter = changedProp.getSetter();
        if (propSetter != null && !KJvmUtils.isPrivate(Attributes.getVisibility(propSetter))) {
          if (propDiff.becameNotNull() || propDiff.setterAccessRestricted()) {
            JvmMethod setter = getJvmMethod(changedClass, JvmExtensionsKt.getSetterSignature(changedProp));
            if (setter != null) {
              debug("A property has become not-null or its setter has become less accessible; affecting setter usages ", setter);
              affectMemberUsages(context, changedClass.getReferenceID(), setter, future.collectSubclassesWithoutMethod(changedClass.getReferenceID(), setter));
            }
          }
        }
      }

      for (KmTypeAlias alias : flat(metaDiff.typeAliases().removed(), metaDiff.typeAliases().added())) {
        if (!KJvmUtils.isPrivate(Attributes.getVisibility(alias))) {
          debug("A type alias declaration was added/removed; affecting lookup usages ", alias.getName());
          affectMemberLookupUsages(context, changedClass, alias.getName(), future);
        }
      }
      for (Difference.Change<KmTypeAlias, KotlinMeta.KmTypeAliasDiff> aChange : metaDiff.typeAliases().changed()) {
        KotlinMeta.KmTypeAliasDiff aDiff = aChange.getDiff();
        if (aDiff.accessRestricted() || aDiff.underlyingTypeChanged()) {
          KmTypeAlias changedAlias = aChange.getPast();
          debug("A type alias declaration has access restricted or underlying type has changed; affecting lookup usages ", changedAlias.getName());
          affectMemberLookupUsages(context, changedClass, changedAlias.getName(), future);
        }
      }

    }

    return true;
  }
@Override
  public boolean processChangedClass(DifferentiateContext context, Difference.Change<JvmClass, JvmClass.Diff> change, Utils future, Utils present)