<commit_before>/*
 * Copyright 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package leap.orm.mapping;

import java.util.ArrayList;
import java.util.List;

import leap.db.model.DbForeignKeyBuilder;
import leap.db.model.DbForeignKeyColumn;
import leap.lang.Strings;
import leap.lang.beans.BeanProperty;
import leap.lang.beans.BeanType;
import leap.orm.annotation.Relational;
import leap.orm.metadata.MetadataException;
import leap.orm.naming.NamingStrategy;

public class RelationMapper implements Mapper {

	@Override
	public void completeMappings(MappingConfigContext context) throws MetadataException {
        for(EntityMappingBuilder emb : context.getEntityMappings()){
            for(RelationMappingBuilder rmb : emb.getRelationMappings()){
                preProcessRelationMapping(context, emb, rmb);
            }
        }

		for(EntityMappingBuilder emb : context.getEntityMappings()){
            for(RelationMappingBuilder rmb : emb.getRelationMappings()){
                postProcessRelationMapping(context, emb, rmb);
            }
		}

        for(EntityMappingBuilder emb : context.getEntityMappings()) {
            processInverseRelations(context, emb);
        }

        for(EntityMappingBuilder emb : context.getEntityMappings()) {
            processRelationProperties(context, emb);
        }
	}

    protected void preProcessRelationMapping(MappingConfigContext context, EntityMappingBuilder emb, RelationMappingBuilder rmb) {
        verifyTargetEntity(context, emb, rmb);

        RelationType type = rmb.getType();
        if(type == RelationType.MANY_TO_ONE){
            processManyToOneMapping(context, emb, rmb);
            return;
        }
    }

    protected void postProcessRelationMapping(MappingConfigContext context, EntityMappingBuilder emb, RelationMappingBuilder rmb) {
        RelationType type = rmb.getType();

        if(type == RelationType.MANY_TO_MANY) {
            processManyToManyMapping(context, emb, rmb);
            return;
        }
    }

    protected void processInverseRelations(MappingConfigContext context, EntityMappingBuilder emb) {
        List<RelationMappingBuilder> keyRelations = new ArrayList<>();

        //Avoid the java.util.ConcurrentModificationException in relation mapping list.
        List<RelationMappingBuilder> relationSnapshot = new ArrayList<>(emb.getRelationMappings());

        for(RelationMappingBuilder rmb : relationSnapshot) {
            //Be careful, the target entity may be the self entity of local entity, so we use snapshot list
            EntityMappingBuilder targetEmb = context.getEntityMapping(rmb.getTargetEntityName());

            RelationType type = rmb.getType();

            //check is a key reference of target entity (reference to the id fields).
            if(RelationType.MANY_TO_ONE.equals(type)) {

                boolean isAllIdFields = true;

                for(JoinFieldMappingBuilder jf : rmb.getJoinFields()) {
                    FieldMappingBuilder rf = targetEmb.findFieldMappingByName(jf.getReferencedFieldName());
                    if(!rf.isId()) {
                        isAllIdFields = false;
                        break;
                    }
                }

                if(isAllIdFields) {
                    keyRelations.add(rmb);
                }
            }

            //create one-to-many for many-to-one
            if(RelationType.MANY_TO_ONE.equals(type)) {
                //find one-to-many in target entity.
                RelationMappingBuilder inverse = findRelation(targetEmb, emb, RelationType.ONE_TO_MANY, rmb.getInverseRelationName());
                if(null == inverse) {
                    //create the virtual inverse one-to-many relation in target entity.
                    targetEmb.addRelationMapping(createInverseOneToManyRelation(emb, targetEmb, rmb));
                }else{
                    inverse.setInverseRelationName(rmb.getName());
                    rmb.setInverseRelationName(inverse.getName());
                }
                continue;
            }

            //create many-to-many for another side.
            if(RelationType.MANY_TO_MANY.equals(type) && !rmb.isEmbedded()) {
                RelationMappingBuilder inverse = findRelation(targetEmb, emb, RelationType.MANY_TO_MANY, rmb.getInverseRelationName());
                if(null == inverse) {
                    //create the virtual inverse relation in target entity.
                    EntityMappingBuilder joinEmb = context.getEntityMapping(rmb.getJoinEntityName());
                    targetEmb.addRelationMapping(createInverseManyToManyRelation(emb, targetEmb, joinEmb, rmb));
                }else{
                    inverse.setInverseRelationName(rmb.getName());
                    rmb.setInverseRelationName(inverse.getName());
                }
                continue;
            }
        }
    }

    protected RelationMappingBuilder findManyToManyRelation(EntityMappingBuilder emb, String targetEntityName, String joinEntityName) {

        for(RelationMappingBuilder rm : emb.getRelationMappings()) {<commit_msg>orm: skip if inverse relation already created<commit_after>/*
 * Copyright 2014 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package leap.orm.mapping;

import java.util.ArrayList;
import java.util.List;

import leap.db.model.DbForeignKeyBuilder;
import leap.db.model.DbForeignKeyColumn;
import leap.lang.Strings;
import leap.lang.beans.BeanProperty;
import leap.lang.beans.BeanType;
import leap.orm.annotation.Relational;
import leap.orm.metadata.MetadataException;
import leap.orm.naming.NamingStrategy;

public class RelationMapper implements Mapper {

	@Override
	public void completeMappings(MappingConfigContext context) throws MetadataException {
        for(EntityMappingBuilder emb : context.getEntityMappings()){
            for(RelationMappingBuilder rmb : emb.getRelationMappings()){
                preProcessRelationMapping(context, emb, rmb);
            }
        }

		for(EntityMappingBuilder emb : context.getEntityMappings()){
            for(RelationMappingBuilder rmb : emb.getRelationMappings()){
                postProcessRelationMapping(context, emb, rmb);
            }
		}

        for(EntityMappingBuilder emb : context.getEntityMappings()) {
            processInverseRelations(context, emb);
        }

        for(EntityMappingBuilder emb : context.getEntityMappings()) {
            processRelationProperties(context, emb);
        }
	}

    protected void preProcessRelationMapping(MappingConfigContext context, EntityMappingBuilder emb, RelationMappingBuilder rmb) {
        verifyTargetEntity(context, emb, rmb);

        RelationType type = rmb.getType();
        if(type == RelationType.MANY_TO_ONE){
            processManyToOneMapping(context, emb, rmb);
            return;
        }
    }

    protected void postProcessRelationMapping(MappingConfigContext context, EntityMappingBuilder emb, RelationMappingBuilder rmb) {
        RelationType type = rmb.getType();

        if(type == RelationType.MANY_TO_MANY) {
            processManyToManyMapping(context, emb, rmb);
            return;
        }
    }

    protected void processInverseRelations(MappingConfigContext context, EntityMappingBuilder emb) {
        List<RelationMappingBuilder> keyRelations = new ArrayList<>();

        //Avoid the java.util.ConcurrentModificationException in relation mapping list.
        List<RelationMappingBuilder> relationSnapshot = new ArrayList<>(emb.getRelationMappings());

        for(RelationMappingBuilder rmb : relationSnapshot) {
            //Be careful, the target entity may be the self entity of local entity, so we use snapshot list
            EntityMappingBuilder targetEmb = context.getEntityMapping(rmb.getTargetEntityName());

            RelationType type = rmb.getType();

            //check is a key reference of target entity (reference to the id fields).
            if(RelationType.MANY_TO_ONE.equals(type)) {

                boolean isAllIdFields = true;

                for(JoinFieldMappingBuilder jf : rmb.getJoinFields()) {
                    FieldMappingBuilder rf = targetEmb.findFieldMappingByName(jf.getReferencedFieldName());
                    if(!rf.isId()) {
                        isAllIdFields = false;
                        break;
                    }
                }

                if(isAllIdFields) {
                    keyRelations.add(rmb);
                }
            }

            //create one-to-many for many-to-one
            if(RelationType.MANY_TO_ONE.equals(type)) {
                //find one-to-many in target entity.
                RelationMappingBuilder inverse = findRelation(targetEmb, emb, RelationType.ONE_TO_MANY, rmb.getInverseRelationName());
                if(null == inverse) {
                    inverse = createInverseOneToManyRelation(emb, targetEmb, rmb);

                    //is inverse already created?
                    if(isInverseAlreadyCreated(emb, targetEmb, inverse)) {
                        continue;
                    }

                    //create the virtual inverse one-to-many relation in target entity.
                    targetEmb.addRelationMapping(inverse);
                }else{
                    inverse.setInverseRelationName(rmb.getName());
                    rmb.setInverseRelationName(inverse.getName());
                }
                continue;
            }

            //create many-to-many for another side.
            if(RelationType.MANY_TO_MANY.equals(type) && !rmb.isEmbedded()) {
                RelationMappingBuilder inverse = findRelation(targetEmb, emb, RelationType.MANY_TO_MANY, rmb.getInverseRelationName());
                if(null == inverse) {
                    //create the virtual inverse relation in target entity.
                    EntityMappingBuilder joinEmb = context.getEntityMapping(rmb.getJoinEntityName());
                    targetEmb.addRelationMapping(createInverseManyToManyRelation(emb, targetEmb, joinEmb, rmb));
                }else{
                    inverse.setInverseRelationName(rmb.getName());
                    rmb.setInverseRelationName(inverse.getName());
                }
                continue;
            }
        }
    }

    protected boolean isInverseAlreadyCreated(EntityMappingBuilder emb, EntityMappingBuilder targetEmb, RelationMappingBuilder inverse) {
        RelationMappingBuilder existence = targetEmb.getRelationMapping(inverse.getName());
        if(null == existence) {
            return false;
        }
        return existence.getTargetEntityName().equalsIgnoreCase(inverse.getTargetEntityName())
                && existence.getType().equals(inverse.getType());
    }

    protected RelationMappingBuilder findManyToManyRelation(EntityMappingBuilder emb, String targetEntityName, String joinEntityName) {

        for(RelationMappingBuilder rm : emb.getRelationMappings()) {
{'fraction_non_alphanumeric': 0.0815868263473054, 'fraction_numerical': 0.0013306719893546241, 'mean_word_length': 2.445558739255014, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7587621', 'n_tokens_mistral': 3104, 'n_tokens_neox': 2964, 'n_words': 716}