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

org.babyfish.persistence.tool.instrument.metadata.MetadataPropertyImpl Maven / Gradle / Ivy

Go to download

A sub project of BabyFish to support basic logic for BabyFish maven plugins, it should not 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-2015, 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.persistence.tool.instrument.metadata;

import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.NavigableMap;
import java.util.NavigableSet;
import java.util.Set;

import javax.persistence.Access;
import javax.persistence.AccessType;
import javax.persistence.AttributeOverride;
import javax.persistence.AttributeOverrides;
import javax.persistence.Basic;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Embeddable;
import javax.persistence.Embedded;
import javax.persistence.EmbeddedId;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinColumns;
import javax.persistence.JoinTable;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.MapKey;
import javax.persistence.MapKeyColumn;
import javax.persistence.MapKeyJoinColumn;
import javax.persistence.MapKeyJoinColumns;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.OrderColumn;
import javax.persistence.Transient;
import javax.persistence.Version;

import org.babyfish.collection.ArrayList;
import org.babyfish.collection.HashMap;
import org.babyfish.collection.LinkedHashMap;
import org.babyfish.collection.LinkedHashSet;
import org.babyfish.collection.MACollections;
import org.babyfish.collection.XOrderedSet;
import org.babyfish.lang.Nulls;
import org.babyfish.lang.reflect.asm.ASM;
import org.babyfish.org.objectweb.asm.tree.AnnotationNode;
import org.babyfish.org.objectweb.asm.tree.FieldNode;
import org.babyfish.org.objectweb.asm.tree.MethodNode;
import org.babyfish.persistence.instrument.ContravarianceInstrument;
import org.babyfish.persistence.instrument.JPAObjectModelInstrument;
import org.babyfish.persistence.instrument.NavigableInstrument;
import org.babyfish.persistence.instrument.ReferenceComparisonRuleInstrument;
import org.babyfish.reference.IndexedReference;
import org.babyfish.reference.KeyedReference;
import org.babyfish.reference.Reference;
import org.babyfish.util.Joins;
import org.babyfish.util.LazyResource;

/**
 * @author Tao Chen
 */
class MetadataPropertyImpl implements MetadataProperty {
    
    private static final LazyResource LAZY_RESOURCE = LazyResource.of(Resource.class);

    private MetadataClassImpl declaringMetadataClass;
    
    private String name;
    
    private AccessType accessType;
    
    private String typeDescriptor;
    
    private String keyTypeDescriptor;
    
    private String getterName;
    
    private String setterName;
    
    private List visibleAnnotations;
    
    private List invisibleAnnotations;
    
    private Class primaryAnnotationType;
    
    private boolean lob;
    
    private FetchType fetchType;
    
    private boolean any;
    
    private String columnName;
    
    private Map embeddedColumnNames;
    
    private MetadataClassImpl relatedClass;
    
    private MetadataJoin join;
    
    private Class standardAssocationType;
    
    private String referenceComparisonRule;
    
    private MetadataPropertyImpl oppositeIndexProperty;
    
    private MetadataPropertyImpl oppositeKeyProperty;
    
    private MetadataPropertyImpl oppositeProperty;
    
    private MetadataPropertyImpl indexProperty;
    
    private MetadataPropertyImpl keyProperty;
    
    private MetadataPropertyImpl referenceProperty;
    
    private MetadataPropertyImpl covarianceProperty;
    
    private boolean inverse;
    
    private boolean overridding;
    
    private Unresolved unresolved;
    
    MetadataPropertyImpl(MetadataClassImpl declaringModelClass, Object memberNode) {
        this.declaringMetadataClass = declaringModelClass;
        this.unresolved = new Unresolved();
        this.name = 
                memberNode instanceof FieldNode ? 
                        ((FieldNode)memberNode).name : 
                        Context.propertyName(((MethodNode)memberNode)
                );
        this.attach(memberNode);
    }
    
    MetadataPropertyImpl(MetadataClassImpl declaringModelClass, MetadataPropertyImpl property, String columnName) {
        this.declaringMetadataClass = declaringModelClass;
        this.name = property.name;
        this.getterName = property.getterName;
        this.setterName = property.setterName;
        this.typeDescriptor = property.typeDescriptor;
        this.columnName = columnName;
        this.primaryAnnotationType = property.primaryAnnotationType;
        this.overridding = true;
        this.unresolved = property.unresolved.clone();
    }

    @Override
    public MetadataClass getDeclaringClass() {
        return this.declaringMetadataClass;
    }

    @Override
    public String getName() {
        return this.name;
    }
    
    @Override
    public AccessType getAccessType() {
        return this.accessType;
    }

    @Override
    public String getGetterName() {
        return this.getterName;
    }

    @Override
    public String getSetterName(boolean canReturnNull) {
        if (!canReturnNull && this.setterName == null) {
            String getterName = this.getGetterName();
            return "set" + getterName.substring(getterName.startsWith("is") ? 2 : 3);
        }
        return this.setterName;
    }
    
    @Override
    public String getTypeDescriptor() {
        return this.typeDescriptor;
    }
    
    @Override
    public String getKeyTypeDescriptor() {
        return this.keyTypeDescriptor;
    }

    public List getVisibleAnnotations() {
        return this.visibleAnnotations;
    }

    public List getInvisibleAnnotations() {
        return this.invisibleAnnotations;
    }

    @Override
    public String getColumnName() {
        return this.columnName;
    }

    @Override
    public Map getEmbeddedColumnNames() {
        return this.embeddedColumnNames;
    }
    
    @Override
    public boolean isOverriding() {
        return this.overridding;
    }

    @Override
    public boolean isBasic() {
        return this.primaryAnnotationType == Basic.class;
    }

    @Override
    public boolean isLob() {
        return this.lob;
    }

    @Override
    public boolean isId() {
        return this.primaryAnnotationType == Id.class || 
                this.primaryAnnotationType == EmbeddedId.class;
    }

    @Override
    public boolean isVersion() {
        return this.primaryAnnotationType == Version.class;
    }

    @Override
    public boolean isEmbedded() {
        return this.primaryAnnotationType == Embedded.class ||
                this.primaryAnnotationType == EmbeddedId.class;
    }

    @Override
    public boolean isAssociation() {
        return this.isReference() || this.isCollection();
    }

    @Override
    public boolean isReference() {
        return this.primaryAnnotationType == OneToOne.class ||
                this.primaryAnnotationType == ManyToOne.class ||
                this.any;
    }
    
    @Override
    public boolean isOneToOne() {
        return this.primaryAnnotationType == OneToOne.class;
    }
    
    @Override
    public boolean isManyToOne() {
        return this.primaryAnnotationType == ManyToOne.class;
    }
    
    @Override
    public boolean isAny() {
        return this.any;
    }

    @Override
    public boolean isCollection() {
        return this.primaryAnnotationType == OneToMany.class ||
                this.primaryAnnotationType == ManyToMany.class;
    }
    
    @Override
    public boolean isOneToMany() {
        return this.primaryAnnotationType == OneToMany.class;
    }
    
    @Override
    public boolean isManyToMany() {
        return this.primaryAnnotationType == ManyToMany.class;
    }

    @Override
    public FetchType getFetchType() {
        return this.fetchType;
    }

    @Override
    public MetadataClass getRelatedClass() {
        return this.relatedClass;
    }

    @Override
    public MetadataJoin getJoin() {
        return this.join;
    }

    @Override
    public Class getStandardAssocationType() {
        return this.standardAssocationType;
    }

    @Override
    public String getReferenceComparisonRule() {
        return this.referenceComparisonRule;
    }

