/* * Copyright (c) 2008, 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.association; 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.association.Association; import org.qi4j.api.association.AssociationDescriptor; import org.qi4j.api.association.GenericAssociationInfo; import org.qi4j.api.common.MetaInfo; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.composite.Composite; import org.qi4j.api.constraint.ConstraintViolation; import org.qi4j.api.constraint.ConstraintViolationException; import org.qi4j.api.entity.Aggregated; import org.qi4j.api.entity.Queryable; import org.qi4j.api.property.Immutable; 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 static org.qi4j.functional.Iterables.empty; import static org.qi4j.functional.Iterables.first; /** * Model for an Association. * * <p>Equality is based on the Association accessor object (associated type and name), not on the QualifiedName.</p> */ public final class AssociationModel implements AssociationDescriptor, AssociationInfo, Binder, Visitable<AssociationModel> { private MetaInfo metaInfo; private Type type; private AccessibleObject accessor; private QualifiedName qualifiedName; private ValueConstraintsInstance constraints; private ValueConstraintsInstance associationConstraints; private boolean queryable; private boolean immutable; private boolean aggregated; private AssociationInfo builderInfo; public AssociationModel( AccessibleObject accessor, ValueConstraintsInstance valueConstraintsInstance, ValueConstraintsInstance associationConstraintsInstance, MetaInfo metaInfo ) { this.metaInfo = metaInfo; this.constraints = valueConstraintsInstance; this.associationConstraints = associationConstraintsInstance; this.accessor = accessor; initialize(); } private void initialize() { this.type = GenericAssociationInfo.associationTypeOf( accessor ); this.qualifiedName = QualifiedName.fromAccessor( accessor ); this.immutable = metaInfo.get( Immutable.class ) != null; this.aggregated = metaInfo.get( Aggregated.class ) != null; 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 ); } @Override public QualifiedName qualifiedName() { return qualifiedName; } @Override public Type type() { return type; } @Override public boolean isImmutable() { return immutable; } @Override public boolean isAggregated() { return aggregated; } @Override public AccessibleObject accessor() { return accessor; } @Override public boolean queryable() { return queryable; } public AssociationInfo getBuilderInfo() { return builderInfo; } @Override public <ThrowableType extends Throwable> boolean accept( Visitor<? super AssociationModel, 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( "", empty, (Member) accessor, violations ); } } } public void checkAssociationConstraints( Association<?> association ) throws ConstraintViolationException { if( associationConstraints != null ) { List<ConstraintViolation> violations = associationConstraints.checkConstraints( association ); if( !violations.isEmpty() ) { throw new ConstraintViolationException( (Composite) association.get(), (Member) accessor, violations ); } } } @Override public void bind( Resolution resolution ) throws BindingException { builderInfo = new AssociationInfo() { @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 { AssociationModel.this.checkConstraints( value ); } }; if( type instanceof TypeVariable ) { Class mainType = first( resolution.model().types() ); type = Classes.resolveTypeVariable( (TypeVariable) type, ( (Member) accessor ).getDeclaringClass(), mainType ); } } @Override public boolean equals( Object o ) { if( this == o ) { return true; } if( o == null || getClass() != o.getClass() ) { return false; } AssociationModel that = (AssociationModel) o; if( !accessor.equals( that.accessor ) ) { return false; } return true; } @Override public int hashCode() { return accessor.hashCode(); } @Override public String toString() { if( accessor instanceof Field ) { return ( (Field) accessor ).toGenericString(); } else { return ( (Method) accessor ).toGenericString(); } } }