// Path: drm-core/src/main/java/com/nitorcreations/domain/Edge.java
// public class Edge {
// 
//     public final DomainObject source;
//     public final DomainObject target;
//     public final EdgeType type;
//     public final Direction direction;
// 
//     public Edge(DomainObject source, DomainObject target, EdgeType type, Direction direction) {
//         this.source = source;
//         this.target = target;
//         this.type = type;
//         this.direction = direction;
//     }
// 
//     public Edge(DomainObject source, DomainObject target, EdgeType type) {
//         this(source, target, type, null);
//     }
// 
//     @Override
//     public final int hashCode() {
//         return HashCodeBuilder.reflectionHashCode(this);
//     }
// 
//     @Override
//     public final boolean equals(Object obj) {
//         return EqualsBuilder.reflectionEquals(this, obj);
//     }
// 
//     @Override
//     public String toString() {
//         return ReflectionToStringBuilder.toString(this);
//     }
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/domain/EdgeType.java
// public enum EdgeType {
// 
//     ONE_TO_ONE, MANY_TO_ONE, MANY_TO_MANY, ONE_TO_MANY, EXTENDS, INNER_CLASS;
// 
//     public static EdgeType resolveEdgeType(EdgeType source, EdgeType target) {
//         if (source.equals(target)) {
//             switch (source) {
//                 case ONE_TO_ONE:
//                 case MANY_TO_MANY:
//                     return source;
//                 case MANY_TO_ONE:
//                 case ONE_TO_MANY:
//                     return MANY_TO_MANY;
//                 case EXTENDS:
//                 case INNER_CLASS:
//                     throw new RuntimeException("impossible");
//             }
//         }
//         if (source.equals(ONE_TO_ONE)) {
//             return target;
//         } else if (target.equals(ONE_TO_ONE)) {
//             return source;
//         }
//         throw new RuntimeException("impossible");
//     }
// 
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/scanners/EdgeOperations.java
// public static Edge createEdge(Class<?> sourceClass, Class<?> field, EdgeType type, String name) {
//     DomainObject source = new DomainObject(sourceClass, name);
//     DomainObject target = new DomainObject(field);
//     return new Edge(source, target, type, UNI_DIRECTIONAL);
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/scanners/EdgeOperations.java
// public static List<Edge> mergeBiDirectionals(List<Edge> edges) {
//     Collection<List<Edge>> groupedEdges = groupEdges(edges);
//     List<Edge> uniDirectionals = takeSingleItemsGroups(groupedEdges);
//     List<Edge> biDirectionals = mergeNonSingleGroups(groupedEdges);
//     List<Edge> mergedEdges = Lists.newArrayList();
//     mergedEdges.addAll(uniDirectionals);
//     mergedEdges.addAll(biDirectionals);
//     return mergedEdges;
// }


import com.nitorcreations.domain.Edge;
import com.nitorcreations.domain.EdgeType;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static com.nitorcreations.scanners.EdgeOperations.createEdge;
import static com.nitorcreations.scanners.EdgeOperations.mergeBiDirectionals;
import static java.util.Optional.empty;
import static java.util.Optional.of;
        return mergeBiDirectionals(edges);
    }

    private List<Edge> extractFieldEdges(Class<?> clazz) {
        List<Edge> fieldEdges = new ArrayList<>();
        try {
            InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace(".", "/") + ".class");
            ClassReader reader = new ClassReader(is);
            reader.accept(new ClassVisitor(Opcodes.ASM4) {
                @Override
                public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
                    try {
                        Optional<Edge> fieldEdge = createFieldEdge(clazz, clazz.getDeclaredField(name));
                        if (fieldEdge.isPresent()) {
                            fieldEdges.add(fieldEdge.get());
                        }
                    } catch (NoSuchFieldException e) {
                        // should never happen
                    } catch (NoClassDefFoundError e) {
                        logger.warn("Skipped field " + name + " in class " + clazz.getName() + " because it's type class is not available. Field description: " + desc);
                    }
                    return super.visitField(access, name, desc, signature, value);
                }
            }, ClassReader.SKIP_CODE);
        } catch (IOException e) {
            logger.warn("Failed to read bytecode for class " + clazz.getName(), e);
        }
        return fieldEdges;
    }

