# IdentificationStep.java

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

    import org.codefilarete.reflection.AccessorDefinition;
    import org.codefilarete.stalactite.dsl.MappingConfigurationException;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.CompositeKeyMapping;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.KeyMapping;
    import org.codefilarete.stalactite.dsl.entity.EntityMappingConfiguration.SingleKeyMapping;
    import org.codefilarete.stalactite.engine.configurer.AbstractIdentification;
    import org.codefilarete.tool.Reflections;
    import org.codefilarete.tool.VisibleForTesting;
    import org.codefilarete.tool.function.Hanger.Holder;

    /**
     * Determine identification (single/composite), perform mapping-superclass + inheritance constraints, ensure equals/hashCode constraints for composite.
     *
     * @author Guillaume Mary
     */
    public class IdentificationStep<C, I> extends AbstractIdentificationStep<C, I> {

    	/**
    	 * Looks for {@link EntityMappingConfiguration} defining the identifier policy by going up through inheritance hierarchy.
    	 *
    	 * @return a wrapper of necessary elements to manage entity identifier
    	 * @throws UnsupportedOperationException when identification was not found, because it doesn't make sense to have an entity without identification
    	 */
    	AbstractIdentification<C, I> determineIdentification(EntityMappingConfiguration<C, I> entityMappingConfiguration) {
    		if (entityMappingConfiguration.getInheritanceConfiguration() != null && entityMappingConfiguration.getPropertiesMapping().getMappedSuperClassConfiguration() != null) {
    			throw new MappingConfigurationException("Combination of mapped super class and inheritance is not supported, please remove one of them");
    		}
    		if (entityMappingConfiguration.getKeyMapping() != null && entityMappingConfiguration.getInheritanceConfiguration() != null) {
    			throw new MappingConfigurationException("Defining an identifier in conjunction with entity inheritance is not supported : "
    					+ Reflections.toString(entityMappingConfiguration.getEntityType()) + " defines identifier "
    					+ AccessorDefinition.toString(entityMappingConfiguration.getKeyMapping().getAccessor())
    					+ " while it inherits from " + Reflections.toString(entityMappingConfiguration.getInheritanceConfiguration().getParentMappingConfiguration().getEntityType()));
    		}

    		// if mappedSuperClass is used, then identifier is expected to be declared on the configuration
    		// because mappedSuperClass can't define it (it is an EmbeddableMappingConfiguration)
    		Holder<EntityMappingConfiguration<C, I>> configurationDefiningIdentification = new Holder<>();
    		// hierarchy must be scanned to find the very first configuration that defines identification
    		// iterating over mapping from inheritance
    		entityMappingConfiguration.inheritanceIterable().forEach(entityConfiguration -> {
    			KeyMapping keyMapping = entityConfiguration.getKeyMapping();
    			if (keyMapping != null &&
    					(keyMapping instanceof EntityMappingConfiguration.SingleKeyMapping && ((SingleKeyMapping<?, ?>) keyMapping).getIdentifierPolicy() != null)
    					|| keyMapping instanceof EntityMappingConfiguration.CompositeKeyMapping) {
    				if (configurationDefiningIdentification.get() != null) {
    					throw new UnsupportedOperationException("Identifier policy is defined twice in hierarchy : first by "
    							+ Reflections.toString(configurationDefiningIdentification.get().getEntityType())
    							+ ", then by " + Reflections.toString(entityConfiguration.getEntityType()));
    				} else {
    					configurationDefiningIdentification.set((EntityMappingConfiguration<C, I>) entityConfiguration);
    				}
    			}
    		});
    		EntityMappingConfiguration<C, I> foundConfiguration = configurationDefiningIdentification.get();
    		if (foundConfiguration == null) {
    			throw newMissingIdentificationException(entityMappingConfiguration.getEntityType());
    		}

    		KeyMapping<C, I> foundKeyMapping = foundConfiguration.getKeyMapping();
    		if (foundKeyMapping instanceof SingleKeyMapping) {
    			return AbstractIdentification.forSingleKey(foundConfiguration);
    		} else if (foundKeyMapping instanceof CompositeKeyMapping) {
    			assertCompositeKeyIdentifierOverridesEqualsHashcode((CompositeKeyMapping<?, ?>) foundKeyMapping);
    			return AbstractIdentification.forCompositeKey(foundConfiguration, (CompositeKeyMapping<C, I>) foundKeyMapping);
    		} else {
    			// should not happen
    			throw new MappingConfigurationException("Unknown key mapping : " + foundKeyMapping);
    		}
    	}

    	@VisibleForTesting
    	static void assertCompositeKeyIdentifierOverridesEqualsHashcode(CompositeKeyMapping<?, ?> compositeKeyIdentification) {
    		Class<?> compositeKeyType = AccessorDefinition.giveDefinition(compositeKeyIdentification.getAccessor()).getMemberType();
    		try {
    			compositeKeyType.getDeclaredMethod("equals", Object.class);
    			compositeKeyType.getDeclaredMethod("hashCode");
    		} catch (NoSuchMethodException e) {
    			throw new MappingConfigurationException("Composite key identifier class " + Reflections.toString(compositeKeyType) + " seems to have default implementation of equals() and hashcode() methods,"
    					+ " which is not supported (identifiers must be distinguishable), please make it implement them");
    		}
    	}
    }