    @Override
    public MetadataProperty getOppositeProperty() {
        return this.oppositeProperty;
    }
    
    @Override
    public MetadataProperty getOppositeIndexProperty() {
        return this.oppositeIndexProperty;
    }

    @Override
    public MetadataProperty getOppositeKeyProperty() {
        return this.oppositeKeyProperty;
    }

    @Override
    public MetadataProperty getIndexProperty() {
        return this.indexProperty;
    }

    @Override
    public MetadataProperty getKeyProperty() {
        return this.keyProperty;
    }

    @Override
    public MetadataProperty getReferenceProperty() {
        return this.referenceProperty;
    }

    @Override
    public MetadataProperty getCovarianceProperty() {
        return this.covarianceProperty;
    }

    @Override
    public boolean isInverse() {
        return this.inverse;
    }

    Object getPropertyNode() {
        return this.unresolved.propertyNode;
    }
    
    void attach(Object memberNode) {
        if (memberNode instanceof FieldNode) {
            this.unresolved.fieldNode = (FieldNode)memberNode;
        } else {
            this.unresolved.methodNode = (MethodNode)memberNode;
        }
    }
    
    void resolveSelf(Context context) {
        Unresolved unresolved = this.unresolved;
        AccessType fieldAccessType = accessType(unresolved.fieldNode);
        AccessType methodAccessType = accessType(unresolved.methodNode);
        if (fieldAccessType != null && methodAccessType != null && fieldAccessType != methodAccessType) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().conflictAccessTypes(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            Access.class
                    )
            );
        }
        AccessType accessType = 
                fieldAccessType != null ?
                fieldAccessType : (
                        methodAccessType != null ?
                        methodAccessType :
                        this.declaringMetadataClass.getAccessType()
                );
        if (accessType == AccessType.FIELD) {
            if (unresolved.fieldNode == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().noPropertyField(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AccessType.FIELD
                        )
                );
            }
            if (isJPAAnnotationPresent(unresolved.methodNode)) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().badMethodAnnotation(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AccessType.FIELD
                        )
                );
            }
            unresolved.propertyNode = unresolved.fieldNode;
        } else if (accessType == AccessType.PROPERTY) {
            if (unresolved.methodNode == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().noPropertyMethod(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AccessType.PROPERTY
                        )
                );
            }
            if (isJPAAnnotationPresent(unresolved.fieldNode)) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().badFieldAnnotation(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AccessType.PROPERTY
                        )
                );
            }
            unresolved.propertyNode = unresolved.methodNode;
        } else if (unresolved.fieldNode != null && unresolved.methodNode != null) {
            boolean useField = isJPAAnnotationPresent(unresolved.fieldNode);
            boolean useMethod = isJPAAnnotationPresent(unresolved.methodNode);
            if (useField && useMethod) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownAccessType(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AccessType.class
                        )
                );
            }
            if (useMethod) {
                unresolved.propertyNode = unresolved.methodNode;
            } else {
                unresolved.propertyNode = unresolved.fieldNode;   
            }
        } else {
            unresolved.propertyNode = 
                    unresolved.fieldNode != null ? 
                            unresolved.fieldNode : 
                            unresolved.methodNode;
        }
        this.accessType = unresolved.propertyNode == unresolved.fieldNode ? AccessType.FIELD : AccessType.PROPERTY;
        this.afterAccessTypeDetermined(context);
    }
    
    void resolveEmbedded(Context context) {
        this.resolveEmbedded(context, new LinkedHashSet());
    }

    @SuppressWarnings({ "unchecked", "rawtypes" })
    void resolveEmbedded(Context context, XOrderedSet resolvingQueue) {
        if (this.embeddedColumnNames != null || !this.isEmbedded()) {
            return;
        }
        if (!resolvingQueue.add(this)) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().embeddedCircular(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            Embedded.class,
                            resolvingQueue
                    )
            );
        }
        try {
            String descriptor = this.typeDescriptor;
            descriptor = descriptor.substring(1, descriptor.length() - 1).replace('/', '.');
            MetadataClassImpl embeddedClass = (MetadataClassImpl)context.getModelClasses().get(descriptor);
            if (embeddedClass == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownRelatedClass(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                Embedded.class,
                                descriptor
                        )
                );
            }
            if (!embeddedClass.isEmbeddable()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().embeddedTyepIsNotEmbeddable(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                Embedded.class,
                                embeddedClass.getName(),
                                Embeddable.class
                        )
                );
            }
            if (this.declaringMetadataClass.isJPAObjectModelInstrument() != embeddedClass.isJPAObjectModelInstrument()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().incongruousJPAObjectInstrument(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                embeddedClass.getName(),
                                JPAObjectModelInstrument.class
                        )
                );
            }
            this.relatedClass = embeddedClass;
            Map embeddedColumnNames = new LinkedHashMap<>();
            Map overrideMap = this.getOverrideMap(); 
            Map deeperProperties = 
                    (Map)(Map)embeddedClass.getProperties();
            if (!overrideMap.isEmpty() && !deeperProperties.keySet().containsAll(overrideMap.keySet())) {
                List unmatchedNames = new ArrayList<>(overrideMap.keySet());
                unmatchedNames.removeAll(deeperProperties.keySet());
                throw new MetadataException(
                        LAZY_RESOURCE.get().canNotOverrideDeepProperties(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AttributeOverride.class,
                                embeddedClass.getName(),
                                unmatchedNames
                        )
                );
            }
            for (MetadataPropertyImpl deeperProperty : deeperProperties.values()) {
                deeperProperty.resolveEmbedded(context, resolvingQueue);
                Map deeperEmbeddedColumnNames = deeperProperty.getEmbeddedColumnNames();
                if (deeperEmbeddedColumnNames != null) {
                    if (overrideMap.containsKey(deeperProperty.getName())) {
                        throw new MetadataException();
                    }
                    embeddedColumnNames.putAll(deeperEmbeddedColumnNames);
                } else {
                    String overrideColumnName = overrideMap.get(deeperProperty.getName());
                    if (overrideColumnName != null) {
                        embeddedColumnNames.put(deeperProperty, overrideColumnName);
                    } else {
                        embeddedColumnNames.put(deeperProperty, deeperProperty.getColumnName());
                    }
                }
            }
            this.embeddedColumnNames = embeddedColumnNames;
        } finally {
            resolvingQueue.remove(this);
        }
    }

    void resolveJoin(Context context) {
        if (this.covarianceProperty != null || !this.isAssociation()) {
            return;
        }
        Unresolved unresolved = this.unresolved;
        String thisTypeDescriptor = typeDescriptor;
        AnnotationNode annotationNode = Context.getAnnotationNode(unresolved.propertyNode, this.primaryAnnotationType);
        unresolved.mappedBy = Nulls.emptyToNull(Context.getAnnotationValue(annotationNode, "mappedBy"));
        this.decideTargetEntity(false, context);
        if (this.isCollection()) {
            if (thisTypeDescriptor.equals(ASM.getDescriptor(Map.class))) {
                if (Context.getAnnotationNode(unresolved.propertyNode, MapKeyJoinColumns.class) != null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().annotationIsNotSupportedInObjectModel4JPA(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    MapKeyJoinColumns.class
                            )
                    );
                }
                if (Context.getAnnotationNode(unresolved.propertyNode, MapKeyJoinColumn.class) != null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().annotationIsNotSupportedInObjectModel4JPA(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    MapKeyJoinColumn.class
                            )
                    );
                }
                AnnotationNode mapKeyNode = Context.getAnnotationNode(unresolved.propertyNode, MapKey.class);
                AnnotationNode mapKeyColumnNode = Context.getAnnotationNode(unresolved.propertyNode, MapKeyColumn.class);
                if (mapKeyNode != null && mapKeyColumnNode != null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().conflictAnnotations(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    MapKey.class,
                                    MapKeyColumn.class
                            )
                    );
                }
                if (mapKeyNode != null) {
                    unresolved.mapKey = Nulls.emptyToNull(Context.getAnnotationValue(mapKeyNode, "name"));
                    if (unresolved.mapKey == null) {
                        unresolved.mapKey = this.relatedClass.getIdProperty().getName();
                    }
                } else if (mapKeyColumnNode != null) {
                    unresolved.mapKeyColumn = Nulls.emptyToNull(Context.getAnnotationValue(mapKeyColumnNode, "name"));
                    if (unresolved.mapKeyColumn == null) {
                        unresolved.mapKeyColumn = this.relatedClass.getIdProperty().getName();
                    }
                }
            } else if (typeDescriptor.equals(ASM.getDescriptor(List.class))) {
                AnnotationNode orderColumnNode = Context.getAnnotationNode(unresolved.propertyNode, OrderColumn.class);
                if (orderColumnNode == null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().listRequiresOrderColumnInObjectModel4JPA(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    List.class,
                                    OrderColumn.class
                            )
                    );
                }
                unresolved.orderColumn = Nulls.emptyToNull(Context.getAnnotationValue(orderColumnNode, "name"));
                if (unresolved.orderColumn == null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().listRequiresOrderColumnValueInObjectModel4JPA(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    OrderColumn.class
                            )
                    );
                }
            }
        }
        this.validateJoin();
        this.join = this.createEntityJoin();
    }
    
    void resolveExplicitOppositeProperty(Context context) {
        if (this.covarianceProperty != null) {
            return;
        }
        if (this.unresolved.mapKey != null) {
            MetadataPropertyImpl oppositeKeyProperty = 
                    (MetadataPropertyImpl)
                    this
                    .relatedClass
                    .getDeclaredProperties()
                    .get(this.unresolved.mapKey);
            if (oppositeKeyProperty == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownRelatedMapKey(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                MapKey.class,
                                this.unresolved.mapKey,
                                this.relatedClass.getName()
                        )
                );
            }
            if (oppositeKeyProperty.isBasic() &&
                    !oppositeKeyProperty.isEmbedded() &&
                    !oppositeKeyProperty.isId()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().relatedMapKeyMustBe(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                MapKey.class,
                                this.unresolved.mapKey,
                                this.relatedClass.getName(),
                                new Class[] { Basic.class, Id.class, Embedded.class }
                        )
                );
            }
            if (!oppositeKeyProperty.getTypeDescriptor().equals(this.keyTypeDescriptor)) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().illlegalRelatedMapKeyType(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                MapKey.class,
                                this.unresolved.mapKey,
                                this.relatedClass.getName(),
                                this.keyTypeDescriptor,
                                oppositeKeyProperty.getTypeDescriptor()
                        )
                );
            }
            this.oppositeKeyProperty = oppositeKeyProperty;
        }
        if (this.unresolved.mapKeyColumn != null) {
            String mapKeyColumn = this.unresolved.mapKeyColumn;
            for (MetadataProperty targetEntityProperty : this.relatedClass.getDeclaredProperties().values()) {
                if (mapKeyColumn.equals(targetEntityProperty.getColumnName())) {
                    if (!targetEntityProperty.getTypeDescriptor().equals(this.keyTypeDescriptor)) {
                        throw new MetadataException(
                                LAZY_RESOURCE.get().illlegalRelatedMapKeyColumnType(
                                        this.declaringMetadataClass.getName(),
                                        this.name,
                                        MapKeyColumn.class,
                                        this.unresolved.mapKeyColumn,
                                        this.relatedClass.getName(),
                                        this.keyTypeDescriptor,
                                        oppositeKeyProperty.getTypeDescriptor()
                                )
                        );
                    }
                    this.oppositeKeyProperty = (MetadataPropertyImpl)targetEntityProperty;
                    break;
                }
            }
            if (this.oppositeKeyProperty == null && this.unresolved.mappedBy != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownRelatedMapKeyColumn(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                MapKeyColumn.class,
                                this.unresolved.mapKeyColumn,
                                this.primaryAnnotationType,
                                "mappedBy",
                                this.relatedClass.getName()
                        )
                );
            }
        }
        if (this.unresolved.orderColumn != null) {
            String orderColumn = this.unresolved.orderColumn;
            for (MetadataProperty targetEntityProperty : this.relatedClass.getDeclaredProperties().values()) {
                if (orderColumn.equals(targetEntityProperty.getColumnName())) {
                    if (!targetEntityProperty.getTypeDescriptor().equals("I")) {
                        throw new MetadataException(
                                LAZY_RESOURCE.get().illlegalRelatedOrderColumnType(
                                        this.declaringMetadataClass.getName(),
                                        this.name,
                                        OrderColumn.class,
                                        this.unresolved.orderColumn,
                                        this.relatedClass.getName()
                                )
                        );
                    }
                    this.oppositeIndexProperty = (MetadataPropertyImpl)targetEntityProperty;
                    break;
                }
            }
            if (this.oppositeIndexProperty == null && this.unresolved.mappedBy != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownRelatedOrderColumn(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                OrderColumn.class,
                                this.unresolved.orderColumn,
                                this.primaryAnnotationType,
                                "mappedBy",
                                this.relatedClass.getName()
                        )
                );
            }
        }
        if (this.unresolved.mappedBy != null) {
            MetadataPropertyImpl oppositeProperty = 
                    (MetadataPropertyImpl)
                    this
                    .relatedClass
                    .getDeclaredProperties()
                    .get(this.unresolved.mappedBy);
            if (oppositeProperty == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unknownMappedBy(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                this.primaryAnnotationType,
                                this.unresolved.mappedBy,
                                this.relatedClass.getName()
                        )
                );
            }
            if (!oppositeProperty.isAssociation()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().mappedByIsNotAssociation(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                this.primaryAnnotationType,
                                this.unresolved.mappedBy,
                                oppositeProperty.declaringMetadataClass.getName()
                        )
                );
            }
            if (oppositeProperty.relatedClass != this.declaringMetadataClass) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().mappedByOwnerIsWrong(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                this.primaryAnnotationType,
                                this.unresolved.mappedBy,
                                oppositeProperty.getDeclaringClass().getName()
                        )
                );
            }
            if (oppositeProperty.unresolved.mappedBy != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().targetOfMappedByCanNotUseMappedBy(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                this.primaryAnnotationType,
                                this.unresolved.mappedBy,
                                oppositeProperty.getDeclaringClass().getName(),
                                oppositeProperty.primaryAnnotationType
                        )
                );
            }
            if (oppositeProperty.oppositeProperty != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().conflictBidirectionalAssociation(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                this.primaryAnnotationType,
                                this.unresolved.mappedBy,
                                oppositeProperty.getDeclaringClass().getName(),
                                oppositeProperty.oppositeProperty.getDeclaringClass().getName(),
                                oppositeProperty.oppositeProperty.getName()
                        )
                );
            }
            this.oppositeProperty = oppositeProperty;
            oppositeProperty.oppositeProperty = this;
            this.inverse = true;
        }
    }
    
    void resolveImplicitOppositeProperty(Context context) {
        if (this.covarianceProperty != null) {
            return;
        }
        if (!this.isAssociation() || this.oppositeProperty != null) {
            return;
        }
        MetadataPropertyImpl oppositeProperty = null;
        MetadataJoin reversedJoin = this.join.reversedJoin();
        for (MetadataProperty metadataProperty : this.relatedClass.getDeclaredProperties().values()) {
            if (reversedJoin.equals(metadataProperty.getJoin())) {
                oppositeProperty = (MetadataPropertyImpl)metadataProperty;
                break;
            }
        }
        if (oppositeProperty == null) {
            if (this.isCollection()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().unidirectionalCanNotBeCollection(
                                this.declaringMetadataClass.getName(),
                                this.name
                        )
                );
            }
            return;
        }
        if (oppositeProperty.oppositeProperty != null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().conflictBidirectionalAssociation(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            this.primaryAnnotationType,
                            this.unresolved.mappedBy,
                            oppositeProperty.declaringMetadataClass.getName(),
                            oppositeProperty.oppositeProperty.getDeclaringClass().getName(),
                            oppositeProperty.oppositeProperty.getName()
                    )
            );
        }
        Boolean inverse = isInverse(this.join);
        if (inverse == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().canNotCollectInverse(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            this.join
                    )
            );
        }
        Boolean oppositeInverse = isInverse(oppositeProperty.getJoin());
        if (oppositeInverse == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().canNotCollectInverse(
                            oppositeProperty.declaringMetadataClass.getName(),
                            oppositeProperty.name,
                            oppositeProperty.join
                    )
            );
        }
        if (inverse.booleanValue() == oppositeInverse.booleanValue()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().implicitInverseMustBeDifferent(
                            this.declaringMetadataClass.getName(), 
                            this.name,
                            this.inverse,
                            oppositeProperty.getDeclaringClass().getName(),
                            oppositeProperty.getName(),
                            oppositeProperty.isInverse()
                    )
            );
        }
        if (inverse && this.isCollection()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().inverseCollectionMustUseMappedBy(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            this.primaryAnnotationType
                    )
            );
        }
        if (oppositeInverse && oppositeProperty.isCollection()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().inverseCollectionMustUseMappedBy(
                            oppositeProperty.declaringMetadataClass.getName(),
                            oppositeProperty.name,
                            oppositeProperty.primaryAnnotationType
                    )
            );
        }
        this.inverse = inverse;
        oppositeProperty.inverse = oppositeInverse;
        this.oppositeProperty = oppositeProperty;
        oppositeProperty.oppositeProperty = this;
    }
    
    void resolveReference(Context context) {
        if (this.covarianceProperty != null || !this.isReference()) {
            return;
        }
        this.standardAssocationType = Reference.class;
        MetadataPropertyImpl oppositeProperty = this.oppositeProperty;
        if (oppositeProperty != null) {
            if (oppositeProperty.oppositeIndexProperty != null) {
                this.standardAssocationType = IndexedReference.class;
                oppositeProperty.oppositeIndexProperty.referenceProperty = this;
                this.indexProperty = oppositeProperty.oppositeIndexProperty;
            } else if (oppositeProperty.oppositeKeyProperty != null) {
                this.standardAssocationType = KeyedReference.class;
                oppositeProperty.oppositeKeyProperty.referenceProperty = this;
                this.keyProperty = oppositeProperty.oppositeKeyProperty;
                this.keyTypeDescriptor = oppositeProperty.keyTypeDescriptor;
            }
        }
    }

    @SuppressWarnings("unchecked")
    void resolveContravariance(Context context) {
        if (this.covarianceProperty != null) {
            return;
        }
        AnnotationNode contravarianceInstrumentNode = Context.getAnnotationNode(
                this.unresolved.propertyNode, 
                ContravarianceInstrument.class
        );
        if (contravarianceInstrumentNode == null) {
            return;
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, Transient.class) == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().annotationCanOnlyBeUsedWith(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            ContravarianceInstrument.class, 
                            Transient.class
                    )
            );
        }
        MetadataClass superMetadataClass = this.declaringMetadataClass.getSuperMetadataClass();
        if (superMetadataClass == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().contravarianceRequiresSuperMetaClass(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            ContravarianceInstrument.class
                    )
            );
        }
        String value = Context.getAnnotationValue(
                contravarianceInstrumentNode, 
                "value"
        );
        if (Nulls.isNullOrEmpty(value)) {
            value = this.name;
        }
        MetadataPropertyImpl covarianceProperty = (MetadataPropertyImpl)superMetadataClass.getProperties().get(value);
        if (covarianceProperty == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().noCovarianceProperty(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            ContravarianceInstrument.class, 
                            value, 
                            superMetadataClass.getName()
                    )
            );
        }
        
        covarianceProperty.resolveContravariance(context);
        if (!covarianceProperty.isAssociation()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().covariancePropertyMustBeAssociation(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            ContravarianceInstrument.class, 
                            value, 
                            covarianceProperty.declaringMetadataClass.getName()
                    )
            );
        }
        if (covarianceProperty.isCollection() && !covarianceProperty.typeDescriptor.equals(this.typeDescriptor)) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().illegalCovariancePropertyType(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            ContravarianceInstrument.class, 
                            value, 
                            covarianceProperty.declaringMetadataClass.getName(),
                            this.typeDescriptor,
                            covarianceProperty.typeDescriptor
                    )
            );
        }
        
        this.covarianceProperty = covarianceProperty;
        this.primaryAnnotationType = covarianceProperty.primaryAnnotationType;
        this.any = covarianceProperty.any;
        this.fetchType = this.covarianceProperty.fetchType;
        this.inverse = this.covarianceProperty.inverse;
        this.standardAssocationType = this.covarianceProperty.standardAssocationType;
        this.keyProperty = this.covarianceProperty.keyProperty;
        this.indexProperty = this.covarianceProperty.indexProperty;
        this.keyTypeDescriptor = this.covarianceProperty.keyTypeDescriptor;
        this.oppositeProperty = this.covarianceProperty.oppositeProperty;
        this.oppositeKeyProperty = this.covarianceProperty.oppositeKeyProperty;
        this.oppositeIndexProperty = this.covarianceProperty.oppositeIndexProperty;
        this.join = this.covarianceProperty.join;
        this.decideTargetEntity(true, context);
    }

    void finishResolving() {
        this.visibleAnnotations = this.visibleAnnotations == null ?
                MACollections.emptyList() :
                MACollections.unmodifiable(this.visibleAnnotations);
        this.invisibleAnnotations = this.invisibleAnnotations == null ?
                MACollections.emptyList() :
                MACollections.unmodifiable(this.invisibleAnnotations);
        this.embeddedColumnNames = this.embeddedColumnNames == null ?
                MACollections.emptyMap() :
                MACollections.unmodifiable(this.embeddedColumnNames);
        this.unresolved = null;
    }

    @SuppressWarnings("unchecked")
    private void afterAccessTypeDetermined(Context context) {
        if (this.accessType == AccessType.FIELD) {
            this.typeDescriptor = this.unresolved.fieldNode.desc;
            this.unresolved.typeSignature = this.unresolved.fieldNode.signature;
        } else {
            String desc = this.unresolved.methodNode.desc;
            this.typeDescriptor = desc.substring(desc.lastIndexOf(')') + 1);
            String signature = this.unresolved.methodNode.signature;
            if (signature != null) {
                this.unresolved.typeSignature = signature.substring(signature.lastIndexOf(')') + 1);
            }
        }
        if (unresolved.methodNode != null) {
            this.getterName = unresolved.methodNode.name;
        } else {
            String getterName = unresolved.fieldNode.name;
            getterName = (unresolved.fieldNode.desc.equals("Z") ? "is" : "get") + Character.toUpperCase(getterName.charAt(0)) + getterName.substring(1);
            this.getterName = getterName;
        }
        this.visibleAnnotations = createAnnotations(Context.getVisibleAnnotations(this.unresolved.propertyNode));
        this.invisibleAnnotations = createAnnotations(Context.getInvisibleAnnotations(this.unresolved.propertyNode));
        if (this.declaringMetadataClass.getClassNode().methods != null) {
            String name = "set" + this.getterName.substring(this.getterName.startsWith("is") ? 2 : 3);
            String desc = '(' + this.typeDescriptor + ")V";
            for (MethodNode methodNode : (List)this.declaringMetadataClass.getClassNode().methods) {
                if (methodNode.name.equals(name) && methodNode.desc.equals(desc)) {
                    this.setterName = name;
                    break;
                }
            }
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, Id.class) != null) {
            this.setPrimaryAnnotationType(Id.class);
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, EmbeddedId.class) != null) {
            this.setPrimaryAnnotationType(EmbeddedId.class);
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, Version.class) != null) {
            this.setPrimaryAnnotationType(Version.class);
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, Basic.class) != null) {
            this.setPrimaryAnnotationType(Basic.class);
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, Basic.class));
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, Embedded.class) != null) {
            this.setPrimaryAnnotationType(Embedded.class);
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, ElementCollection.class) != null) {
            this.setPrimaryAnnotationType(ElementCollection.class);
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, OneToOne.class) != null) {
            this.setPrimaryAnnotationType(OneToOne.class);
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, OneToOne.class));
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, ManyToOne.class) != null) {
            this.setPrimaryAnnotationType(ManyToOne.class);
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, ManyToOne.class));
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, OneToMany.class) != null) {
            this.setPrimaryAnnotationType(OneToMany.class);
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, OneToMany.class));
        }
        if (Context.getAnnotationNode(this.unresolved.propertyNode, ManyToMany.class) != null) {
            this.setPrimaryAnnotationType(ManyToMany.class);
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, ManyToMany.class));
        }
        if (context.getAnyAnnotationType() != null && 
                Context.getAnnotationNode(
                        this.unresolved.propertyNode, 
                        context.getAnyAnnotationType()
                ) != null) {
            this.setPrimaryAnnotationType(context.getAnyAnnotationType());
            this.setFetchType(Context.getAnnotationNode(this.unresolved.propertyNode, context.getAnyAnnotationType()));
            this.any = true;
        }
        if (this.primaryAnnotationType == null) {
            this.primaryAnnotationType = Basic.class;
        }
        if (this.fetchType == null) {
            this.fetchType = FetchType.EAGER;
        }
        
        this.lob = Context.getAnnotationNode(this.unresolved.propertyNode, Lob.class) != null;
        if (this.lob && this.primaryAnnotationType != Basic.class) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().conflictAnnotations(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            Lob.class,
                            this.primaryAnnotationType 
                    )
            );
        }
        if (this.fetchType == FetchType.EAGER) {
            if (this.primaryAnnotationType == ManyToOne.class && !this.declaringMetadataClass.isEagerReferenceAllowed()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().mustNotBeEager(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                FetchType.EAGER,
                                JPAObjectModelInstrument.class,
                                "allowEagerReferences",
                                FetchType.LAZY
                        )
                );
            }
            if (this.lob && !this.declaringMetadataClass.isEagerLobAllowed()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().mustNotBeEager(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                FetchType.EAGER,
                                JPAObjectModelInstrument.class,
                                "allowEagerLobs",
                                FetchType.LAZY
                        )
                );
            }
        }
        
        AnnotationNode columnNode = Context.getAnnotationNode(this.unresolved.propertyNode, Column.class);
        if (this.primaryAnnotationType == Basic.class ||
                this.primaryAnnotationType == Id.class ||
                this.primaryAnnotationType == Version.class) {
            if (columnNode != null) {
                this.columnName = Nulls.emptyToNull(Context.getAnnotationValue(columnNode, "name"));
                if (this.columnName == null) {
                    this.columnName = name;
                }
            }
        } else if (columnNode != null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().annotationCanOnlyBeUsedWith(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            Column.class,
                            new Class[] { Basic.class, Id.class, Version.class }
                    )               
            );
        }
        if (!this.isEmbedded()) {
            if (Context.getAnnotationNode(this.unresolved.propertyNode, AttributeOverride.class) != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().annotationCanOnlyBeUsedWith(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AttributeOverride.class,
                                Embedded.class
                        )
                );
            } 
            if (Context.getAnnotationNode(this.unresolved.propertyNode, AttributeOverrides.class) != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().annotationCanOnlyBeUsedWith(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                AttributeOverrides.class,
                                Embedded.class
                        ));
            }
        }
        
        AnnotationNode referenceComparsionInstrumentNode = 
                Context.getAnnotationNode(
                        this.unresolved.propertyNode, 
                        ReferenceComparisonRuleInstrument.class
                );
        if (referenceComparsionInstrumentNode != null) {
            if (!this.isCollection()) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().annotationCanOnlyBeUsedWith(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                ReferenceComparisonRuleInstrument.class, 
                                OneToMany.class,
                                ManyToMany.class)
                );
            }
            this.referenceComparisonRule = 
                    Joins.join(
                            Context.>getAnnotationValue(
                                    referenceComparsionInstrumentNode, 
                                    "value"
                            )
                    )
                    .trim();
        }
        
        AnnotationNode navigableInstrumentNode =
                Context.getAnnotationNode(
                        this.unresolved.propertyNode, 
                        NavigableInstrument.class
                );
        if (navigableInstrumentNode != null) {
            String desc;
            if (this.unresolved.propertyNode == this.unresolved.fieldNode) {
                desc = this.unresolved.fieldNode.desc;
            } else {
                int index = this.unresolved.methodNode.desc.lastIndexOf(')');
                desc = this.unresolved.methodNode.desc.substring(index + 1);
            }
            if (!desc.equals(ASM.getDescriptor(Set.class)) && !desc.equals(ASM.getDescriptor(Map.class))) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().annotationRequiresAnyOneType(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                NavigableInstrument.class,
                                Set.class,
                                Map.class
                        )
                );
            }
            this.unresolved.navigableCollection = true;
        }
    }
    
    @SuppressWarnings("unchecked")
    private static List createAnnotations(List annotationNodes) {
        if (Nulls.isNullOrEmpty(annotationNodes)) {
            return MACollections.emptyList();
        }
        List visibleAnnotations = new ArrayList<>(annotationNodes.size());
        for (AnnotationNode annotationNode : (List)annotationNodes) {
            visibleAnnotations.add(new ModelAnnotationImpl(annotationNode));
        }
        return visibleAnnotations;
    }
    
    private void decideTargetEntity(boolean contravariance, Context context) {
        String targetEntity = null;
        if (!contravariance) {
            AnnotationNode annotationNode = Context.getAnnotationNode(unresolved.propertyNode, this.primaryAnnotationType);
            targetEntity = Nulls.emptyToNull(Context.getAnnotationValue(annotationNode, "targetEntity"));
        }
        if (targetEntity == null) {
            String thisTypeDescriptor = this.typeDescriptor;
            if (this.isReference()) {
                if (!thisTypeDescriptor.startsWith("L") || !thisTypeDescriptor.endsWith(";")) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().typeDescriptorMustBeClass(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    thisTypeDescriptor
                            )
                    );
                }
                targetEntity = thisTypeDescriptor.substring(1, thisTypeDescriptor.length() - 1).replace('/', '.');
            } else {
                if (thisTypeDescriptor.equals(ASM.getDescriptor(Collection.class))) {
                    this.standardAssocationType = Collection.class;
                } else if (thisTypeDescriptor.equals(ASM.getDescriptor(Set.class))) {
                    this.standardAssocationType = this.unresolved.navigableCollection ? NavigableSet.class : Set.class;
                } else if (thisTypeDescriptor.equals(ASM.getDescriptor(List.class))) {
                    this.standardAssocationType = List.class;
                } else if (thisTypeDescriptor.equals(ASM.getDescriptor(Map.class))) {
                    this.standardAssocationType = this.unresolved.navigableCollection ? NavigableMap.class : Map.class;
                } else {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().badCollectionType(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    this.primaryAnnotationType,
                                    new Class[] { 
                                        Collection.class,
                                        List.class,
                                        Set.class,
                                        Map.class 
                                    }
                            )
                    );
                }
                if (unresolved.typeSignature == null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().collectionMustBeGenericType(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    this.primaryAnnotationType
                            )
                    );
                }
                String[] typeArguments = this.getTypeArguments(unresolved.typeSignature);
                if (typeArguments.length == 2) {
                    this.keyTypeDescriptor = 'L' + typeArguments[0].replace('.', '/') + ';';
                }
                targetEntity = typeArguments[typeArguments.length - 1];
            }
        }
        this.relatedClass = (MetadataClassImpl)context.getModelClasses().get(targetEntity);
        if (this.relatedClass == null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().unknownRelatedClass(
                            this.getDeclaringClass().getName(), 
                            this.name, 
                            this.primaryAnnotationType, 
                            targetEntity
                    )
            );
        }
        if (!this.relatedClass.isEntity()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().relatedClassMustBeEntity(
                            this.getDeclaringClass().getName(), 
                            this.name, 
                            this.primaryAnnotationType, 
                            targetEntity,
                            Entity.class
                    )
            );
        }
        if (this.declaringMetadataClass.isJPAObjectModelInstrument() != this.relatedClass.isJPAObjectModelInstrument()) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().incongruousJPAObjectInstrument(
                            this.getDeclaringClass().getName(),
                            this.name, 
                            this.relatedClass.getName(), 
                            JPAObjectModelInstrument.class
                    )
            );
        }
    }
    
    private Map getOverrideMap() {
        Map map = new HashMap<>();
        AnnotationNode attributeOverrideNode = Context.getAnnotationNode(this.unresolved.propertyNode, AttributeOverride.class);
        AnnotationNode attributeOverridesNode = Context.getAnnotationNode(this.unresolved.propertyNode, AttributeOverrides.class);
        if (attributeOverrideNode == null && attributeOverridesNode == null) {
            return MACollections.emptyMap();
        }
        if (attributeOverrideNode != null && attributeOverridesNode != null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().conflictAnnotations(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            AttributeOverride.class,
                            AttributeOverrides.class
                    )
            );
        }
        List attributeOverrideNodes;
        if (attributeOverrideNode != null) {
            attributeOverrideNodes = MACollections.wrap(attributeOverrideNode);
        } else {
            attributeOverrideNodes = Context.getAnnotationValue(attributeOverridesNode, "value");
        }
        for (AnnotationNode attributeOverrideNode_ : attributeOverrideNodes) {
            String key = Context.getAnnotationValue(attributeOverrideNode_, "name");
            AnnotationNode columnNode = Context.getAnnotationValue(attributeOverrideNode_, "column");
            String value = Nulls.emptyToNull(Context.getAnnotationValue(columnNode, "name"));
            if (value == null) {
                value = key;
            }
            map.put(key, value);
        }
        return map;
    }

    private void validateJoin() {
        Object propertyNode = this.unresolved.propertyNode;
        Class joinAnnotationType = null; 
        if (Context.getAnnotationNode(propertyNode, JoinColumn.class) != null) {
            joinAnnotationType = JoinColumn.class;
        }
        if (Context.getAnnotationNode(propertyNode, JoinColumns.class) != null) {
            if (joinAnnotationType != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().conflictAnnotations(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                joinAnnotationType, 
                                JoinColumns.class)
                );
            }
            joinAnnotationType = JoinColumns.class;
        }
        if (Context.getAnnotationNode(propertyNode, JoinTable.class) != null) {
            if (joinAnnotationType != null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().conflictAnnotations(
                                this.declaringMetadataClass.getName(), 
                                this.name, 
                                joinAnnotationType, 
                                JoinTable.class)
                );
            }
            joinAnnotationType = JoinTable.class;
        }
        if (joinAnnotationType != null && this.unresolved.mappedBy != null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().canNotUseMappedByWith(
                            this.declaringMetadataClass.getName(),
                            this.name,
                            this.primaryAnnotationType,
                            joinAnnotationType
                    )
            );
        }
    }
    
    private MetadataJoin createEntityJoin() {
        
        MetadataClassImpl thisEntity = this.declaringMetadataClass;
        MetadataClassImpl targetEntity = this.relatedClass;

        AnnotationNode singleJoinColumnNode = Context.getAnnotationNode(this.unresolved.propertyNode, JoinColumn.class);
        if (singleJoinColumnNode != null) {
            String name = Nulls.emptyToNull(Context.getAnnotationValue(singleJoinColumnNode, "name"));
            String referencedColumnName = Nulls.emptyToNull(Context.getAnnotationValue(singleJoinColumnNode, "referencedColumnName"));
            String parentPKColumnName = null;
            if (name == null || referencedColumnName == null) {
                MetadataProperty idProperty = this.isReference() ? targetEntity.getIdProperty() : thisEntity.getIdProperty();
                if (idProperty.isEmbedded() && idProperty.getEmbeddedColumnNames().size() > 1) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().illegalJoinColumnOnMultipColumns(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    JoinColumn.class,
                                    idProperty.getDeclaringClass().getName(),
                                    idProperty.getName()
                            )
                    );
                }
                parentPKColumnName = idProperty.isEmbedded() ? idProperty.getName() : "";
            }
            if (name == null) {
                name = (this.isReference() ? this.name : thisEntity.getName()) + '_' + parentPKColumnName;
            }
            if (referencedColumnName == null) {
                referencedColumnName = parentPKColumnName;
            }
            return new MetadataJoinImpl(
                    MACollections.wrap(
                            new MetadataJoinImpl.ColumnImpl(
                                    name, 
                                    referencedColumnName,
                                    Context.getAnnotationValue(singleJoinColumnNode, "insertable"),
                                    Context.getAnnotationValue(singleJoinColumnNode, "updatable")
                            )
                    )
            );
        }
        
        AnnotationNode joinColumnsNode = Context.getAnnotationNode(this.unresolved.propertyNode, JoinColumns.class);
        if (joinColumnsNode != null) {
            List joinColumnNodes = Nulls.emptyToNull(Context.>getAnnotationValue(joinColumnsNode, "value"));
            if (joinColumnNodes == null) {
                throw new MetadataException(
                        LAZY_RESOURCE.get().emptyJoinColumns(
                                this.declaringMetadataClass.getName(),
                                this.name,
                                JoinColumns.class,
                                JoinColumn.class
                        )
                );
            }
            List columns = this.createJoinColumnWithoutDefaultValue(joinColumnNodes);
            return new MetadataJoinImpl(columns);
        }
        
        AnnotationNode joinTableNode = Context.getAnnotationNode(this.unresolved.propertyNode, JoinTable.class);
        String tableName = null;
        List columns = null;
        List inverseColumns = null;
        if (joinTableNode != null) {
            tableName = Nulls.emptyToNull(Context.getAnnotationValue(joinTableNode, "name"));
            List joinColumnNodes = Nulls.emptyToNull(Context.>getAnnotationValue(joinTableNode, "joinColumns"));
            if (joinColumnNodes != null) {
                columns = this.createJoinColumnWithoutDefaultValue(joinColumnNodes);
            }
            List inverseJoinColumnNodes = Nulls.emptyToNull(Context.>getAnnotationValue(joinTableNode, "inverseJoinColumns"));
            if (inverseJoinColumnNodes != null) {
                inverseColumns = this.createJoinColumnWithoutDefaultValue(inverseJoinColumnNodes);
            }
        }
        if (tableName == null) {
            tableName = thisEntity.getTableName() + '_' + targetEntity.getTableName();
        }
        if (columns == null) {
            columns = this.createJoinColumnWithDefaultValue(false);
        }
        if (inverseColumns == null) {
            inverseColumns = this.createJoinColumnWithDefaultValue(true);
        }
        return new MetadataJoinImpl(tableName, columns, inverseColumns);
    }
    
    private List createJoinColumnWithoutDefaultValue(List joinColumnNodes) {
        List columns = new ArrayList<>();
        if (joinColumnNodes != null) {
            for (AnnotationNode joinColumnNode : joinColumnNodes) {
                String name = Nulls.emptyToNull(Context.getAnnotationValue(joinColumnNode, "name"));
                if (name == null) {
                    throw new MetadataException(
                            LAZY_RESOURCE.get().emptyJonColumnName(
                                    this.declaringMetadataClass.getName(),
                                    this.name,
                                    JoinColumn.class
                            )
                    );
                }
                String referencedColumnName = Nulls.emptyToNull(Context.getAnnotationValue(joinColumnNode, "referencedColumnName"));
                if (referencedColumnName == null) {
                    referencedColumnName = this.relatedClass.getIdProperty().getColumnName();
                    if (referencedColumnName == null) {
                        throw new MetadataException();
                    }
                }
                columns.add(
                        new MetadataJoinImpl.ColumnImpl(
                                name, 
                                referencedColumnName,
                                Context.getAnnotationValue(joinColumnNode, "insertable"),
                                Context.getAnnotationValue(joinColumnNode, "updatable")
                        )
                );
            }
        }
        return columns;
    }
    
    private List createJoinColumnWithDefaultValue(boolean inverse) {
        MetadataClass metadataClass;
        boolean isReference;
        if (inverse) {
            metadataClass = this.relatedClass;
            isReference = false;
        } else {
            metadataClass = this.declaringMetadataClass;
            isReference = this.primaryAnnotationType == OneToOne.class || this.primaryAnnotationType == ManyToOne.class;
        }
        Collection idColumns;
        if (metadataClass.getIdProperty().isEmbedded()) {
            idColumns = metadataClass.getIdProperty().getEmbeddedColumnNames().values();
        } else {
            idColumns = MACollections.wrap(metadataClass.getIdProperty().getColumnName());
        }
        List columns = new ArrayList<>();
        String prefix = (isReference ? this.name : metadataClass.getTableName()) + '_';
        for (String idColumn : idColumns) {
            columns.add(
                    new MetadataJoinImpl.ColumnImpl(
                            prefix + idColumn, 
                            idColumn,
                            null,
                            null
                    )
            );
        }
        return columns;
    }

    private static AccessType accessType(Object memberNode) {
        if (memberNode != null && Context.getVisibleAnnotations(memberNode) != null) {
            for (AnnotationNode annotationNode : Context.getVisibleAnnotations(memberNode)) {
                if (annotationNode.desc.equals(ASM.getDescriptor(Access.class))) {
                    return AccessType.valueOf(Context.getAnnotationValue(annotationNode, "value")[1]);
                }
            }
        }
        return null;
    }

    private static boolean isJPAAnnotationPresent(Object memberNode) {
        if (memberNode != null && Context.getVisibleAnnotations(memberNode) != null) {
            for (AnnotationNode annotationNode : Context.getVisibleAnnotations(memberNode)) {
                if (annotationNode.desc.startsWith("Ljavax/persistence/")) {
                    return true;
                }
            }
        }
        return false;
    }
    
    private String[] getTypeArguments(String signature) {
        // SignatureReader is too complex...
        int depth = 0;
        int sigLen = signature.length();
        int len = 0;
        char[] arr = new char[signature.length()];
        for (int i = 0; i < sigLen; i++) {
            char c = signature.charAt(i);
            if (c == '<') {
                depth++;
            } else if (c == '>') {
                depth--;
            } else if (depth == 1) {
                arr[len++] = c == '/' ? '.' : c;
            }
        }
        if (arr[0] != 'L') {
            throw new MetadataException(
                    LAZY_RESOURCE.get().typeDescriptorMustBeClass(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            this.typeDescriptor
                    )
            );
        }
        int speratorIndex = -1;
        for (int i = 0; i < len; i++) {
            if (arr[i] == ';') {
                speratorIndex = i;
                break;
            }
        }
        if (speratorIndex + 1 == len) {
            return new String[] { new String(arr, 1, len - 2) };
        }
        String[] pair = new String[2];
        pair[0] = new String(arr, 1, speratorIndex - 1);
        if (arr[speratorIndex + 1] != 'L' || arr[len - 1] != ';') {
            throw new MetadataException(
                    LAZY_RESOURCE.get().typeArgumentMustBeClass(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            this.typeDescriptor,
                            new String(arr, speratorIndex + 1, len)
                    )
            );
        }
        pair[1] = new String(arr, speratorIndex + 2, len - speratorIndex - 3);
        return pair;
    }
    
    private void setPrimaryAnnotationType(Class primaryAnnotationType) {
        if (this.primaryAnnotationType != null) {
            throw new MetadataException(
                    LAZY_RESOURCE.get().conflictAnnotations(
                            this.declaringMetadataClass.getName(), 
                            this.name, 
                            this.primaryAnnotationType, 
                            primaryAnnotationType)
            );
        }
        this.primaryAnnotationType = primaryAnnotationType;
    }
    
    private void setFetchType(AnnotationNode annotationNode) {
        String[] values = Context.getAnnotationValue(annotationNode, "fetch");
        if (values != null) {
            this.fetchType = FetchType.valueOf(values[1]);
        }
    }
    
    private static Boolean isInverse(MetadataJoin join) {
        Boolean inverse = null;
        for (MetadataJoin.Column column : join.getColumns().values()) {
            if ((inverse = collectInverse(inverse, !column.isInsertable())) == null) {
                return null;
            }
            if ((inverse = collectInverse(inverse, !column.isUpdatable())) == null) {
                return null;
            }
        }
        if (join.getInverseColumns() != null) {
            for (MetadataJoin.Column inverseColumn : join.getInverseColumns().values()) {
                if ((inverse = collectInverse(inverse, !inverseColumn.isInsertable())) == null) {
                    return null;
                }
                if ((inverse = collectInverse(inverse, !inverseColumn.isUpdatable())) == null) {
                    return null;
                }
            }
        }
        return inverse;
    }
    
    private static Boolean collectInverse(Boolean inverse, boolean newInverse) {
        if (inverse == null) {
            return newInverse;
        }
        if (inverse.booleanValue() != newInverse) {
            return null;
        }
        return inverse;
    }

    private static class Unresolved implements Cloneable {
        FieldNode fieldNode;
        MethodNode methodNode;
        Object propertyNode;
        String typeSignature;
        String mappedBy;
        String mapKey;
        String mapKeyColumn;
        String orderColumn;
        boolean navigableCollection;
        @Override
        public Unresolved clone() {
            Unresolved cloned = new Unresolved();
            cloned.fieldNode = this.fieldNode;
            cloned.methodNode = this.methodNode;
            cloned.propertyNode = this.propertyNode;
            cloned.typeSignature = this.typeSignature;
            cloned.mappedBy = this.mappedBy;
            cloned.mapKey = this.mapKey;
            cloned.mapKeyColumn = this.mapKeyColumn;
            cloned.orderColumn = this.orderColumn;
            return cloned;
        }
    }
    
    private interface Resource {

        String conflictAccessTypes(
                String className, 
                String propertyName, 
                Class accessType);

        String typeArgumentMustBeClass(
                String className, 
                String propertyName,
                String typeDescriptor,
                String typeArgument);

        String emptyJonColumnName(
                String className, 
                String propertyName,
                Class joinColumnTypeConstant);

        String emptyJoinColumns(
                String className, 
                String propertyName,
                Class joinColumnsTypeConstant, 
                Class joinColumnTypeConstant);

        String illegalJoinColumnOnMultipColumns(
                String className, 
                String propertyName,
                Class joinColumn,
                String targetClassName, 
                String targetIdPropertyName);

        String canNotUseMappedByWith(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                Class annotationType);

        String annotationRequiresAnyOneType(
                String className, 
                String propertyName,
                Class annoatinType, 
                Class... requiredTypes);

        @SuppressWarnings("unchecked") 
        String annotationCanOnlyBeUsedWith(
                String className, 
                String propertyName,
                Class annotationType, 
                Class... otherAnnotationTypes);

        String inverseCollectionMustUseMappedBy(
                String className, 
                String propertyName,
                Class primaryAnnotationType);

        String implicitInverseMustBeDifferent(
                String className, 
                String propertyName,
                boolean inverse, 
                String oppositeClassName, 
                String oppositePropertyName, 
                boolean oppositeInverse);

        String canNotCollectInverse(
                String className, 
                String propertyName, 
                MetadataJoin join);

        String unidirectionalCanNotBeCollection(
                String className, 
                String propertyName);

        String conflictBidirectionalAssociation(
                String classname, 
                String propertyName,
                Class primaryAnnotationType,
                String mappedBy, 
                String oppositeClassName, 
                String oppositeOppositeClassName, 
                String oppositeOppositePropertyName);

        String targetOfMappedByCanNotUseMappedBy(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                String mappedBy, 
                String oppositeClassName, 
                Class oppositePrimaryAnnotationType);

        String mappedByOwnerIsWrong(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                String mappedBy, 
                String oppositeClassName);

        String mappedByIsNotAssociation(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                String mappedBy, 
                String oppositeClassName);

        String unknownMappedBy(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                String mappedBy, 
                String relatedClassName);

        String illlegalRelatedOrderColumnType(
                String className, 
                String propertyName,
                Class orderColumnTypeConstant,
                String orderColumn,
                String relatedClassName);

        String unknownRelatedOrderColumn(
                String className, 
                String propertyName,
                Class orderColumnTypeConstant,
                String orderColumn,
                Class primaryAnnotationType,
                String mappedByConstant,
                String relatedClassName);

        String illlegalRelatedMapKeyColumnType(
                String className, 
                String propertyName,
                Class mapKeyColumnTypeConstant,
                String mapKeyColumn,
                String targetClassName,
                String expectedTypeDescriptor, 
                String actualTypeDescriptor);

        String unknownRelatedMapKeyColumn(
                String className, 
                String propertyName,
                Class mapKeyColumnTypeConstant,
                String mapKeyColumn,
                Class primaryAnnotationType,
                String mappedByConstant,
                String targetClassName);

        String illlegalRelatedMapKeyType(
                String className, 
                String propertyName,
                Class mapKeyTypeConstant,
                String mapKeyColumn,
                String targetClassName,
                String expectedTypeDescriptor, 
                String actualTypeDescriptor);

        String relatedMapKeyMustBe(
                String className, 
                String propertyName,
                Class mapKeyTypeConstant, 
                String mapKey,
                String targetClassName,
                Class... classes);

        String unknownRelatedMapKey(
                String className, 
                String propertyName,
                Class mapKeyTypeConstant, 
                String mapKey,
                String targetClassName);

        String listRequiresOrderColumnValueInObjectModel4JPA(
                String className, 
                String propertyName,
                Class orderColumnTypeConstant);

        @SuppressWarnings("rawtypes")
        String listRequiresOrderColumnInObjectModel4JPA(
                String className, 
                String propertyName,
                Class listTypeConstant, 
                Class orderColumnTypeConstant);

        String relatedClassMustBeEntity(
                String className, 
                String property,
                Class primaryAnnotationType,
                String targetClassName, 
                Class entityTypeDescriptor);

        String conflictAnnotations(
                String className, 
                String propertyName,
                Class annotationType1, 
                Class annotationType2);

        String annotationIsNotSupportedInObjectModel4JPA(
                String className,
                String propertyName, 
                Class annotationType);

        String collectionMustBeGenericType(
                String className, 
                String propertyName,
                Class primaryAnnotationType);

        String badCollectionType(
                String className, 
                String propertyName,
                Class primaryAnnotationType,
                Class... classes);

        String typeDescriptorMustBeClass(
                String className, 
                String propertyName,
                String typeDescriptor);

        String canNotOverrideDeepProperties(
                String className, 
                String propertyName,
                Class attributeOverrideTypeConstant,
                String embeddedClassName, 
                Collection unmatchedNames);

        String incongruousJPAObjectInstrument(
                String className,
                String propertyName,
                String targetClassName, 
                Class jpaObjectModelInstrumentType);

        String embeddedTyepIsNotEmbeddable(
                String className, 
                String propertyName,
                Class embeddedTypeConstant, 
                String targetClassName,
                Class embeddableTypeConstant);

        String unknownRelatedClass(
                String className, 
                String propertyName,
                Class annotationType, 
                String targeClassName);

        String embeddedCircular(
                String className, 
                String property,
                Class embeddedTypeConstant, 
                XOrderedSet resolvingQueue);

        String unknownAccessType(
                String className, 
                String propertyName,
                Class accessTypeConstant);

        String badFieldAnnotation(
                String className, 
                String propertyName, 
                AccessType accessType);

        String noPropertyMethod(
                String className, 
                String propertyName, 
                AccessType accessType);

        String badMethodAnnotation(
                String className, 
                String propertyName, 
                AccessType accessType);

        String noPropertyField(
                String className, 
                String propertyName, 
                AccessType accessType);
        
        String contravarianceRequiresSuperMetaClass(
                String className, 
                String propertyName,
                Class contravarianceInstrumentTypeConstant);
        
        String noCovarianceProperty(
                String className, 
                String propertyName,
                Class contravarianceInstrumentTypeConstant,
                String covariancePropertyName,
                String superClassName);
        
        String covariancePropertyMustBeAssociation(
                String className, 
                String propertyName,
                Class contravarianceInstrumentTypeConstant,
                String covariancePropertyName,
                String covariancePropertyOwnerClassName);
        
        String illegalCovariancePropertyType(
                String className, 
                String propertyName,
                Class contravarianceInstrumentTypeConstant,
                String covariancePropertyName,
                String covariancePropertyOwnerClassName,
                String thisTypeDescriptor,
                String covariancePropertyTypeDescriptor);
        
        String mustNotBeEager(
                String className, 
                String propertyName, 
                FetchType eagerConstant,
                Class jpaObjectModelInstrumentTypeConstant, 
                String attributeName,
                FetchType lazyConstant);
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy