io.takari.maven.plugins.compile.jdt.ClassfileDigester Maven / Gradle / Ivy
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);
}
}