# PropertyModel.java

    /*
     * Copyright (c) 2007, Rickard Öberg. All Rights Reserved.
     *
     * 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.property;

    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Field;
    import java.lang.reflect.Member;
    import java.lang.reflect.Method;
    import java.lang.reflect.Type;
    import java.lang.reflect.TypeVariable;
    import java.util.List;
    import org.qi4j.api.common.MetaInfo;
    import org.qi4j.api.common.QualifiedName;
    import org.qi4j.api.constraint.ConstraintViolation;
    import org.qi4j.api.constraint.ConstraintViolationException;
    import org.qi4j.api.entity.Queryable;
    import org.qi4j.api.property.DefaultValues;
    import org.qi4j.api.property.GenericPropertyInfo;
    import org.qi4j.api.property.InvalidPropertyTypeException;
    import org.qi4j.api.property.Property;
    import org.qi4j.api.property.PropertyDescriptor;
    import org.qi4j.api.structure.Module;
    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.bootstrap.BindingException;
    import org.qi4j.functional.Visitable;
    import org.qi4j.functional.Visitor;
    import org.qi4j.runtime.composite.ValueConstraintsInstance;
    import org.qi4j.runtime.model.Binder;
    import org.qi4j.runtime.model.Resolution;
    import org.qi4j.runtime.types.ValueTypeFactory;

    import static org.qi4j.functional.Iterables.empty;
    import static org.qi4j.functional.Iterables.first;

    /**
     * Model for a Property.
     *
     * <p>Equality is based on the Property accessor object (property type and name), not on the QualifiedName.</p>
     */
    public class PropertyModel
        implements PropertyDescriptor, PropertyInfo, Binder, Visitable<PropertyModel>
    {
        private Type type;

        private transient AccessibleObject accessor; // Interface accessor

        private final QualifiedName qualifiedName;

        private final ValueConstraintsInstance constraints; // May be null

        protected final MetaInfo metaInfo;

        private final Object initialValue;

        private final boolean useDefaults;

        private final boolean immutable;

        private ValueType valueType;

        protected PropertyInfo builderInfo;

        private final boolean queryable;

        public PropertyModel( AccessibleObject accessor,
                              boolean immutable,
                              boolean useDefaults,
                              ValueConstraintsInstance constraints,
                              MetaInfo metaInfo,
                              Object initialValue
        )
        {
            if( accessor instanceof Method )
            {
                Method m = (Method) accessor;
                if( !m.getReturnType().equals( Property.class ) )
                {
                    throw new InvalidPropertyTypeException( accessor );
                }
            }
            this.immutable = immutable;
            this.metaInfo = metaInfo;
            type = GenericPropertyInfo.propertyTypeOf( accessor );
            this.accessor = accessor;
            qualifiedName = QualifiedName.fromAccessor( accessor );

            this.useDefaults = useDefaults;

            this.initialValue = initialValue;

            this.constraints = constraints;

            final Queryable queryable = accessor.getAnnotation( Queryable.class );
            this.queryable = queryable == null || queryable.value();
        }

        @Override
        public <T> T metaInfo( Class<T> infoType )
        {
            return metaInfo.get( infoType );
        }

        public String name()
        {
            return qualifiedName.name();
        }

        @Override
        public QualifiedName qualifiedName()
        {
            return qualifiedName;
        }

        @Override
        public Type type()
        {
            return type;
        }

        @Override
        public AccessibleObject accessor()
        {
            return accessor;
        }

        @Override
        public ValueType valueType()
        {
            return valueType;
        }

        @Override
        public boolean isImmutable()
        {
            return immutable;
        }

        public PropertyInfo getBuilderInfo()
        {
            return builderInfo;
        }

        @Override
        public boolean queryable()
        {
            return queryable;
        }

        @Override
        public Object initialValue( Module module )
        {
            // Use supplied value from assembly
            Object value = initialValue;

            // Check for @UseDefaults annotation
            if( value == null && useDefaults )
            {
                if( valueType instanceof ValueCompositeType )
                {
                    return module.newValue( (Class<?>) first( valueType().types() ) );
                }
                else
                {
                    value = DefaultValues.getDefaultValueOf( type );
                }
            }

            return value;
        }

        @Override
        public void bind( Resolution resolution )
            throws BindingException
        {
            ValueTypeFactory factory = ValueTypeFactory.instance();
            Class<?> declaringClass = ( (Member) accessor() ).getDeclaringClass();
            Class<?> mainType = first( resolution.model().types() );
            Serialization.Variant variant = findVariant();
            valueType = factory.newValueType( type(), declaringClass, mainType, resolution.layer(), resolution.module(), variant );
            builderInfo = new BuilderPropertyInfo();
            if( type instanceof TypeVariable )
            {
                type = Classes.resolveTypeVariable( (TypeVariable) type, declaringClass, mainType );
            }
        }

        private Serialization.Variant findVariant()
        {
            Serialization serialization = metaInfo.get( Serialization.class );
            Serialization.Variant variant = null;
            if( serialization != null )
            {
                variant = serialization.value();
            }
            if( variant == null )
            {
                variant = Serialization.Variant.entry;
            }
            return variant;
        }

        @Override
        public <ThrowableType extends Throwable> boolean accept( Visitor<? super PropertyModel, ThrowableType> visitor )
            throws ThrowableType
        {
            return visitor.visit( this );
        }

        @Override
        public void checkConstraints( Object value )
            throws ConstraintViolationException
        {
            if( constraints != null )
            {
                List<ConstraintViolation> violations = constraints.checkConstraints( value );
                if( !violations.isEmpty() )
                {
                    Iterable<Class<?>> empty = empty();
                    throw new ConstraintViolationException( "<new instance>", empty, ( (Member) accessor ), violations );
                }
            }
        }

        @Override
        public boolean equals( Object o )
        {
            if( this == o )
            {
                return true;
            }
            if( o == null || getClass() != o.getClass() )
            {
                return false;
            }

            PropertyModel that = (PropertyModel) o;
            return accessor.equals( that.accessor );
        }

        @Override
        public int hashCode()
        {
            return accessor.hashCode();
        }

        @Override
        public String toString()
        {
            if( accessor instanceof Field )
            {
                return ( (Field) accessor ).toGenericString();
            }
            else
            {
                return ( (Method) accessor ).toGenericString();
            }
        }

        private class BuilderPropertyInfo implements PropertyInfo
        {
            @Override
            public boolean isImmutable()
            {
                return false;
            }

            @Override
            public QualifiedName qualifiedName()
            {
                return qualifiedName;
            }

            @Override
            public Type type()
            {
                return type;
            }

            @Override
            public void checkConstraints( Object value )
                throws ConstraintViolationException
            {
                if( constraints != null )
                {
                    List<ConstraintViolation> violations = constraints.checkConstraints( value );
                    if( !violations.isEmpty() )
                    {
                        Iterable<Class<?>> empty = empty();
                        throw new ConstraintViolationException( "<new instance>", empty, ( (Member) accessor ), violations );
                    }
                }
            }
        }
    }