All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.takari.maven.plugins.compile.jdt.ClassfileDigester Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
package io.takari.maven.plugins.compile.jdt;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.eclipse.jdt.internal.compiler.classfmt.ClassFileReader;
import org.eclipse.jdt.internal.compiler.classfmt.FieldInfo;
import org.eclipse.jdt.internal.compiler.classfmt.MethodInfo;
import org.eclipse.jdt.internal.compiler.env.ClassSignature;
import org.eclipse.jdt.internal.compiler.env.EnumConstantSignature;
import org.eclipse.jdt.internal.compiler.env.IBinaryAnnotation;
import org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair;
import org.eclipse.jdt.internal.compiler.env.IBinaryNestedType;
import org.eclipse.jdt.internal.compiler.env.IBinaryType;
import org.eclipse.jdt.internal.compiler.impl.Constant;
import org.eclipse.jdt.internal.compiler.lookup.TagBits;
import org.eclipse.jdt.internal.compiler.lookup.TypeIds;

import com.google.common.base.Charsets;

/**
 * Adopted from {@link ClassFileReader#hasStructuralChanges(byte[], boolean, boolean)}
 */
public class ClassfileDigester {

  // TODO use Guava Hasher
  private final MessageDigest digester;

  public ClassfileDigester() {
    try {
      digester = MessageDigest.getInstance("SHA1");
    } catch (NoSuchAlgorithmException e) {
      throw new IllegalStateException("Unsupported JVM", e);
    }
  }

  public byte[] digest(IBinaryType classFile) {

    // type level comparison
    // modifiers
    updateInt(classFile.getModifiers());

    // only consider a portion of the tagbits which indicate a structural change for dependents
    // e.g. @Override change has no influence outside
    long OnlyStructuralTagBits = TagBits.AnnotationTargetMASK // different @Target status ?
        | TagBits.AnnotationDeprecated // different @Deprecated status ?
        | TagBits.AnnotationRetentionMASK // different @Retention status ?
        | TagBits.HierarchyHasProblems; // different hierarchy status ?

    // meta-annotations
    updateLong(classFile.getTagBits() & OnlyStructuralTagBits);
    // annotations
    updateAnnotations(classFile.getAnnotations());

    // generic signature
    updateChars(classFile.getGenericSignature());
    // superclass
    updateChars(classFile.getSuperclassName());
    // interfaces
    char[][] interfacesNames = classFile.getInterfaceNames();
    if (interfacesNames != null) {
      for (int i = 0; i < interfacesNames.length; i++) {
        updateChars(interfacesNames[i]);
      }
    }

    // member types
    IBinaryNestedType[] memberTypes = classFile.getMemberTypes();
    if (memberTypes != null) {
      for (int i = 0; i < memberTypes.length; i++) {
        updateChars(memberTypes[i].getName());
        updateInt(memberTypes[i].getModifiers());
      }
    }

    // fields
    FieldInfo[] fieldInfos = (FieldInfo[]) classFile.getFields();
    if (fieldInfos != null) {
      for (int i = 0; i < fieldInfos.length; i++) {
        updateField(fieldInfos[i]);
      }
    }

    // methods
    MethodInfo[] methodInfos = (MethodInfo[]) classFile.getMethods();
    if (methodInfos != null) {
      for (int i = 0; i < methodInfos.length; i++) {
        updateMethod(methodInfos[i]);
      }
    }

    // missing types
    char[][][] missingTypes = classFile.getMissingTypeNames();
    if (missingTypes != null) {
      for (int i = 0; i < missingTypes.length; i++) {
        for (int j = 0; j < missingTypes[i].length; j++) {
          if (j > 0) {
            updateChar('.'); // don't ask why
          }
          updateChars(missingTypes[i][j]);
        }
      }
    }

    return digester.digest();
  }

  private void updateMethod(MethodInfo methodInfo) {
    // generic signature
    updateChars(methodInfo.getGenericSignature());
    updateInt(methodInfo.getModifiers());
    updateLong(methodInfo.getTagBits() & TagBits.AnnotationDeprecated);
    updateAnnotations(methodInfo.getAnnotations());
    // parameter annotations:
    for (int i = 0; i < methodInfo.getAnnotatedParametersCount(); i++) {
      updateAnnotations(methodInfo.getParameterAnnotations(i));
    }

    updateChars(methodInfo.getSelector());
    updateChars(methodInfo.getMethodDescriptor());
    updateChars(methodInfo.getGenericSignature());

    char[][] thrownExceptions = methodInfo.getExceptionTypeNames();
    for (int i = 0; i < thrownExceptions.length; i++) {
      updateChars(thrownExceptions[i]);
    }
  }

  private void updateField(FieldInfo fieldInfo) {
    // generic signature
    updateChars(fieldInfo.getGenericSignature());
    updateInt(fieldInfo.getModifiers());
    updateLong(fieldInfo.getTagBits() & TagBits.AnnotationDeprecated);
    updateAnnotations(fieldInfo.getAnnotations());
    updateChars(fieldInfo.getName());
    updateChars(fieldInfo.getTypeName());
    updateBoolean(fieldInfo.hasConstant());
    if (fieldInfo.hasConstant()) {
      updateConstant(fieldInfo.getConstant());
    }
  }

