Package org.springsource.loaded

Source Code of org.springsource.loaded.TypeDiffComputer

/*
* Copyright 2010-2012 VMware and contributors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springsource.loaded;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;

import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AbstractInsnNode;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldInsnNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.IincInsnNode;
import org.objectweb.asm.tree.InsnList;
import org.objectweb.asm.tree.InsnNode;
import org.objectweb.asm.tree.IntInsnNode;
import org.objectweb.asm.tree.JumpInsnNode;
import org.objectweb.asm.tree.LabelNode;
import org.objectweb.asm.tree.LdcInsnNode;
import org.objectweb.asm.tree.LineNumberNode;
import org.objectweb.asm.tree.LookupSwitchInsnNode;
import org.objectweb.asm.tree.MethodInsnNode;
import org.objectweb.asm.tree.MethodNode;
import org.objectweb.asm.tree.MultiANewArrayInsnNode;
import org.objectweb.asm.tree.TableSwitchInsnNode;
import org.objectweb.asm.tree.TypeInsnNode;
import org.objectweb.asm.tree.VarInsnNode;

/**
* Compute the differences between two versions of a type as a series of deltas. Entry point is the computeDifferences method.
*
* @author Andy Clement
* @since 0.5.0
*/
public class TypeDiffComputer implements Opcodes {

  public static TypeDelta computeDifferences(byte[] oldbytes, byte[] newbytes) {
    ClassNode oldClassNode = new ClassNode();
    new ClassReader(oldbytes).accept(oldClassNode, 0);
    ClassNode newClassNode = new ClassNode();
    new ClassReader(newbytes).accept(newClassNode, 0);
    TypeDelta delta = computeDelta(oldClassNode, newClassNode);
    return delta;
  }

  private static TypeDelta computeDelta(ClassNode oldClassNode, ClassNode newClassNode) {
    // The type itself: (int version, int access, String name, String signature, String superName, String[] interfaces) {
    TypeDelta td = new TypeDelta();
    computeTypeDelta(oldClassNode, newClassNode, td);
    computeFieldDelta(oldClassNode, newClassNode, td);
    computeMethodDelta(oldClassNode, newClassNode, td);
    // TODO delta: implement the rest of computeDelta.  These methods from ClassVisitor should help in knowing what is left to do:
    //    public void visitSource(String source, String debug) {
    //    public void visitOuterClass(String owner, String name, String desc) {
    //    public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
    //    public void visitAttribute(Attribute attr) {
    //    public void visitInnerClass(String name, String outerName, String innerName, int access) {
    //    public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
    //    public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
    //    public void visitEnd() {
    return td;
  }

  @SuppressWarnings("unchecked")
  private static void computeMethodDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
    List<MethodNode> nMethods = newClassNode.methods;
    List<MethodNode> oMethods = new ArrayList<MethodNode>(oldClassNode.methods);

