All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.babyfish.model.jpa.instrument.impl.JPAMetadataJoin Maven / Gradle / Ivy

Go to download

This tool project supports some maven plugins of BabyFish, it shouldn't used by the user directly.

The newest version!
/*
 * BabyFish, Object Model Framework for Java and JPA.
 * https://github.com/babyfish-ct/babyfish
 *
 * Copyright (c) 2008-2016, Tao Chen
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * Please visit "http://opensource.org/licenses/LGPL-3.0" to know more.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 */
package org.babyfish.model.jpa.instrument.impl;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;

import org.babyfish.collection.ArrayList;
import org.babyfish.collection.MACollections;
import org.babyfish.lang.I18N;
import org.babyfish.lang.bytecode.ASMTreeUtils;
import org.babyfish.lang.bytecode.ASMUtils;
import org.babyfish.lang.instrument.IllegalClassException;
import org.babyfish.model.jpa.instrument.spi.ASMConstants;
import org.babyfish.org.objectweb.asm.tree.AnnotationNode;
import org.babyfish.org.objectweb.asm.tree.FieldNode;

/**
 * @author Tao Chen
 */
final class JPAMetadataJoin {
    
    String tableName;
    
    List columns;
    
    List inversedColumns;

    public JPAMetadataJoin(JPAMetadataPropertyImpl property) {
        List> annotationTypes = new ArrayList<>();
        FieldNode fieldNode = property.unresolved.fieldNode;
        AnnotationNode joinColumnNode = ASMTreeUtils.getAnnotationNode(fieldNode, JoinColumn.class);
        AnnotationNode joinColumnsNode = ASMTreeUtils.getAnnotationNode(fieldNode, JoinColumns.class);
        AnnotationNode joinTableNode = ASMTreeUtils.getAnnotationNode(fieldNode, JoinTable.class);
        if (property.unresolved.primaryAnnotationNode.desc.equals(ASMConstants.MANY_TO_MANY_DESCRIPTOR)) {
            if (joinTableNode == null) {
                throw new IllegalClassException(
                        requireAnnoatation(
                                property, 
                                ASMUtils.toClassName(property.unresolved.primaryAnnotationNode.desc),
                                JoinTable.class
                        )
                );
            }
        }
        if (joinColumnNode != null) {
            annotationTypes.add(JoinColumn.class);
        } else if (joinColumnsNode != null) {
            annotationTypes.add(JoinColumns.class);
        } else if (joinTableNode != null) {
            annotationTypes.add(JoinTable.class);
        }
        if (annotationTypes.isEmpty()) {
            throw new IllegalClassException(
                    requireAnyAnnoatation(
                            property, 
                            ASMUtils.toClassName(property.unresolved.primaryAnnotationNode.desc),
                            JoinColumn.class, 
                            JoinColumns.class, 
                            JoinTable.class
                    )
            );
        }
        if (annotationTypes.size() > 1) {
            throw new IllegalClassException(
                    conflictAnnotations(property, annotationTypes)
            );
        }
        
        JPAMetadataClassImpl referencedSingleIdClass;
        JPAMetadataClassImpl inverseReferencedSingleIdClass = null;
        if (joinTableNode != null || 
                property.unresolved.primaryAnnotationNode.desc
                .equals(ASMConstants.ONE_TO_MANY_DESCRIPTOR)) {
            referencedSingleIdClass = singleIdClass((JPAMetadataClassImpl)property.getDeclaringClass());
            inverseReferencedSingleIdClass = singleIdClass(property.targetClass);
        } else {
            referencedSingleIdClass = singleIdClass(property.targetClass);
            inverseReferencedSingleIdClass = singleIdClass((JPAMetadataClassImpl)property.getDeclaringClass());
        }
        
        if (joinColumnNode != null) {
            this.initByJoinColumnNode(property, joinColumnNode, referencedSingleIdClass);
        } else if (joinColumnsNode != null) {
            this.initByJoinColumnsNode(property, joinColumnsNode, referencedSingleIdClass);
        } else {
            this.initByJoinTableNode(property, joinTableNode, referencedSingleIdClass, inverseReferencedSingleIdClass);
        }
    }
    
    private void initByJoinColumnNode(
            JPAMetadataPropertyImpl property, 
            AnnotationNode joinColumnNode,
            JPAMetadataClassImpl referencedSingleIdClass) {
        this.columns = MACollections.wrap(
                new Column(
                        property,
                        joinColumnNode,
                        "joinColumns", 
                        0, 
                        referencedSingleIdClass
                )
        );
    }
    
