package rtt.annotation.editor.data.asm;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.AnnotationNode;
import org.objectweb.asm.tree.ClassNode;
import org.objectweb.asm.tree.FieldNode;
import org.objectweb.asm.tree.MethodNode;
import rtt.annotation.editor.model.ClassElement;
import rtt.annotation.editor.model.ClassModel;
import rtt.annotation.editor.model.FieldElement;
import rtt.annotation.editor.model.MethodElement;
import rtt.annotation.editor.model.annotation.Annotatable;
import rtt.annotation.editor.model.annotation.Annotation;
final class ExportModelFileWalker extends AbstractFileWalker {
public class ExportClassAdapter extends ClassNode {
private ClassElement element;
public ExportClassAdapter(ClassVisitor cv, ClassElement element) {
super(Opcodes.ASM5);
this.cv = cv;
this.element = element;
}
@SuppressWarnings("unchecked")
@Override
public void visitEnd() {
// set JRE version to at least 1.5
if ((version & 0xFF) < Opcodes.V1_5) {
version = Opcodes.V1_5;
}
if (visibleAnnotations != null) {
removeObsoleteAnnotations(visibleAnnotations);
}
if (element.hasAnnotation()) {
if (visibleAnnotations == null) {
visibleAnnotations = new ArrayList<>();
}
addAnnotation(element, visibleAnnotations);
}
processFields(this, element.getValuableFields());
processMethods(this, element.getValuableMethods());
processMethods(this, element.getInitializableMethods());
accept(cv);
}
}
public ExportModelFileWalker(ClassModel model) {
super(model);
}
@Override
protected void processData(Path file) throws IOException {
ClassReader reader = new ClassReader(Files.readAllBytes(file));
String className = reader.getClassName().replace("/", ".");
final ClassElement element = ASMClassModelManager.RESOLVER.findClass(className, model);
if (element != null && element.hasChanged()) {
ClassWriter writer = new ClassWriter(reader, 0);
ClassVisitor visitor = new ExportClassAdapter(writer, element);
reader.accept(visitor, 0);
Files.write(file, writer.toByteArray(), StandardOpenOption.WRITE);
}
}
private void removeObsoleteAnnotations(List<AnnotationNode> annotations) {
Iterator<AnnotationNode> iterator = annotations.iterator();
while (iterator.hasNext()) {
AnnotationNode annotationNode = iterator.next();
Annotation annotation = ASMAnnotationConverter.
getAnnotation(annotationNode.desc);
if (annotation != null) {
iterator.remove();
}
}
}
private void addAnnotation(Annotatable<?> element, List<AnnotationNode> annotations) {
if (element.hasAnnotation()) {
Annotation annotation = element.getAnnotation();
String descriptor = ASMAnnotationConverter.getDescriptor(annotation.getClass());
AnnotationNode annotationNode = new AnnotationNode(descriptor);
for (String key : annotation.getKeys()) {
annotationNode.visit(key, annotation.getAttribute(key));
}
annotations.add(annotationNode);
}
}
@SuppressWarnings("unchecked")
private <T extends Annotation> void processFields(ClassNode node,
List<FieldElement<T>> elements) {
for (FieldElement<?> fieldElement : elements) {
if (fieldElement.hasChanged()) {
FieldNode fieldNode = findField(node.fields, fieldElement);
if (fieldNode.visibleAnnotations != null) {
removeObsoleteAnnotations(fieldNode.visibleAnnotations);
}
if (fieldElement.hasAnnotation()) {
if (fieldNode.visibleAnnotations == null) {
fieldNode.visibleAnnotations = new ArrayList<>();
}
addAnnotation(fieldElement, fieldNode.visibleAnnotations);
}
}
}
}
private FieldNode findField(List<FieldNode> fields, FieldElement<?> fieldElement) {
String className = null;
for (FieldNode fieldNode : fields) {
className = Type.getType(fieldNode.desc).getClassName();
if (fieldElement.getName().equals(fieldNode.name) &&
fieldElement.getType().equals(className)) {
return fieldNode;
}
}
return null;
}
@SuppressWarnings("unchecked")
private <T extends Annotation> void processMethods(ClassNode node,
List<MethodElement<T>> elements) {
for(MethodElement<?> methodElement : elements) {
if (methodElement.hasChanged()) {
MethodNode methodNode = findMethod(node.methods, methodElement);
if (methodNode.visibleAnnotations != null) {
removeObsoleteAnnotations(methodNode.visibleAnnotations);
}
if (methodElement.hasAnnotation()) {
if (methodNode.visibleAnnotations == null) {
methodNode.visibleAnnotations = new ArrayList<>();
}
addAnnotation(methodElement, methodNode.visibleAnnotations);
}
}
}
}
private MethodNode findMethod(List<MethodNode> methods, MethodElement<?> method) {
String returningClass = null;
for (MethodNode methodNode : methods) {
returningClass = Type.getReturnType(methodNode.desc).getClassName();
if (method.getName().equals(methodNode.name) &&
method.getType().equals(returningClass) &&
equalParameters(method, methodNode)) {
return methodNode;
}
}
return null;
}
private boolean equalParameters(MethodElement<?> method, MethodNode methodNode) {
Type[] parameters = Type.getArgumentTypes(methodNode.desc);
if (method.getParameters().size() != parameters.length) {
return false;
}
for(int i = 0; i < parameters.length; i++) {
if (!method.getParameters().get(i).equals(parameters[i].getClassName())) {
return false;
}
}
return true;
}
}