    // Going through the new methods and comparing them to the old
    if (nMethods != null) {
      for (MethodNode nMethod : nMethods) {
        MethodNode found = null;
        for (MethodNode oMethod : oMethods) {
          if (oMethod.name.equals(nMethod.name) && oMethod.desc.equals(nMethod.desc)) { // TODO modifiers compared?
            found = oMethod;
            computeAnyMethodDifferences(oMethod, nMethod, td);
          }
        }
        if (found == null) {
          td.addNewMethod(nMethod);
        } else {
          oMethods.remove(found);
        }
      }
    }
    for (MethodNode lostMethod : oMethods) {
      td.addLostMethod(lostMethod);
    }
  }

  @SuppressWarnings("unchecked")
  private static void computeFieldDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
    //    int oSize = oldClassNode.fields.size();
    int nSize = newClassNode.fields.size();

    // Take a copy as we are going to delete entries in the next loop
    List<FieldNode> oFields = new ArrayList<FieldNode>(oldClassNode.fields);

    // Going through the new fields comparing them to the old
    for (int n = 0; n < nSize; n++) {
      FieldNode nField = (FieldNode) newClassNode.fields.get(n);
      FieldNode found = null;
      for (FieldNode oField : oFields) {
        if (oField.name.equals(nField.name)) {
          // found it!
          found = oField;
          // is it exactly the same?
          computeAnyFieldDifferences(oField, nField, td);
        }
      }
      if (found == null) {
        // this is a new field
        td.addNewField(nField);
      } else {
        oFields.remove(found);
      }
    }

    // Those left in oFields were not in nFields so have been removed!
    for (FieldNode lostField : oFields) {
      td.addLostField(lostField);
    }
  }

  /**
   * Check the properties of the field - if they have changed at all then record what kind of change for the field. Thinking the
   * type delta should have a map from names to a delta describing (capturing) the change.
   */
  @SuppressWarnings("unchecked")
  private static void computeAnyFieldDifferences(FieldNode oField, FieldNode nField, TypeDelta td) {
    // Want to record things that are different between these two fields...
    FieldDelta fd = new FieldDelta(oField.name);
    if (oField.access != nField.access) {
      // access changed
      fd.setAccessChanged(oField.access, nField.access);
    }
    if (!oField.desc.equals(nField.desc)) {
      // type changed
      fd.setTypeChanged(oField.desc, nField.desc);
    }
    String annotationChange = compareAnnotations(oField.invisibleAnnotations, nField.invisibleAnnotations);
    annotationChange = annotationChange + compareAnnotations(oField.visibleAnnotations, nField.visibleAnnotations);
    if (annotationChange.length() != 0) {
      fd.setAnnotationsChanged(annotationChange);
    }
    if (fd.hasAnyChanges()) {
      // it needs recording
      td.addChangedField(fd);
    }
  }

  /**
   * Determine if there any differences between the methods supplied. A MethodDelta object is built to record any differences and
   * stored against the type delta.
   *
   * @param oMethod 'old' method
   * @param nMethod 'new' method
   * @param td the type delta where changes are currently being accumulated
   */
  private static void computeAnyMethodDifferences(MethodNode oMethod, MethodNode nMethod, TypeDelta td) {
    MethodDelta md = new MethodDelta(oMethod.name, oMethod.desc);
    if (oMethod.access != nMethod.access) {
      md.setAccessChanged(oMethod.access, nMethod.access);
    }
    // TODO annotations
    InsnList oInstructions = oMethod.instructions;
    InsnList nInstructions = nMethod.instructions;
    if (oInstructions.size() != nInstructions.size()) {
      md.setInstructionsChanged(oInstructions.toArray(), nInstructions.toArray());
    } else {
      // TODO Just interested in constructors right now - should add others
      if (oMethod.name.charAt(0) == '<') {
        String oInvokeSpecialDescriptor = null;
        String nInvokeSpecialDescriptor = null;
        int oUninitCount = 0;
        int nUninitCount = 0;
        boolean codeChange = false;
        for (int i = 0, max = oInstructions.size(); i < max; i++) {
          AbstractInsnNode oInstruction = oInstructions.get(i);
          AbstractInsnNode nInstruction = nInstructions.get(i);
          if (!codeChange) {
            if (!sameInstruction(oInstruction, nInstruction)) {
              codeChange = true;
            }

          }
          if (oInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
            if (oInstruction.getOpcode() == Opcodes.NEW) {
              oUninitCount++;
            }
          }
          if (nInstruction.getType() == AbstractInsnNode.TYPE_INSN) {
            if (nInstruction.getOpcode() == Opcodes.NEW) {
              nUninitCount++;
            }
          }
          if (oInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
            MethodInsnNode mi = (MethodInsnNode) oInstruction;
            if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
              if (oUninitCount == 0) {
                // this is the one!
                oInvokeSpecialDescriptor = mi.desc;
              } else {
                oUninitCount--;
              }
            }
          }
          if (nInstruction.getType() == AbstractInsnNode.METHOD_INSN) {
            MethodInsnNode mi = (MethodInsnNode) nInstruction;
            if (mi.getOpcode() == INVOKESPECIAL && mi.name.equals("<init>")) {
              if (nUninitCount == 0) {
                // this is the one!
                nInvokeSpecialDescriptor = mi.desc;
              } else {
                nUninitCount--;
              }
            }
          }
        }
        // Has the invokespecial changed?
        if (oInvokeSpecialDescriptor == null) {
          if (nInvokeSpecialDescriptor != null) {
            md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
          }
        } else {
          if (!oInvokeSpecialDescriptor.equals(nInvokeSpecialDescriptor)) {
            md.setInvokespecialChanged(oInvokeSpecialDescriptor, nInvokeSpecialDescriptor);
          }
        }
        if (codeChange) {
          md.setCodeChanged(oInstructions.toArray(), nInstructions.toArray());
        }
      }
    }
    if (md.hasAnyChanges()) {
      // it needs recording
      td.addChangedMethod(md);
    }

  }

  private static boolean sameInstruction(AbstractInsnNode o, AbstractInsnNode n) {
    if (o.getType() != o.getType() || o.getOpcode() != n.getOpcode()) {
      return false;
    }
    switch (o.getType()) {
    case (AbstractInsnNode.INSN): // 0
      if (!sameInsnNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.INT_INSN): // 1
      if (!sameIntInsnNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.VAR_INSN): // 2
      if (!sameVarInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.TYPE_INSN):// 3
      if (!sameTypeInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.FIELD_INSN): // 4
      if (!sameFieldInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.METHOD_INSN): // 5
      if (!sameMethodInsnNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.JUMP_INSN): // 6
      if (!sameJumpInsnNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.LABEL): // 7
      if (!sameLabelNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.LDC_INSN): // 8
      if (!sameLdcInsnNode(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.IINC_INSN): // 9
      if (!sameIincInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.TABLESWITCH_INSN): // 10
      if (!sameTableSwitchInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.LOOKUPSWITCH_INSN): // 11
      if (!sameLookupSwitchInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.MULTIANEWARRAY_INSN): // 12
      if (!sameMultiANewArrayInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.FRAME): // 13
      if (!sameFrameInsn(o, n)) {
        return false;
      }
      break;
    case (AbstractInsnNode.LINE): // 14
      if (!sameLineNumberNode(o, n)) {
        return false;
      }
      break;
    default:
      throw new IllegalStateException("nyi " + o.getType());
    }
    return true;
  }

  private static boolean sameFrameInsn(AbstractInsnNode o, AbstractInsnNode n) {
    // given that these nodes are computed based on everything else.  if everything else is the same then these
    // must be the same.  A full comparison could be a little ugly as different frames can be equivalent (maybe
    // the compiler produces an incremental frame on one run then a full frame on the next).
    return true;
  }

  private static boolean sameMultiANewArrayInsn(AbstractInsnNode o, AbstractInsnNode n) {
    if (!(n instanceof MultiANewArrayInsnNode)) {
      return false;
    }
    MultiANewArrayInsnNode mnao = (MultiANewArrayInsnNode) o;
    MultiANewArrayInsnNode mnan = (MultiANewArrayInsnNode) n;
    if (!mnao.desc.equals(mnan.desc)) {
      return false;
    }
    if (mnao.dims != mnan.dims) {
      return false;
    }
    return true;
  }

  @SuppressWarnings("unchecked")
  private static boolean sameLookupSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
    if (!(n instanceof LookupSwitchInsnNode)) {
      return false;
    }
    LookupSwitchInsnNode lsio = (LookupSwitchInsnNode) o;
    LookupSwitchInsnNode lsin = (LookupSwitchInsnNode) n;
    if (sameLabels(lsio.dflt, lsin.dflt)) {
      return false;
    }
    List<Integer> keyso = lsio.keys;
    List<Integer> keysn = lsin.keys;
    if (keyso.size() != keysn.size()) {
      return false;
    }
    for (int i = 0, max = keyso.size(); i < max; i++) {
      if (keyso.get(i) != keysn.get(i)) {
        return false;
      }
    }
    List<LabelNode> labelso = lsio.labels;
    List<LabelNode> labelsn = lsin.labels;
    if (labelso.size() != labelsn.size()) {
      return false;
    }
    for (int i = 0, max = labelso.size(); i < max; i++) {
      if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
        return false;
      }
    }
    return true;
  }

  @SuppressWarnings("unchecked")
  private static boolean sameTableSwitchInsn(AbstractInsnNode o, AbstractInsnNode n) {
    if (!(n instanceof TableSwitchInsnNode)) {
      return false;
    }
    TableSwitchInsnNode tsio = (TableSwitchInsnNode) o;
    TableSwitchInsnNode tsin = (TableSwitchInsnNode) n;
    if (sameLabels(tsio.dflt, tsin.dflt)) {
      return false;
    }
    if (tsio.min != tsin.min) {
      return false;
    }
    if (tsio.max != tsin.max) {
      return false;
    }
    List<LabelNode> labelso = tsio.labels;
    List<LabelNode> labelsn = tsin.labels;
    if (labelso.size() != labelsn.size()) {
      return false;
    }
    for (int i = 0, max = labelso.size(); i < max; i++) {
      if (!sameLabelNode(labelso.get(i), labelsn.get(i))) {
        return false;
      }
    }
    return true;
  }

  private static boolean sameLabels(LabelNode lno, LabelNode lnn) {
    // TODO implement?
    return false;
  }

  private static boolean sameFieldInsn(AbstractInsnNode o, AbstractInsnNode n) {
    FieldInsnNode oi = (FieldInsnNode) o;
    if (!(n instanceof FieldInsnNode)) {
      return false;
    }
    FieldInsnNode ni = (FieldInsnNode) n;
    return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
  }

  private static boolean sameMethodInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
    MethodInsnNode oi = (MethodInsnNode) o;
    if (!(n instanceof MethodInsnNode)) {
      return false;
    }
    MethodInsnNode ni = (MethodInsnNode) n;
    return oi.name.equals(ni.name) && oi.desc.equals(ni.desc) && oi.owner.equals(ni.owner);
  }

  private static boolean sameVarInsn(AbstractInsnNode o, AbstractInsnNode n) {
    VarInsnNode oi = (VarInsnNode) o;
    if (!(n instanceof VarInsnNode)) {
      return false;
    }
    VarInsnNode ni = (VarInsnNode) n;
    return oi.var == ni.var;
  }

  private static boolean sameInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
    InsnNode oi = (InsnNode) o;
    if (!(n instanceof InsnNode)) {
      return false;
    }
    InsnNode ni = (InsnNode) n;
    return oi.getOpcode() == ni.getOpcode();
  }

  private static boolean sameJumpInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
    //    JumpInsnNode oJumpInsnNode = (JumpInsnNode) o;
    if (!(n instanceof JumpInsnNode)) {
      return false;
    }
    //    JumpInsnNode nJumpInsnNode = (JumpInsnNode) n;
    // TODO tricky to compare destinations when captured as labels with no exposed identifier/position
    return true;
  }

  private static boolean sameLdcInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
    LdcInsnNode oi = (LdcInsnNode) o;
    if (!(n instanceof LdcInsnNode)) {
      return false;
    }
    LdcInsnNode ni = (LdcInsnNode) n;
    Object ocst = oi.cst;
    if (ocst instanceof Integer) {
      if (!(ni.cst instanceof Integer)) {
        return false;
      }
      return ((Integer) ocst).equals(ni.cst);
    }
    if (ocst instanceof Float) {
      if (!(ni.cst instanceof Float)) {
        return false;
      }
      return ((Float) ocst).equals(ni.cst);
    }
    if (ocst instanceof Long) {
      if (!(ni.cst instanceof Long)) {
        return false;
      }
      return ((Long) ocst).equals(ni.cst);
    }
    if (ocst instanceof Double) {
      if (!(ni.cst instanceof Double)) {
        return false;
      }
      return ((Double) ocst).equals(ni.cst);
    }
    if (ocst instanceof String) {
      if (!(ni.cst instanceof String)) {
        return false;
      }
      return ((String) ocst).equals(ni.cst);
    }
    // must be Type
    return ((Type) ocst).equals(ni.cst);
  }

  private static boolean sameIntInsnNode(AbstractInsnNode o, AbstractInsnNode n) {
    IntInsnNode oi = (IntInsnNode) o;
    if (!(n instanceof IntInsnNode)) {
      return false;
    }
    IntInsnNode ni = (IntInsnNode) n;
    return oi.operand == ni.operand;
  }

  private static boolean sameLineNumberNode(AbstractInsnNode o, AbstractInsnNode n) {
    LineNumberNode oi = (LineNumberNode) o;
    if (!(n instanceof LineNumberNode)) {
      return false;
    }
    LineNumberNode ni = (LineNumberNode) n;
    return oi.line == ni.line;
    // TODO check oi.start?
  }

  private static boolean sameIincInsn(AbstractInsnNode o, AbstractInsnNode n) {
    IincInsnNode oi = (IincInsnNode) o;
    if (!(n instanceof IincInsnNode)) {
      return false;
    }
    IincInsnNode ni = (IincInsnNode) n;
    return oi.var == ni.var && oi.incr == ni.incr;
  }

  private static boolean sameTypeInsn(AbstractInsnNode o, AbstractInsnNode n) {
    TypeInsnNode oi = (TypeInsnNode) o;
    if (!(n instanceof TypeInsnNode)) {
      return false;
    }
    TypeInsnNode ni = (TypeInsnNode) n;
    return oi.desc.equals(ni.desc);
  }

  /**
   * Compare two labels to check they are the same.
   *
   * @param o 'old' label
   * @param n 'new' label
   * @return true if they are different
   */
  private static boolean sameLabelNode(AbstractInsnNode o, AbstractInsnNode n) {
    //    LabelNode oi = (LabelNode) o;
    if (!(n instanceof LabelNode)) {
      return false;
    }
    //    LabelNode ni = (LabelNode) n;

    // TODO tricky to get right.  Unfortunately the positions aren't always available - and we can't check if they are, we have to call the
    // getOffset() method on label and catch an exception if they aren't
    return true;
  }

  private static String compareAnnotations(List<AnnotationNode> oldAnnos, List<AnnotationNode> newAnnos) {
    if (oldAnnos == null) {
      if (newAnnos == null) {
        return "";
      }
      oldAnnos = Collections.emptyList();
    }
    if (newAnnos == null) {
      newAnnos = Collections.emptyList();
    }
    StringBuilder diff = new StringBuilder();
    // Which have been removed
    for (AnnotationNode o : oldAnnos) {
      boolean found = false;
      String oFormatted = Utils.annotationNodeFormat(o);
      for (AnnotationNode n : newAnnos) {
        String nFormatted = Utils.annotationNodeFormat(n);
        if (oFormatted.equals(nFormatted)) {
          found = true;
          break;
        }
      }
      if (!found) {
        diff.append("-").append(oFormatted);
      }
    }
    // Which have been added
    for (AnnotationNode n : newAnnos) {
      boolean found = false;
      String nFormatted = Utils.annotationNodeFormat(n);
      for (AnnotationNode o : oldAnnos) {
        String oFormatted = Utils.annotationNodeFormat(o);
        if (oFormatted.equals(nFormatted)) {
          found = true;
          break;
        }
      }
      if (!found) {
        diff.append("+").append(nFormatted);
      }
    }
    return diff.toString();
  }

  @SuppressWarnings("unchecked")
  private static void computeTypeDelta(ClassNode oldClassNode, ClassNode newClassNode, TypeDelta td) {
    //    if (oldClassNode.version != newClassNode.version) {
    //      td.setTypeVersionChange(oldClassNode.version, newClassNode.version);
    //    }
    if (oldClassNode.access != newClassNode.access) {
      // Is it only because of 0x20000 - that appears to represent Deprecated!
      if ((oldClassNode.access & 0xffff) != (newClassNode.access&0xffff)) {
        td.setTypeAccessChange(oldClassNode.access, newClassNode.access);
      }
    }
    if (!oldClassNode.name.equals(newClassNode.name)) {
      td.setTypeNameChange(oldClassNode.name, newClassNode.name);
    }
    //    if (oldClassNode.signature == null) {
    //      if (newClassNode.signature != null) {
    //        td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
    //      }
    //    } else if (newClassNode.signature == null) {
    //      if (oldClassNode.signature != null) {
    //        td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
    //      }
    //    } else if (!oldClassNode.signature.equals(newClassNode.signature)) {
    //      td.setTypeSignatureChange(oldClassNode.signature, newClassNode.signature);
    //    }
    if (oldClassNode.superName == null) {
      if (newClassNode.superName != null) {
        td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
      }
    } else if (newClassNode.superName == null) {
      if (oldClassNode.superName != null) {
        td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
      }
    } else if (!oldClassNode.superName.equals(newClassNode.superName)) {
      td.setTypeSuperNameChange(oldClassNode.superName, newClassNode.superName);
    }
    if (oldClassNode.interfaces.size() == 0) {
      if (newClassNode.interfaces.size() != 0) {
        td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
      }
    } else if (newClassNode.interfaces.size() == 0) {
      if (oldClassNode.interfaces.size() != 0) {
        td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
      }
    } else {
      if (oldClassNode.interfaces.size() != newClassNode.interfaces.size()) {
        td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
      }
      HashSet<String> oldInterfaceSet = new HashSet<String>(oldClassNode.interfaces);
      HashSet<String> newInterfaceSet = new HashSet<String>(newClassNode.interfaces);
      if (!oldInterfaceSet.equals(newInterfaceSet)) { // TODO expensive? keep the interfaces list sorted instead?
        td.setTypeInterfacesChange(oldClassNode.interfaces, newClassNode.interfaces);
      }
    }
  }
}
TOP

Related Classes of org.springsource.loaded.TypeDiffComputer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.