    private void initByJoinColumnsNode(
            JPAMetadataPropertyImpl property, 
            AnnotationNode joinColumnsNode,
            JPAMetadataClassImpl referencedSingleIdClass) {
        List joinColumnNodes = ASMTreeUtils.getAnnotationValue(joinColumnsNode, "value");
        if (joinColumnNodes == null || joinColumnNodes.isEmpty()) {
            throw new IllegalClassException(
                    emptyJoinColumns(property, JoinColumns.class, JoinColumn.class)
            );
        }
        if (joinColumnNodes.size() == 1) {
            this.columns = MACollections.wrap(
                    new Column(
                            property,
                            joinColumnNodes.get(0),
                            "joinColumns", 
                            0, 
                            referencedSingleIdClass
                    )
            );
        } else {
            Column[] columns = new Column[joinColumnNodes.size()];
            int index = 0;
            for (AnnotationNode joinColumnNode : joinColumnNodes) {
                columns[index++] = new Column(
                        property,
                        joinColumnNode,
                        "joinColumns",
                        index,
                        null
                );
            }
            this.columns = MACollections.wrap(columns);
        }
    }
    
    private void initByJoinTableNode(
            JPAMetadataPropertyImpl property, 
            AnnotationNode joinTableNode,
            JPAMetadataClassImpl referencedSingleIdClass,
            JPAMetadataClassImpl inverseReferencedSingleIdClass) {
        this.tableName = ASMTreeUtils.getAnnotationValue(joinTableNode, "name", "");
        if (this.tableName.isEmpty()) {
            throw new IllegalClassException(
                    emptyJoinTableName(property, JoinTable.class)
            );
        }
        List joinColumnNodes = ASMTreeUtils.getAnnotationValue(joinTableNode, "joinColumns");
        if (joinColumnNodes == null || joinColumnNodes.isEmpty()) {
            throw new IllegalClassException(
                    emptyJoinTableColumns(
                            property, 
                            JoinTable.class, 
                            "joinColumns", 
                            JoinColumn.class)
            );
        }
        List inverseJoinColumnNodes = ASMTreeUtils.getAnnotationValue(joinTableNode, "inverseJoinColumns");
        if (inverseJoinColumnNodes == null || inverseJoinColumnNodes.isEmpty()) {
            throw new IllegalClassException(
                    emptyJoinTableColumns(
                            property, 
                            JoinTable.class, 
                            "inverseJoinColumnNodes", 
                            JoinColumn.class)
            );
        }
        int count = joinColumnNodes.size();
        if (count == 1) {
            this.columns = MACollections.wrap(
                    new Column(
                            property,
                            joinColumnNodes.get(0),
                            "joinColumns",
                            0,
                            referencedSingleIdClass
                    )
            );
            this.inversedColumns = MACollections.wrap(
                    new Column(
                            property,
                            inverseJoinColumnNodes.get(0),
                            "inverseJoinColumns",
                            0,
                            inverseReferencedSingleIdClass
                    )
            );
        } else {
            Column[] columns = new Column[count];
            Column[] inverseColumns = new Column[count];
            int index;
            
            index = 0;
            for (AnnotationNode joinColumnNode : joinColumnNodes) {
                columns[index++] = new Column(
                        property,
                        joinColumnNode,
                        "joinColumns",
                        index,
                        null
                );
            }
            
            index = 0;
            for (AnnotationNode inverseJoinColumnNode : inverseJoinColumnNodes) {
                inverseColumns[index++] = new Column(
                        property,
                        inverseJoinColumnNode,
                        "inverseJoinColumns",
                        index,
                        null
                );
            }
            
            this.columns = MACollections.wrap(columns);
            this.inversedColumns = MACollections.wrap(inverseColumns);
        }
    }
    
    private static JPAMetadataClassImpl singleIdClass(JPAMetadataClassImpl jpaMetadataClassImpl) {
        if (jpaMetadataClassImpl.idProperty.targetClass != null) {
            return null; //Embedded id, not single id
        }
        return jpaMetadataClassImpl;
    }

    public boolean likeBedirectionalAssociation(JPAMetadataJoin join) {
        if (join == null) {
            return false;
        }
        if (this.tableName != null && join.tableName != null) {
            return 
                    DbIdentifiers.laxIdentifier(this.tableName)
                    .equals(DbIdentifiers.laxIdentifier(join.tableName)) &&
                    like(this.columns, join.inversedColumns) && 
                    like(this.inversedColumns, join.columns);
        }
        if (this.tableName == null && join.tableName == null) {
            return like(this.columns, join.columns);
        }
        return false;
    }
    
    public boolean isReadonly() {
        for (Column column : columns) {
            if (column.insertable || column.updatable) {
                return false;
            }
        }
        if (this.inversedColumns != null) {
            for (Column inversedColumn : this.inversedColumns) {
                if (inversedColumn.insertable || inversedColumn.updatable) {
                    return false;
                }
            }
        }
        return true;
    }
    
    @Override
    public String toString() {
        if (this.tableName != null) {
            StringBuilder builder = new StringBuilder();
            builder
            .append("@JoinTable(name = \"")
            .append(this.tableName)
            .append("\", joinColumns = ");
            appendColumns(this.columns, builder);
            builder.append(", inverseJoinColumns = ");
            appendColumns(this.inversedColumns, builder);
            builder.append(')');
            return builder.toString();
        }
        if (this.columns.size() > 1) {
            StringBuilder builder = new StringBuilder();
            builder.append("@JoinColumns(");
            appendColumns(this.columns, builder);
            builder.append(')');
            return builder.toString();
        } 
        return this.columns.get(0).toString();
    }
    
