Java tutorial
/******************************************************************************* * Copyright (c) 2017, Xavier Miret Andres <xavier.mires@gmail.com> * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. *******************************************************************************/ package org.alkemy.instr; import static org.objectweb.asm.Opcodes.ASM5; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Supplier; import java.util.stream.Collectors; import org.alkemy.annotations.AlkemyLeaf; import org.alkemy.annotations.Order; import org.alkemy.instr.DefaultAlkemizer.Stop; import org.alkemy.instr.DefaultAlkemizerWriter.OrderValueReader; import org.objectweb.asm.AnnotationVisitor; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Iterables; public class DefaultAlkemizableVisitor extends AbstractAlkemizableVisitor { private static final Logger log = LoggerFactory.getLogger(DefaultAlkemizer.class); // maintain both identified && non-identified leafs markers to speed up the process. private final Set<String> leafMarkers = new HashSet<>(); private final Set<String> nonLeafMarkers = new HashSet<>(); final List<String> orderedFields = new ArrayList<>(); public DefaultAlkemizableVisitor(String className) { super(className); } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { final FieldVisitor fv = super.visitField(access, name, desc, signature, value); return new FieldAnnotationVisitor(fv, name, fieldMap, leafMarkers, nonLeafMarkers, new HashSet<>(), new HasLeaves()); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (Order.class.getName().equals(AlkemizerUtils.toQualifiedNameFromDesc(desc))) { return new OrderValueReader(orderedFields, super.visitAnnotation(desc, visible)); } return super.visitAnnotation(desc, visible); } @Override public void visitEnd() { final List<String> fields = fieldMap.entrySet().stream().filter(entry -> entry.getValue().alkemizable) .map(entry -> entry.getKey()).collect(Collectors.toList()); if (!orderedFields.isEmpty()) { checkFieldNames(fields, orderedFields); } else { orderedFields.addAll(fields); } } private static void checkFieldNames(Collection<String> fields, List<String> orderedFields) { if (!fields.containsAll(orderedFields)) throw new Stop("Invalid order definition (alien)."); // invalid definition } static class FieldProperties { final boolean isEnum; final boolean isStatic; final String desc; final String signature; boolean alkemizable; public FieldProperties(String desc, String signature, boolean isEnum, boolean isStatic) { this.isEnum = isEnum; this.isStatic = isStatic; this.desc = desc; this.signature = signature; } } static class MethodProperties { final boolean isStatic; final String desc; final String signature; boolean alkemizable; public MethodProperties(String desc, String signature, boolean isStatic) { this.isStatic = isStatic; this.desc = desc; this.signature = signature; } } static class FieldAnnotationVisitor extends FieldVisitor { protected final String name; protected final Map<String, FieldProperties> fields; protected final Set<String> leafMarkers; protected final Set<String> nonLeafMarkers; protected final Set<String> visited; protected final HasLeaves hasLeaves; FieldAnnotationVisitor(FieldVisitor fv, String name, Map<String, FieldProperties> fields, Set<String> leafMarkers, Set<String> nonLeafMarkers, Set<String> visited, HasLeaves hasLeaves) { super(ASM5, fv); this.name = name; this.fields = fields; this.leafMarkers = leafMarkers; this.nonLeafMarkers = nonLeafMarkers; this.visited = visited; this.hasLeaves = hasLeaves; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (visible && isAlkemizable(desc)) { fields.get(name).alkemizable = true; } return super.visitAnnotation(desc, visible); } @Override public void visitEnd() { // Nodes are not annotated themselves, but contain leaves at some depth. // If a non-annotated type is found, deep search for leaves to identify it as a node. final FieldProperties props = fields.get(name); if (!props.alkemizable && !visited.contains(props.desc) && AlkemizerUtils.isType(props.desc) && !AlkemizerUtils.isEnum(props.desc)) { try { if (isNode(props)) { props.alkemizable = true; } } catch (IOException e) { log.trace("Cannot read the annotation '{}'. Ignore.", name); } } super.visitEnd(); } private boolean isNode(FieldProperties props) throws IOException { final String desc; if (AlkemizerUtils.isCollection(props.desc)) desc = AlkemizerUtils.toGenericType(props.signature, props.desc); else desc = props.desc; // Avoid cycles visited.add(desc); new ClassReader(AlkemizerUtils.toQualifiedNameFromDesc(desc)).accept( new TypeDeepLeafSearch(visited, leafMarkers, nonLeafMarkers, hasLeaves), ClassReader.SKIP_CODE); return hasLeaves.get(); } private boolean isAlkemizable(String desc) { return !nonLeafMarkers.contains(desc) && (leafMarkers.contains(desc) | isLeaf(desc)); } private boolean isLeaf(String desc) { final String qualifiedName = AlkemizerUtils.toQualifiedNameFromDesc(desc); try { final ClassReader cr = new ClassReader(qualifiedName); final SearchForLeafMarker cv = new SearchForLeafMarker(nonLeafMarkers); cr.accept(cv, ClassReader.SKIP_CODE); if (cv.annotated) { leafMarkers.add(desc); } return cv.annotated; } catch (IOException e) { nonLeafMarkers.add(desc); log.trace("Cannot read the annotation '{}'. Ignore.", desc); } return false; } } @Override public boolean isClassAlkemizable() { return Iterables.find(fieldMap.values(), p -> p.alkemizable) != null || Iterables.find(methodMap.values(), p -> p.alkemizable) != null; } static class SearchForLeafMarker extends ClassVisitor { private final Set<String> nonLeafMarkers; private boolean annotated = false; SearchForLeafMarker(Set<String> nonAlkemizableAnnotations) { super(ASM5); this.nonLeafMarkers = nonAlkemizableAnnotations; } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { if (AlkemyLeaf.class.getName().equals(AlkemizerUtils.toQualifiedNameFromDesc(desc))) { annotated = true; } else { nonLeafMarkers.add(desc); } return super.visitAnnotation(desc, visible); } } static class TypeDeepLeafSearch extends AbstractAlkemizableVisitor { private final Set<String> visited = new HashSet<>(); private final Set<String> leafMarkers; private final Set<String> nonLeafMarkers; private final HasLeaves hasLeaves; public TypeDeepLeafSearch(Set<String> visited, Set<String> alkemizableAnnotations, Set<String> nonAlkemizableAnnotations, HasLeaves hasLeaves) { super(null); // this.visited = visited; this.leafMarkers = alkemizableAnnotations; this.nonLeafMarkers = nonAlkemizableAnnotations; this.hasLeaves = hasLeaves; } @Override public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) { final FieldVisitor fv = super.visitField(access, name, desc, signature, value); if (visited.contains(desc)) return fv; return new FieldLeafVisitor(visited, name, fieldMap, leafMarkers, nonLeafMarkers, hasLeaves); } @Override public boolean isClassAlkemizable() { return hasLeaves.get(); } } static class FieldLeafVisitor extends FieldAnnotationVisitor { FieldLeafVisitor(Set<String> visited, String name, Map<String, FieldProperties> fields, Set<String> leafMarkers, Set<String> nonLeafMarkers, HasLeaves hasLeaves) { super(null, name, fields, leafMarkers, nonLeafMarkers, visited, hasLeaves); } @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { final AnnotationVisitor av = super.visitAnnotation(desc, visible); if (fields.get(name).alkemizable) { hasLeaves.leaf = true; } return av; } @Override public void visitEnd() { if (hasLeaves.get()) { return; } else { super.visitEnd(); } } }; static class HasLeaves implements Supplier<Boolean> { boolean leaf; @Override public Boolean get() { return leaf; } } }