# ValueTypeFactory.java

    /*
     * Copyright 2009 Niclas Hedhman.
     *
     * 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.types;

    import java.lang.reflect.ParameterizedType;
    import java.lang.reflect.Type;
    import java.lang.reflect.TypeVariable;
    import org.qi4j.api.common.InvalidApplicationException;
    import org.qi4j.api.common.MetaInfo;
    import org.qi4j.api.common.Visibility;
    import org.qi4j.api.type.CollectionType;
    import org.qi4j.api.type.EnumType;
    import org.qi4j.api.type.MapType;
    import org.qi4j.api.type.Serialization;
    import org.qi4j.api.type.ValueCompositeType;
    import org.qi4j.api.type.ValueType;
    import org.qi4j.api.util.Classes;
    import org.qi4j.api.value.ValueComposite;
    import org.qi4j.functional.HierarchicalVisitorAdapter;
    import org.qi4j.functional.Iterables;
    import org.qi4j.functional.Specifications;
    import org.qi4j.runtime.association.AssociationsModel;
    import org.qi4j.runtime.association.ManyAssociationsModel;
    import org.qi4j.runtime.association.NamedAssociationsModel;
    import org.qi4j.runtime.composite.CompositeMethodsModel;
    import org.qi4j.runtime.composite.MixinsModel;
    import org.qi4j.runtime.property.PropertiesModel;
    import org.qi4j.runtime.structure.LayerModel;
    import org.qi4j.runtime.structure.ModuleModel;
    import org.qi4j.runtime.structure.UsedLayersModel;
    import org.qi4j.runtime.value.ValueModel;
    import org.qi4j.runtime.value.ValueStateModel;
    import org.qi4j.runtime.value.ValuesModel;

    public class ValueTypeFactory
    {
        private static final ValueTypeFactory instance = new ValueTypeFactory();

        public static ValueTypeFactory instance()
        {
            return instance;
        }

        @SuppressWarnings( {"raw", "unchecked"} )
        public ValueType newValueType( Type type,
                                       Class declaringClass,
                                       Class compositeType,
                                       LayerModel layer,
                                       ModuleModel module,
                                       Serialization.Variant variant
        )
        {
            ValueType valueType = null;
            if( CollectionType.isCollection( type ) )
            {
                if( type instanceof ParameterizedType )
                {
                    ParameterizedType pt = (ParameterizedType) type;
                    Type collectionType = pt.getActualTypeArguments()[ 0 ];
                    if( collectionType instanceof TypeVariable )
                    {
                        TypeVariable collectionTypeVariable = (TypeVariable) collectionType;
                        collectionType = Classes.resolveTypeVariable( collectionTypeVariable, declaringClass, compositeType );
                    }
                    ValueType collectedType = newValueType( collectionType, declaringClass, compositeType, layer, module, variant );
                    valueType = new CollectionType( Classes.RAW_CLASS.map( type ), collectedType );
                }
                else
                {
                    ValueType collectedType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant );
                    valueType = new CollectionType( Classes.RAW_CLASS.map( type ), collectedType );
                }
            }
            else if( MapType.isMap( type ) )
            {
                if( type instanceof ParameterizedType )
                {
                    ParameterizedType pt = (ParameterizedType) type;
                    Type keyType = pt.getActualTypeArguments()[ 0 ];
                    if( keyType instanceof TypeVariable )
                    {
                        TypeVariable keyTypeVariable = (TypeVariable) keyType;
                        keyType = Classes.resolveTypeVariable( keyTypeVariable, declaringClass, compositeType );
                    }
                    ValueType keyedType = newValueType( keyType, declaringClass, compositeType, layer, module, variant );
                    Type valType = pt.getActualTypeArguments()[ 1 ];
                    if( valType instanceof TypeVariable )
                    {
                        TypeVariable valueTypeVariable = (TypeVariable) valType;
                        valType = Classes.resolveTypeVariable( valueTypeVariable, declaringClass, compositeType );
                    }
                    ValueType valuedType = newValueType( valType, declaringClass, compositeType, layer, module, variant );
                    valueType = new MapType( Classes.RAW_CLASS.map( type ), keyedType, valuedType, variant );
                }
                else
                {
                    ValueType keyType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant );
                    ValueType valuesType = newValueType( Object.class, declaringClass, compositeType, layer, module, variant );
                    valueType = new MapType( Classes.RAW_CLASS.map( type ), keyType, valuesType, variant );
                }
            }
            else if( ValueCompositeType.isValueComposite( type ) )
            {
                // Find ValueModel in module/layer/used layers
                ValueModel model = new ValueFinder( layer, module, Classes.RAW_CLASS.map( type ) ).getFoundModel();

                if( model == null )
                {
                    if( type.equals( ValueComposite.class ) )
                    {
                        // Create default model
                        MixinsModel mixinsModel = new MixinsModel();
                        Iterable valueComposite = (Iterable) Iterables.iterable( ValueComposite.class );
                        ValueStateModel valueStateModel = new ValueStateModel( new PropertiesModel(),
                                                                               new AssociationsModel(),
                                                                               new ManyAssociationsModel(),
                                                                               new NamedAssociationsModel() );
                        model = new ValueModel( valueComposite, Visibility.application, new MetaInfo(),
                                                mixinsModel, valueStateModel, new CompositeMethodsModel( mixinsModel ) );
                    }
                    else
                    {
                        throw new InvalidApplicationException( "[" + module.name() + "] Could not find ValueComposite of type " + type );
                    }
                }

                return model.valueType();
            }
            else if( EnumType.isEnum( type ) )
            {
                valueType = new EnumType( Classes.RAW_CLASS.map( type ) );
            }
            else
            {
                valueType = new ValueType( Classes.RAW_CLASS.map( type ) );
            }

            return valueType;
        }

        @SuppressWarnings( "raw" )
        private static class ValueFinder
            extends HierarchicalVisitorAdapter<Object, Object, RuntimeException>
        {
            private Class type;
            private ValueModel foundModel;
            private Visibility visibility;

            private ValueFinder( LayerModel layer, ModuleModel module, Class type )
            {
                this.type = type;

                visibility = Visibility.module;
                module.accept( this );

                if( foundModel == null )
                {
                    visibility = Visibility.layer;
                    layer.accept( this );

                    if( foundModel == null )
                    {
                        visibility = Visibility.application;
                        layer.usedLayers().accept( this );
                    }
                }
            }

            public ValueModel getFoundModel()
            {
                return foundModel;
            }

            @Override
            public boolean visitEnter( Object visited )
                throws RuntimeException
            {
                if( visited instanceof ValuesModel )
                {
                    return true;
                }
                else if( visited instanceof ModuleModel )
                {
                    return true;
                }
                else if (visited instanceof LayerModel )
                {
                    return true;
                }
                else if (visited instanceof UsedLayersModel )
                {
                    return true;
                }
                else if( visited instanceof ValueModel )
                {
                    ValueModel valueModel = (ValueModel) visited;
                    boolean typeEquality = Specifications.in( valueModel.types() ).satisfiedBy( type );
                    if( typeEquality && valueModel.visibility().ordinal() >= visibility.ordinal() )
                    {
                        foundModel = valueModel;
                    }
                }

                return false;
            }

            @Override
            public boolean visitLeave( Object visited )
                throws RuntimeException
            {
                return foundModel == null;
            }
        }
    }