// Path: drm-core/src/main/java/com/nitorcreations/domain/Edge.java
// public class Edge {
// 
//     public final DomainObject source;
//     public final DomainObject target;
//     public final EdgeType type;
//     public final Direction direction;
// 
//     public Edge(DomainObject source, DomainObject target, EdgeType type, Direction direction) {
//         this.source = source;
//         this.target = target;
//         this.type = type;
//         this.direction = direction;
//     }
// 
//     public Edge(DomainObject source, DomainObject target, EdgeType type) {
//         this(source, target, type, null);
//     }
// 
//     @Override
//     public final int hashCode() {
//         return HashCodeBuilder.reflectionHashCode(this);
//     }
// 
//     @Override
//     public final boolean equals(Object obj) {
//         return EqualsBuilder.reflectionEquals(this, obj);
//     }
// 
//     @Override
//     public String toString() {
//         return ReflectionToStringBuilder.toString(this);
//     }
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/domain/EdgeType.java
// public enum EdgeType {
// 
//     ONE_TO_ONE, MANY_TO_ONE, MANY_TO_MANY, ONE_TO_MANY, EXTENDS, INNER_CLASS;
// 
//     public static EdgeType resolveEdgeType(EdgeType source, EdgeType target) {
//         if (source.equals(target)) {
//             switch (source) {
//                 case ONE_TO_ONE:
//                 case MANY_TO_MANY:
//                     return source;
//                 case MANY_TO_ONE:
//                 case ONE_TO_MANY:
//                     return MANY_TO_MANY;
//                 case EXTENDS:
//                 case INNER_CLASS:
//                     throw new RuntimeException("impossible");
//             }
//         }
//         if (source.equals(ONE_TO_ONE)) {
//             return target;
//         } else if (target.equals(ONE_TO_ONE)) {
//             return source;
//         }
//         throw new RuntimeException("impossible");
//     }
// 
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/scanners/EdgeOperations.java
// public static Edge createEdge(Class<?> sourceClass, Class<?> field, EdgeType type, String name) {
//     DomainObject source = new DomainObject(sourceClass, name);
//     DomainObject target = new DomainObject(field);
//     return new Edge(source, target, type, UNI_DIRECTIONAL);
// }
// 
// Path: drm-core/src/main/java/com/nitorcreations/scanners/EdgeOperations.java
// public static List<Edge> mergeBiDirectionals(List<Edge> edges) {
//     Collection<List<Edge>> groupedEdges = groupEdges(edges);
//     List<Edge> uniDirectionals = takeSingleItemsGroups(groupedEdges);
//     List<Edge> biDirectionals = mergeNonSingleGroups(groupedEdges);
//     List<Edge> mergedEdges = Lists.newArrayList();
//     mergedEdges.addAll(uniDirectionals);
//     mergedEdges.addAll(biDirectionals);
//     return mergedEdges;
// }


// Path: drm-core/src/main/java/com/nitorcreations/scanners/FieldScanner.java
import com.nitorcreations.domain.Edge;
import com.nitorcreations.domain.EdgeType;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.ClassVisitor;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.Opcodes;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import static com.nitorcreations.scanners.EdgeOperations.createEdge;
import static com.nitorcreations.scanners.EdgeOperations.mergeBiDirectionals;
import static java.util.Optional.empty;
import static java.util.Optional.of;

        return mergeBiDirectionals(edges);
    }

    private List<Edge> extractFieldEdges(Class<?> clazz) {
        List<Edge> fieldEdges = new ArrayList<>();
        try {
            InputStream is = clazz.getClassLoader().getResourceAsStream(clazz.getName().replace(".", "/") + ".class");
            ClassReader reader = new ClassReader(is);
            reader.accept(new ClassVisitor(Opcodes.ASM4) {
                @Override
                public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
                    try {
                        Optional<Edge> fieldEdge = createFieldEdge(clazz, clazz.getDeclaredField(name));
                        if (fieldEdge.isPresent()) {
                            fieldEdges.add(fieldEdge.get());
                        }
                    } catch (NoSuchFieldException e) {
                        // should never happen
                    } catch (NoClassDefFoundError e) {
                        logger.warn("Skipped field " + name + " in class " + clazz.getName() + " because it's type class is not available. Field description: " + desc);
                    }
                    return super.visitField(access, name, desc, signature, value);
                }
            }, ClassReader.SKIP_CODE);
        } catch (IOException e) {
            logger.warn("Failed to read bytecode for class " + clazz.getName(), e);
        }
        return fieldEdges;
    }

    private Optional<Edge> createFieldEdge(Class<?> clazz, Field field) {