# ValueBuilderWithPrototype.java

    /*
     * Copyright 2007, Rickard Öberg.
     * Copyright 2009, Niclas Hedhman.
     * Copyright 2012, Kent Sølvsten.
     * Copyright 2013, Paul Merlin.
     *
     * 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 org.qi4j.runtime.value;

    import java.util.HashMap;
    import java.util.Map;
    import org.qi4j.api.association.AssociationDescriptor;
    import org.qi4j.api.association.AssociationStateHolder;
    import org.qi4j.api.association.NamedAssociation;
    import org.qi4j.api.common.ConstructionException;
    import org.qi4j.api.entity.EntityReference;
    import org.qi4j.api.property.PropertyDescriptor;
    import org.qi4j.api.value.ValueBuilder;
    import org.qi4j.api.value.ValueComposite;
    import org.qi4j.functional.Function;
    import org.qi4j.runtime.composite.FunctionStateResolver;
    import org.qi4j.runtime.composite.MixinModel;
    import org.qi4j.runtime.composite.MixinsModel;
    import org.qi4j.runtime.composite.StateResolver;
    import org.qi4j.runtime.composite.UsesInstance;
    import org.qi4j.runtime.injection.InjectionContext;
    import org.qi4j.spi.module.ModelModule;
    import org.qi4j.runtime.structure.ModuleInstance;

    /**
     * Implementation of ValueBuilder with a prototype supplied
     */
    public class ValueBuilderWithPrototype<T>
        implements ValueBuilder<T>
    {
        private ValueInstance prototypeInstance;
        private final ValueModel valueModel;

        public ValueBuilderWithPrototype( ModelModule<ValueModel> compositeModelModule,
                                          ModuleInstance currentModule,
                                          T prototype
        )
        {
            valueModel = compositeModelModule.model();
            // Only shallow clone, as all generic types of the ValueComposites are expected to be Immutable.

            MixinsModel mixinsModel = valueModel.mixinsModel();
            Object[] mixins = mixinsModel.newMixinHolder();
            final ValueStateInstance prototypeState = ValueInstance.valueInstanceOf( (ValueComposite) prototype ).state();
            StateResolver resolver = new FunctionStateResolver(
                new PropertyDescriptorFunction( prototypeState ),
                new AssociationDescriptorEntityReferenceFunction( prototypeState ),
                new AssociationDescriptorIterableFunction( prototypeState ),
                new AssociationDescriptorMapFunction( prototypeState )
            );
            ValueStateInstance state = new ValueStateInstance( compositeModelModule, currentModule, resolver );
            ValueInstance valueInstance = new ValueInstance(
                valueModel,
                currentModule,
                mixins,
                state
            );

            int i = 0;
            InjectionContext injectionContext = new InjectionContext( valueInstance, UsesInstance.EMPTY_USES, state );
            for( MixinModel mixinModel : mixinsModel.mixinModels() )
            {
                mixins[ i++ ] = mixinModel.newInstance( injectionContext );
            }

    //        // Use serialization-deserialization to make a copy of the prototype
    //        final Object value;
    //        try
    //        {
    //            // @TODO there is probably a more efficient way to do this
    //            ValueSerialization valueSerialization = currentModule.valueSerialization();
    //            String serialized = valueSerialization.serialize( prototype );
    //            value = valueSerialization.deserialize( valueModel.valueType(), serialized);
    //        }
    //        catch( ValueSerializationException e )
    //        {
    //            throw new IllegalStateException( "Could not serialize-copy Value", e );
    //        }

    //        ValueInstance valueInstance = ValueInstance.valueInstanceOf( (ValueComposite) value );
            valueInstance.prepareToBuild();
            this.prototypeInstance = valueInstance;
        }

        @Override
        public T prototype()
        {
            verifyUnderConstruction();
            return prototypeInstance.<T>proxy();
        }

        @Override
        public AssociationStateHolder state()
        {
            verifyUnderConstruction();
            return prototypeInstance.state();
        }

        @Override
        public <K> K prototypeFor( Class<K> mixinType )
        {
            verifyUnderConstruction();
            return prototypeInstance.newProxy( mixinType );
        }

        @Override
        public T newInstance()
            throws ConstructionException
        {
            verifyUnderConstruction();

            // Set correct info's (immutable) on the state
            prototypeInstance.prepareBuilderState();

            // Check that it is valid
            valueModel.checkConstraints( prototypeInstance.state() );

            try
            {
                return prototypeInstance.<T>proxy();
            }
            finally
            {
                // Invalidate builder
                prototypeInstance = null;
            }
        }

        private void verifyUnderConstruction()
        {
            if( prototypeInstance == null )
            {
                throw new IllegalStateException( "ValueBuilder instances cannot be reused" );
            }
        }

        private static class PropertyDescriptorFunction
            implements Function<PropertyDescriptor, Object>
        {
            private final ValueStateInstance prototypeState;

            public PropertyDescriptorFunction( ValueStateInstance prototypeState )
            {
                this.prototypeState = prototypeState;
            }

            @Override
            public Object map( PropertyDescriptor descriptor )
            {
                return prototypeState.propertyFor( descriptor.accessor() ).get();
            }
        }

        private static class AssociationDescriptorEntityReferenceFunction
            implements Function<AssociationDescriptor, EntityReference>
        {
            private final ValueStateInstance prototypeState;

            public AssociationDescriptorEntityReferenceFunction( ValueStateInstance prototypeState )
            {
                this.prototypeState = prototypeState;
            }

            @Override
            public EntityReference map( AssociationDescriptor descriptor )
            {
                return prototypeState.associationFor( descriptor.accessor() ).reference();
            }
        }

        private static class AssociationDescriptorIterableFunction
            implements Function<AssociationDescriptor, Iterable<EntityReference>>
        {
            private final ValueStateInstance prototypeState;

            public AssociationDescriptorIterableFunction( ValueStateInstance prototypeState )
            {
                this.prototypeState = prototypeState;
            }

            @Override
            public Iterable<EntityReference> map( AssociationDescriptor descriptor )
            {
                return prototypeState.manyAssociationFor( descriptor.accessor() ).references();
            }
        }

        private static class AssociationDescriptorMapFunction
            implements Function<AssociationDescriptor, Map<String, EntityReference>>
        {
            private final ValueStateInstance prototypeState;

            public AssociationDescriptorMapFunction( ValueStateInstance prototypeState )
            {
                this.prototypeState = prototypeState;
            }

            @Override
            public Map<String, EntityReference> map( AssociationDescriptor descriptor )
            {
                Map<String, EntityReference> result = new HashMap<>();
                NamedAssociation<?> namedAssociation = prototypeState.namedAssociationFor( descriptor.accessor() );
                for( String name : namedAssociation )
                {
                    result.put( name, namedAssociation.referenceOf( name ) );
                }
                return result;
            }
        }
    }