  private void updateConstant(Constant constant) {
    updateInt(constant.typeID());
    updateString(constant.getClass().getName());
    switch (constant.typeID()) {
      case TypeIds.T_int:
        updateInt(constant.intValue());
        break;
      case TypeIds.T_byte:
        updateByte(constant.byteValue());
        break;
      case TypeIds.T_short:
        updateShort(constant.shortValue());
        break;
      case TypeIds.T_char:
        updateChar(constant.charValue());
        break;
      case TypeIds.T_long:
        updateLong(constant.longValue());
        break;
      case TypeIds.T_float:
        updateFloat(constant.floatValue());
        break;
      case TypeIds.T_double:
        updateDouble(constant.doubleValue());
        break;
      case TypeIds.T_boolean:
        updateBoolean(constant.booleanValue());
        break;
      case TypeIds.T_JavaLangString:
        updateString(constant.stringValue());
        break;
      default:
        throw new IllegalArgumentException("Unexpected constant typeID=" + constant.typeID());
    }
  }

  private void updateAnnotations(IBinaryAnnotation[] annotations) {
    if (annotations != null) {
      for (int i = 0; i < annotations.length; i++) {
        updateAnnotation(annotations[i]);
      }
    }
  }

  private void updateAnnotation(IBinaryAnnotation annotation) {
    updateChars(annotation.getTypeName());
    IBinaryElementValuePair[] pairs = annotation.getElementValuePairs();
    for (int j = 0; j < pairs.length; j++) {
      updateChars(pairs[j].getName());
      final Object value = pairs[j].getValue();
      if (value instanceof Object[]) {
        Object[] values = (Object[]) value;
        for (int n = 0; n < values.length; n++) {
          updateAnnotationValue(values[n]);
        }
      } else {
        updateAnnotationValue(value);
      }
    }
  }

  private void updateAnnotationValue(Object object) {
    // @see org.eclipse.jdt.internal.compiler.env.IBinaryElementValuePair.getValue()
    // @see org.eclipse.jdt.internal.compiler.classfmt.AnnotationInfo.decodeDefaultValue()
    if (object instanceof ClassSignature) {
      updateChars(((ClassSignature) object).getTypeName());
    } else if (object instanceof Constant) {
      updateConstant((Constant) object);
    } else if (object instanceof EnumConstantSignature) {
      updateChars(((EnumConstantSignature) object).getTypeName());
      updateChars(((EnumConstantSignature) object).getEnumConstantName());
    } else if (object instanceof IBinaryAnnotation) {
      updateAnnotation((IBinaryAnnotation) object);
    } else {
      throw new IllegalArgumentException("Unsupported annotation value " + object.toString());
    }
  }

  //
  // TODO move to a general purpose digester?
  //

  private void updateLong(long value) {
    byte[] tmp = new byte[8];
    tmp[0] = (byte) ((value >> 0x00) & 0xFF);
    tmp[1] = (byte) ((value >> 0x08) & 0xFF);
    tmp[2] = (byte) ((value >> 0x10) & 0xFF);
    tmp[3] = (byte) ((value >> 0x18) & 0xFF);
    tmp[4] = (byte) ((value >> 0x20) & 0xFF);
    tmp[5] = (byte) ((value >> 0x28) & 0xFF);
    tmp[6] = (byte) ((value >> 0x30) & 0xFF);
    tmp[7] = (byte) ((value >> 0x38) & 0xFF);
    digester.update(tmp);
  }

  private void updateInt(int value) {
    byte[] tmp = new byte[4];
    tmp[0] = (byte) ((value >> 0x00) & 0xFF);
    tmp[1] = (byte) ((value >> 0x08) & 0xFF);
    tmp[2] = (byte) ((value >> 0x10) & 0xFF);
    tmp[3] = (byte) ((value >> 0x18) & 0xFF);
    digester.update(tmp);
  }

  private void updateShort(short value) {
    byte[] tmp = new byte[2];
    tmp[0] = (byte) ((value >> 0x00) & 0xFF);
    tmp[1] = (byte) ((value >> 0x08) & 0xFF);
    digester.update(tmp);
  }

  private void updateChars(char[] value) {
    if (value != null) {
      for (int i = 0; i < value.length; i++) {
        updateChar(value[i]);
      }
    }
  }

  private void updateString(String value) {
    digester.update(value.getBytes(Charsets.UTF_8));
  }

  private void updateDouble(double value) {
    updateLong(Double.doubleToRawLongBits(value));
  }

  private void updateFloat(float value) {
    updateInt(Float.floatToRawIntBits(value));
  }

  private void updateChar(char value) {
    updateInt(value);
  }

  private void updateByte(byte value) {
    digester.update(value);
  }

  private void updateBoolean(boolean value) {
    digester.update(value ? (byte) 1 : (byte) 0);
  }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy