package io.takari.maven.plugins.compile;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Set;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.Label;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import com.google.common.io.Files;
class ClassfileMatchers {
private static class DebugInfo extends ClassVisitor {
private boolean hasSource = false;
private boolean hasLines = false;
private boolean hasVars = false;
public DebugInfo() {
super(Opcodes.ASM5);
}
@Override
public void visitSource(String source, String debug) {
hasSource = true;
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) {
hasVars = true;
}
@Override
public void visitLineNumber(int line, Label start) {
hasLines = true;
}
};
}
public boolean hasSource() {
return hasSource;
}
public boolean hasVars() {
return hasVars;
}
public boolean hasLines() {
return hasLines;
}
};
private static class AnnotationInfo extends ClassVisitor {
private final Set<String> annotations = new HashSet<>();
public AnnotationInfo() {
super(Opcodes.ASM5);
}
@Override
public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
annotations.add(desc);
return null;
}
public Set<String> getAnnotations() {
return annotations;
}
}
private static class MethodParameterInfo extends ClassVisitor {
private Set<String> methodParameterNames = new HashSet<>();
public MethodParameterInfo() {
super(Opcodes.ASM5);
}
@Override
public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
return new MethodVisitor(Opcodes.ASM5) {
@Override
public void visitParameter(String name, int access) {
methodParameterNames.add(name);
super.visitParameter(name, access);
}
};
}
public boolean hasMethodParameterName(String methodParameterName) {
return methodParameterNames.contains(methodParameterName);
}
};
private static abstract class ClassfileMatcher<T extends ClassVisitor> extends BaseMatcher<File> {
private String description;
protected ClassfileMatcher(String description) {
this.description = description;
}
@Override
public final boolean matches(Object item) {
File file = (File) item;
try (InputStream is = Files.asByteSource(file).openBufferedStream()) {
T visitor = newClassVisitor();
new ClassReader(is).accept(visitor, 0);
return matches(visitor);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void describeTo(Description description) {
description.appendText(this.description);
}
protected abstract T newClassVisitor();
protected abstract boolean matches(T info);
}
public static Matcher<File> hasDebugSource() {
return new ClassfileMatcher<DebugInfo>("include debug source info") {
@Override
protected boolean matches(DebugInfo info) {
return info.hasSource();
}
@Override
protected DebugInfo newClassVisitor() {
return new DebugInfo();
}
};
}
public static Matcher<File> hasDebugLines() {
return new ClassfileMatcher<DebugInfo>("include debug lines info") {
@Override
protected boolean matches(DebugInfo info) {
return info.hasLines();
}
@Override
protected DebugInfo newClassVisitor() {
return new DebugInfo();
}
};
}
public static Matcher<File> hasDebugVars() {
return new ClassfileMatcher<DebugInfo>("include debug lines info") {
@Override
protected boolean matches(DebugInfo info) {
return info.hasVars();
}
@Override
protected DebugInfo newClassVisitor() {
return new DebugInfo();
}
};
}
public static Matcher<File> hasAnnotation(final String annotation) {
final String desc = "L" + annotation.replace('.', '/') + ";";
return new ClassfileMatcher<AnnotationInfo>("has annotation " + annotation) {
@Override
protected boolean matches(AnnotationInfo info) {
return info.getAnnotations().contains(desc);
}
@Override
protected AnnotationInfo newClassVisitor() {
return new AnnotationInfo();
}
};
}
public static Matcher<File> hasMethodParameterWithName(final String parameter) {
return new ClassfileMatcher<MethodParameterInfo>("has method parameter " + parameter) {
@Override
protected boolean matches(MethodParameterInfo info) {
return info.hasMethodParameterName(parameter);
}
@Override
protected MethodParameterInfo newClassVisitor() {
return new MethodParameterInfo();
}
};
}
}