# ManyToManyAssociationConfiguration.java

    package org.codefilarete.stalactite.engine.configurer.manytomany;

    import java.lang.reflect.Method;
    import java.util.Collection;
    import java.util.function.Supplier;

    import org.codefilarete.reflection.AccessorByMethod;
    import org.codefilarete.reflection.AccessorByMethodReference;
    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.reflection.Accessors;
    import org.codefilarete.reflection.MethodReferenceCapturer;
    import org.codefilarete.reflection.Mutator;
    import org.codefilarete.reflection.MutatorByMethod;
    import org.codefilarete.reflection.MutatorByMethodReference;
    import org.codefilarete.reflection.PropertyAccessor;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.stalactite.dsl.naming.ColumnNamingStrategy;
    import org.codefilarete.stalactite.dsl.naming.ForeignKeyNamingStrategy;
    import org.codefilarete.stalactite.engine.configurer.manytomany.ManyToManyRelation.MappedByConfiguration;
    import org.codefilarete.stalactite.engine.runtime.ConfiguredRelationalPersister;
    import org.codefilarete.stalactite.sql.ddl.structure.PrimaryKey;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.tool.Nullable;
    import org.codefilarete.tool.Reflections;
    import org.danekja.java.util.function.serializable.SerializableBiConsumer;
    import org.danekja.java.util.function.serializable.SerializableFunction;

    import static org.codefilarete.tool.Nullable.nullable;

    /**
     * Class that stores elements necessary to many-to-many association configuration
     * @author Guillaume Mary
     */
    class ManyToManyAssociationConfiguration<SRC, TRGT, SRCID, TRGTID, C1 extends Collection<TRGT>, C2 extends Collection<SRC>,
    		LEFTTABLE extends Table<LEFTTABLE>, RIGHTTABLE extends Table<RIGHTTABLE>> {

    	private final ManyToManyRelation<SRC, TRGT, TRGTID, C1, C2> manyToManyRelation;
    	private final ConfiguredRelationalPersister<SRC, SRCID> srcPersister;
    	private final PrimaryKey<LEFTTABLE, SRCID> leftPrimaryKey;
    	private final ForeignKeyNamingStrategy foreignKeyNamingStrategy;
    	private final ColumnNamingStrategy indexColumnNamingStrategy;
    	private final ReversibleAccessor<SRC, C1> collectionGetter;
    	private final Mutator<SRC, C1> setter;
    	private final boolean orphanRemoval;
    	private final boolean writeAuthorized;
    	private final AccessorDefinition accessorDefinition;
    	private final Supplier<C1> collectionFactory;

    	ManyToManyAssociationConfiguration(ManyToManyRelation<SRC, TRGT, TRGTID, C1, C2> manyToManyRelation,
    									   ConfiguredRelationalPersister<SRC, SRCID> srcPersister,
    									   PrimaryKey<LEFTTABLE, SRCID> leftPrimaryKey,
    									   ForeignKeyNamingStrategy foreignKeyNamingStrategy,
    									   ColumnNamingStrategy indexColumnNamingStrategy,
    									   boolean orphanRemoval,
    									   boolean writeAuthorized) {
    		this.manyToManyRelation = manyToManyRelation;
    		this.srcPersister = srcPersister;
    		this.leftPrimaryKey = leftPrimaryKey;
    		this.foreignKeyNamingStrategy = foreignKeyNamingStrategy;
    		this.collectionGetter = manyToManyRelation.getCollectionAccessor();
    		this.indexColumnNamingStrategy = indexColumnNamingStrategy;
    		this.setter = collectionGetter.toMutator();
    		// we don't use AccessorDefinition.giveMemberDefinition(..) because it gives a cross-member definition, loosing get/set for example,
    		// whereas we need this information to build better association table name
    		this.orphanRemoval = orphanRemoval;
    		this.writeAuthorized = writeAuthorized;
    		this.accessorDefinition = AccessorDefinition.giveDefinition(this.manyToManyRelation.getCollectionAccessor()); //buildAccessorDefinition();
    		this.collectionFactory = buildCollectionFactory();
    	}

    	private Supplier<C1> buildCollectionFactory() {
    		Supplier<C1> result = manyToManyRelation.getCollectionFactory();
    		if (result == null) {
    			result = Reflections.giveCollectionFactory((Class<C1>) this.accessorDefinition.getMemberType());
    		}
    		return result;
    	}

    	public ManyToManyRelation<SRC, TRGT, TRGTID, C1, C2> getManyToManyRelation() {
    		return manyToManyRelation;
    	}

    	public ColumnNamingStrategy getIndexColumnNamingStrategy() {
    		return indexColumnNamingStrategy;
    	}

    	public ConfiguredRelationalPersister<SRC, SRCID> getSrcPersister() {
    		return srcPersister;
    	}

    	public PrimaryKey<LEFTTABLE, SRCID> getLeftPrimaryKey() {
    		return leftPrimaryKey;
    	}

    	public ForeignKeyNamingStrategy getForeignKeyNamingStrategy() {
    		return foreignKeyNamingStrategy;
    	}

    	public ReversibleAccessor<SRC, C1> getCollectionGetter() {
    		return collectionGetter;
    	}

    	public Mutator<SRC, C1> getSetter() {
    		return setter;
    	}

    	public boolean isOrphanRemoval() {
    		return orphanRemoval;
    	}

    	public boolean isWriteAuthorized() {
    		return writeAuthorized;
    	}

    	/**
    	 * Equivalent to {@link ManyToManyRelation#getCollectionAccessor()} but used for table and colum naming.
    	 */
    	public AccessorDefinition getAccessorDefinition() {
    		return accessorDefinition;
    	}

    	/**
    	 * Gives the collection factory used to instantiate relation field.
    	 *
    	 * @return the one given by {@link ManyToManyRelation#getCollectionFactory()} or one deduced from member signature
    	 */
    	protected Supplier<C1> getCollectionFactory() {
    		return collectionFactory;
    	}

    	/**
    	 * Build the accessor for the reverse property, made of configured getter and setter. If one of them is unavailable, it's deduced from the
    	 * present one. If both are absent, null is returned.
    	 *
    	 * @return null if no getter nor setter were defined
    	 */
    	@javax.annotation.Nullable
    	PropertyAccessor<TRGT, C2> buildReversePropertyAccessor() {
    		MappedByConfiguration<SRC, TRGT, C2> mappedByConfiguration = manyToManyRelation.getMappedByConfiguration();
    		Nullable<AccessorByMethodReference<TRGT, C2>> getterReference = nullable(mappedByConfiguration.getReverseCollectionAccessor()).map(Accessors::accessorByMethodReference);
    		Nullable<MutatorByMethodReference<TRGT, C2>> setterReference = nullable(mappedByConfiguration.getReverseCollectionMutator()).map(Accessors::mutatorByMethodReference);
    		if (getterReference.isAbsent() && setterReference.isAbsent()) {
    			return null;
    		} else if (getterReference.isPresent() && setterReference.isPresent()) {
    			// we keep close to user demand : we keep its method references
    			return new PropertyAccessor<>(getterReference.get(), setterReference.get());
    		} else if (getterReference.isPresent() && setterReference.isAbsent()) {
    			// we keep close to user demand : we keep its method reference ...
    			// ... but we can't do it for mutator, so we use the most equivalent manner : a mutator based on setter method (fallback to property if not present)
    			return new PropertyAccessor<>(getterReference.get(), new AccessorByMethod<TRGT, C2>(captureMethod(mappedByConfiguration.getReverseCollectionAccessor())).toMutator());
    		} else {
    			// we keep close to user demand : we keep its method reference ...
    			// ... but we can't do it for getter, so we use the most equivalent manner : a mutator based on setter method (fallback to property if not present)
    			return new PropertyAccessor<>(new MutatorByMethod<TRGT, C2>(captureMethod(mappedByConfiguration.getReverseCollectionMutator())).toAccessor(), setterReference.get());
    		}
    	}

    	private final MethodReferenceCapturer methodSpy = new MethodReferenceCapturer();

    	private Method captureMethod(SerializableFunction getter) {
    		return this.methodSpy.findMethod(getter);
    	}

    	private Method captureMethod(SerializableBiConsumer setter) {
    		return this.methodSpy.findMethod(setter);
    	}
    }