    private static void appendColumns(List columns, StringBuilder builder) {
        if (columns.size() == 1) {
            builder.append(columns.get(0));
        } else {
            builder.append("{ ");
            boolean addComma = false;
            for (Column column : columns) {
                if (addComma) {
                    builder.append(", ");
                } else {
                    addComma = true;
                }
                builder.append(column);
            }
            builder.append(" }");
        }
    }

    private static boolean like(List a, List b) {
        if (a.size() != b.size()) {
            return false;
        }
        a = sort(a);
        b = sort(b);
        for (int i = a.size() - 1; i >= 0; i--) {
            if (!like(a.get(i), b.get(i))) {
                return false;
            }
        }
        return true;
    }
    
    private static List sort(List columns) {
        List list = new ArrayList<>(columns);
        Collections.sort(list, new Comparator() {
            @Override
            public int compare(Column c1, Column c2) {
                return DbIdentifiers.laxIdentifier(c1.name).compareTo(
                        DbIdentifiers.laxIdentifier(c2.name)
                );
            }
        });
        return list;
    }
    
    private static boolean like(Column a, Column b) {
        return  
                DbIdentifiers.laxIdentifier(a.name).equals(
                        DbIdentifiers.laxIdentifier(b.name)
                ) 
                &&
                DbIdentifiers.laxIdentifier(a.referencedColumnName).equals(
                        DbIdentifiers.laxIdentifier(b.referencedColumnName)
                );
    }
    
    private static class Column {
        
        String name;
        
        String referencedColumnName;
        
        boolean insertable;
        
        boolean updatable;
        
        Column(
                JPAMetadataPropertyImpl property, 
                AnnotationNode joinColumnNode,
                String joinColumnsKindName,
                int index,
                JPAMetadataClassImpl referencedSingleIdClass) {
            this.name = ASMTreeUtils.getAnnotationValue(joinColumnNode, "name", "");
            if (this.name.isEmpty()) {
                throw new IllegalClassException(
                        requireJoinColumnName(
                                property, 
                                joinColumnsKindName, 
                                index
                        )
                );
            }
            this.referencedColumnName = ASMTreeUtils.getAnnotationValue(joinColumnNode, "referencedColumnName", "");
            if (this.referencedColumnName.isEmpty()) {
                if (referencedSingleIdClass == null) {
                    throw new IllegalClassException(
                            requireReferencedColumnName(
                                    property, 
                                    joinColumnsKindName, 
                                    index
                            )
                    );
                }
                this.referencedColumnName = referencedSingleIdClass.idProperty.unresolved.singleColumnName;
            }
            this.insertable = ASMTreeUtils.getAnnotationValue(joinColumnNode, "insertable", true);
            this.updatable = ASMTreeUtils.getAnnotationValue(joinColumnNode, "updatable", false);
        }

        @Override
        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder
            .append("@JoinColumn(name = \"")
            .append(this.name)
            .append("\", referencedColumnName = \"")
            .append(this.referencedColumnName)
            .append('"');
            if (!this.insertable) {
                builder.append(", insertable = false");
            }
            if (!this.updatable) {
                builder.append(", updatable = false");
            }
            builder.append(')');
            return builder.toString();
        }
    }
    
    @I18N    
    private static native String requireAnnoatation(
                JPAMetadataPropertyImpl property,
                String associationAnnotationTypeName,
                Class joinTableTypeConstant);
        
    @I18N    
    private static native String requireAnyAnnoatation(
                JPAMetadataPropertyImpl property,
                String associationAnnotationTypeName,
                Class joinColumnTypeConstant,
                Class joinColumnsTypeConstant,
                Class joinTableTypeConstant);

    @I18N    
    private static native String conflictAnnotations(
                JPAMetadataPropertyImpl property,
                Collection> annotationTypes);
        
    @I18N    
    private static native String emptyJoinColumns(
                JPAMetadataPropertyImpl property, 
                Class joinColumnsTypeConstant, 
                Class joinColumnTypeConstant);
        
    @I18N    
    private static native String emptyJoinTableName(
                JPAMetadataPropertyImpl property,
                Class joinTableTypeConstant);
        
    @I18N    
    private static native String emptyJoinTableColumns(
                JPAMetadataPropertyImpl property,
                Class joinTableTypeConstant,
                String attributeName,
                Class joinColumnTypeConstant);
        
    @I18N    
    private static native String requireJoinColumnName(
                JPAMetadataPropertyImpl property,
                String joinColumnsKindName,
                int index);
        
    @I18N    
    private static native String requireReferencedColumnName(
                JPAMetadataPropertyImpl property,
                String joinColumnsKindName,
                int index);
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy