# DefaultComposedIdentifierAssembler.java

    package org.codefilarete.stalactite.engine.configurer;

    import java.lang.reflect.Constructor;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.function.Function;
    import javax.annotation.Nullable;

    import org.codefilarete.reflection.Accessor;
    import org.codefilarete.reflection.Mutator;
    import org.codefilarete.reflection.ReadWritePropertyAccessPoint;
    import org.codefilarete.reflection.ReversibleAccessor;
    import org.codefilarete.stalactite.dsl.MappingConfigurationException;
    import org.codefilarete.stalactite.mapping.id.assembly.ComposedIdentifierAssembler;
    import org.codefilarete.stalactite.sql.ddl.structure.Column;
    import org.codefilarete.stalactite.sql.ddl.structure.Table;
    import org.codefilarete.stalactite.sql.result.ColumnedRow;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.collection.Iterables;

    import static org.codefilarete.tool.Reflections.PRIMITIVE_DEFAULT_VALUES;

    /**
     * Default implementation of {@link ComposedIdentifierAssembler}: read and write values from key beans thanks to given mapping.
     * Note that for now this class only supports no-arg constructor for key-bean instantiation
     *
     * @param <I> identifier type
     * @param <T> table type
     * @author Guillaume Mary
     */
    public class DefaultComposedIdentifierAssembler<I, T extends Table<T>> extends ComposedIdentifierAssembler<I, T> {

    	private final Function<ColumnedRow, I> keyFactory;
    	private final Map<ReadWritePropertyAccessPoint<I, ?>, Column<T, ?>> mapping;
    	private final Map<Accessor<I, ?>, Column<T, ?>> compositeKeyReaders;
    	private final Map<Mutator<I, ?>, Column<T, ?>> compositeKeyWriters;
    	private final Constructor<I> defaultConstructor;

    	public DefaultComposedIdentifierAssembler(T targetTable,
    											  Class<I> keyType,
    											  Map<? extends ReadWritePropertyAccessPoint<I, ?>, ? extends Column<T, ?>> mapping) {
    		super(targetTable);
    		this.mapping = (Map<ReadWritePropertyAccessPoint<I, ?>, Column<T, ?>>) mapping;
    		this.defaultConstructor = Reflections.findConstructor(keyType);
    		// for now we only support no-arg constructor
    		if (defaultConstructor == null) {
    			// we'll lately throw an exception (we could do it now) but the lack of constructor may be due to an abstract class in inheritance
    			// path which currently won't be instanced at runtime (because its concrete subclass will be) so there's no reason to throw
    			// the exception now
    			this.keyFactory = keyValueProvider -> {
    				throw new MappingConfigurationException("Key class " + Reflections.toString(keyType) + " doesn't have a compatible accessible constructor,"
    						+ " please implement a no-arg constructor"
    //						+ " or " + Reflections.toString(idDefinition.getMemberType()) + "-arg constructor"
    				);
    			};
    		} else {
    			this.keyFactory = keyValueProvider -> Reflections.newInstance(defaultConstructor);
    		}

    		this.compositeKeyReaders = Iterables.map(mapping.entrySet(), Map.Entry::getKey, Map.Entry::getValue);
    		this.compositeKeyWriters = Iterables.map(mapping.entrySet(), Map.Entry::getKey, Map.Entry::getValue);
    	}

    	public Map<ReadWritePropertyAccessPoint<I, ?>, Column<T, ?>> getMapping() {
    		return mapping;
    	}

    	public Constructor<I> getDefaultConstructor() {
    		return defaultConstructor;
    	}

    	public Class<I> getKeyType() {
    		return getDefaultConstructor().getDeclaringClass();
    	}

    	public Map<Accessor<I, ?>, Column<T, ?>> getCompositeKeyReaders() {
    		return compositeKeyReaders;
    	}

    	public Map<Mutator<I, ?>, Column<T, ?>> getCompositeKeyWriters() {
    		return compositeKeyWriters;
    	}

    	@Override
    	public Map<Column<T, ?>, Object> getColumnValues(I id) {
    		Map<Column<T, ?>, Object> result = new HashMap<>();
    		compositeKeyReaders.forEach((propertyAccessor, column) -> {
    			result.put(column, id == null ? null : propertyAccessor.get(id));
    		});
    		return result;
    	}

    	@Nullable
    	@Override
    	public I assemble(ColumnedRow columnValueProvider) {
    		// we should not return an id if any value is null
    		boolean hasAnyNullValue = getColumns().stream().anyMatch(column -> {
    			Object partialKeyValue = columnValueProvider.get(column);
    			return partialKeyValue == null || PRIMITIVE_DEFAULT_VALUES.containsValue(partialKeyValue);
    		});
    		if (hasAnyNullValue) {
    			return null;
    		}

    		I result = keyFactory.apply(columnValueProvider);
    		mapping.forEach((setter, col) -> {
    			((ReversibleAccessor<I, Object>) setter).toMutator().set(result, columnValueProvider.get(col));
    		});
    		return result;
    	}
    }