| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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.apache.polygene.runtime.bootstrap; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.AccessibleObject; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.InvocationHandler; |
| import java.lang.reflect.Member; |
| import java.lang.reflect.Method; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.Parameter; |
| import java.lang.reflect.Proxy; |
| import java.lang.reflect.Type; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.function.Consumer; |
| import java.util.function.Predicate; |
| import java.util.stream.Collectors; |
| import java.util.stream.Stream; |
| import org.apache.polygene.api.association.Association; |
| import org.apache.polygene.api.association.GenericAssociationInfo; |
| import org.apache.polygene.api.association.ManyAssociation; |
| import org.apache.polygene.api.association.NamedAssociation; |
| import org.apache.polygene.api.common.MetaInfo; |
| import org.apache.polygene.api.common.Optional; |
| import org.apache.polygene.api.common.QualifiedName; |
| import org.apache.polygene.api.common.UseDefaults; |
| import org.apache.polygene.api.common.Visibility; |
| import org.apache.polygene.api.concern.Concerns; |
| import org.apache.polygene.api.constraint.Constraint; |
| import org.apache.polygene.api.constraint.ConstraintDeclaration; |
| import org.apache.polygene.api.constraint.Constraints; |
| import org.apache.polygene.api.constraint.Name; |
| import org.apache.polygene.api.entity.Lifecycle; |
| import org.apache.polygene.api.injection.scope.State; |
| import org.apache.polygene.api.injection.scope.This; |
| import org.apache.polygene.api.mixin.Initializable; |
| import org.apache.polygene.api.mixin.Mixins; |
| import org.apache.polygene.api.property.GenericPropertyInfo; |
| import org.apache.polygene.api.property.Immutable; |
| import org.apache.polygene.api.property.Property; |
| import org.apache.polygene.api.sideeffect.SideEffects; |
| import org.apache.polygene.api.type.HasTypes; |
| import org.apache.polygene.api.util.Annotations; |
| import org.apache.polygene.api.util.Classes; |
| import org.apache.polygene.api.util.Fields; |
| import org.apache.polygene.api.util.HierarchicalVisitorAdapter; |
| import org.apache.polygene.bootstrap.AssemblyReportException; |
| import org.apache.polygene.bootstrap.StateDeclarations; |
| import org.apache.polygene.runtime.association.AssociationModel; |
| import org.apache.polygene.runtime.association.AssociationsModel; |
| import org.apache.polygene.runtime.association.ManyAssociationModel; |
| import org.apache.polygene.runtime.association.ManyAssociationsModel; |
| import org.apache.polygene.runtime.association.NamedAssociationModel; |
| import org.apache.polygene.runtime.association.NamedAssociationsModel; |
| import org.apache.polygene.runtime.composite.AbstractConstraintModel; |
| import org.apache.polygene.runtime.composite.CompositeConstraintModel; |
| import org.apache.polygene.runtime.composite.CompositeMethodModel; |
| import org.apache.polygene.runtime.composite.CompositeMethodsModel; |
| import org.apache.polygene.runtime.composite.ConcernModel; |
| import org.apache.polygene.runtime.composite.ConcernsModel; |
| import org.apache.polygene.runtime.composite.ConstraintModel; |
| import org.apache.polygene.runtime.composite.ConstraintsModel; |
| import org.apache.polygene.runtime.composite.GenericPredicate; |
| import org.apache.polygene.runtime.composite.InterfaceDefaultMethodsMixin; |
| import org.apache.polygene.runtime.composite.MixinModel; |
| import org.apache.polygene.runtime.composite.MixinsModel; |
| import org.apache.polygene.runtime.composite.SideEffectModel; |
| import org.apache.polygene.runtime.composite.SideEffectsModel; |
| import org.apache.polygene.runtime.composite.StateModel; |
| import org.apache.polygene.runtime.composite.ValueConstraintsInstance; |
| import org.apache.polygene.runtime.composite.ValueConstraintsModel; |
| import org.apache.polygene.runtime.injection.Dependencies; |
| import org.apache.polygene.runtime.injection.DependencyModel; |
| import org.apache.polygene.runtime.property.PropertiesModel; |
| import org.apache.polygene.runtime.property.PropertyModel; |
| |
| import static java.util.stream.Stream.concat; |
| import static java.util.stream.Stream.of; |
| import static org.apache.polygene.api.composite.InvalidCompositeException.handleInvalidCompositeType; |
| import static org.apache.polygene.api.util.Annotations.isType; |
| import static org.apache.polygene.api.util.Annotations.typeHasAnnotation; |
| import static org.apache.polygene.api.util.Classes.classHierarchy; |
| import static org.apache.polygene.api.util.Classes.interfacesOf; |
| import static org.apache.polygene.api.util.Classes.isAssignableFrom; |
| import static org.apache.polygene.api.util.Classes.typeOf; |
| import static org.apache.polygene.api.util.Classes.typesOf; |
| import static org.apache.polygene.api.util.Classes.wrapperClass; |
| |
| /** |
| * Declaration of a Composite. |
| */ |
| public abstract class CompositeAssemblyImpl |
| implements HasTypes |
| { |
| List<Class<?>> concerns = new ArrayList<>(); |
| List<Class<?>> sideEffects = new ArrayList<>(); |
| List<Class<?>> mixins = new ArrayList<>(); |
| List<Class<?>> types = new ArrayList<>(); |
| MetaInfo metaInfo = new MetaInfo(); |
| Visibility visibility = Visibility.module; |
| |
| private boolean immutable; |
| PropertiesModel propertiesModel; |
| StateModel stateModel; |
| MixinsModel mixinsModel; |
| CompositeMethodsModel compositeMethodsModel; |
| private AssemblyHelper helper; |
| private StateDeclarations stateDeclarations; |
| |
| private Set<String> registeredStateNames = new HashSet<>(); |
| |
| CompositeAssemblyImpl( Class<?> mainType ) |
| { |
| types.add( mainType ); |
| } |
| |
| @Override |
| public Stream<Class<?>> types() |
| { |
| return types.stream(); |
| } |
| |
| protected StateModel createStateModel() |
| { |
| return new StateModel( propertiesModel ); |
| } |
| |
| protected MixinsModel createMixinsModel() |
| { |
| return new MixinsModel(); |
| } |
| |
| void buildComposite( AssemblyHelper helper, |
| StateDeclarations stateDeclarations |
| ) |
| { |
| this.stateDeclarations = stateDeclarations; |
| this.helper = helper; |
| for( Class<?> compositeType : types ) |
| { |
| metaInfo = new MetaInfo( metaInfo ).withAnnotations( compositeType ); |
| addAnnotationsMetaInfo( compositeType, metaInfo ); |
| } |
| |
| immutable = metaInfo.get( Immutable.class ) != null; |
| propertiesModel = new PropertiesModel(); |
| stateModel = createStateModel(); |
| mixinsModel = createMixinsModel(); |
| compositeMethodsModel = new CompositeMethodsModel( mixinsModel ); |
| |
| // Implement composite methods |
| List<Class<?>> constraintClasses = toList( constraintDeclarations( getAllTypes() ) ); |
| List<Class<?>> concernClasses = toList( concat( concerns.stream(), concernDeclarations( getAllTypes() ) ) ); |
| List<Class<?>> sideEffectClasses = toList( concat( sideEffects.stream(), sideEffectDeclarations( getAllTypes() ) ) ); |
| List<Class<?>> mixinClasses = toList( concat( mixins.stream(), mixinDeclarations( getAllTypes() ) ) ); |
| //noinspection unchecked |
| implementMixinType( types, |
| constraintClasses, |
| concernClasses, |
| sideEffectClasses, |
| mixinClasses |
| ); |
| |
| // Add state from methods and fields |
| //noinspection unchecked |
| addState( constraintClasses ); |
| } |
| |
| private List<Class<?>> toList( Stream<Class<?>> stream ) |
| { |
| return stream.collect( Collectors.toList() ); |
| } |
| |
| private void addAnnotationsMetaInfo( Class<?> type, MetaInfo compositeMetaInfo ) |
| { |
| Class[] declaredInterfaces = type.getInterfaces(); |
| for( int i = declaredInterfaces.length - 1; i >= 0; i-- ) |
| { |
| addAnnotationsMetaInfo( declaredInterfaces[ i ], compositeMetaInfo ); |
| } |
| compositeMetaInfo.withAnnotations( type ); |
| } |
| |
| private void implementMixinType( List<? extends Class<?>> types, |
| List<Class<?>> constraintClasses, |
| List<Class<?>> concernClasses, |
| List<Class<?>> sideEffectClasses, |
| List<Class<?>> mixinClasses |
| ) |
| { |
| Set<Throwable> exceptions = new HashSet<>(); |
| Set<Class<?>> thisDependencies = new HashSet<>(); |
| types.stream() |
| .peek( mixinType -> mixinsModel.addMixinType( mixinType ) ) |
| .flatMap( mixinType -> Arrays.stream( mixinType.getMethods() ) ) |
| .forEach( method -> implementMixinMethod( method, mixinClasses, constraintClasses, |
| concernClasses, |
| sideEffectClasses, |
| exceptions, |
| thisDependencies |
| ) |
| ); |
| |
| // Implement all @This dependencies that were found |
| thisDependencies.forEach( |
| thisDependency -> |
| { |
| // Add additional declarations from the @This type |
| Stream<Class<?>> typeConstraintClasses = concat( |
| constraintClasses.stream(), |
| constraintDeclarations( thisDependency ) ); |
| Stream<Class<?>> typeConcernClasses = concat( |
| concernClasses.stream(), |
| concernDeclarations( thisDependency ) ); |
| Stream<Class<?>> typeSideEffectClasses = concat( |
| sideEffectClasses.stream(), |
| sideEffectDeclarations( thisDependency ) ); |
| Stream<Class<?>> typeMixinClasses = concat( |
| mixinClasses.stream(), |
| mixinDeclarations( thisDependency ) ); |
| List<? extends Class<?>> singleton = Collections.singletonList( thisDependency ); |
| implementMixinType( singleton, |
| toList( typeConstraintClasses ), |
| toList( typeConcernClasses ), |
| toList( typeSideEffectClasses ), |
| toList( typeMixinClasses ) |
| ); |
| } ); |
| if( exceptions.size() > 0 ) |
| { |
| throw new AssemblyReportException( exceptions ); |
| } |
| } |
| |
| private void implementMixinMethod( Method method, |
| List<Class<?>> mixinClasses, |
| List<Class<?>> constraintClasses, |
| List<Class<?>> concernClasses, |
| List<Class<?>> sideEffectClasses, |
| Set<Throwable> exceptions, |
| Set<Class<?>> thisDependencies ) |
| { |
| try |
| { |
| if( !compositeMethodsModel.isImplemented( method ) |
| && !Proxy.class.equals( method.getDeclaringClass().getSuperclass() ) |
| && !Proxy.class.equals( method.getDeclaringClass() ) |
| && !Modifier.isStatic( method.getModifiers() ) ) |
| { |
| MixinModel mixinModel = implementMethod( method, mixinClasses ); |
| if( mixinModel != null ) |
| { |
| ConcernsModel concernsModel = concernsFor( |
| method, |
| mixinModel.mixinClass(), |
| concat( concernDeclarations( mixinModel.mixinClass() ), |
| concernClasses.stream() ) |
| ); |
| SideEffectsModel sideEffectsModel = sideEffectsFor( |
| method, |
| mixinModel.mixinClass(), |
| concat( sideEffectDeclarations( mixinModel.mixinClass() ), |
| sideEffectClasses.stream() ) |
| ); |
| Stream<Class<?>> concat = concat( constraintDeclarations( mixinModel.mixinClass() ), |
| constraintClasses.stream() ); |
| ConstraintsModel constraints = constraintsFor( method, toList( concat ) ); |
| CompositeMethodModel methodComposite = new CompositeMethodModel( |
| method, |
| constraints, |
| concernsModel, |
| sideEffectsModel, |
| mixinsModel |
| ); |
| |
| Stream<? extends Dependencies> source = of( methodComposite, mixinModel ); |
| source.flatMap( Dependencies::dependencies ) |
| .filter( new DependencyModel.ScopeSpecification( This.class ) ) |
| .map( DependencyModel::rawInjectionType ) |
| .forEach( thisDependencies::add ); |
| |
| interfacesOf( mixinModel.mixinClass() ) |
| .map( Classes.RAW_CLASS ) |
| .filter( clazz -> of( Initializable.class, Lifecycle.class, InvocationHandler.class ) |
| .noneMatch( c -> c.equals( clazz ) ) ) |
| .forEach( thisDependencies::add ); |
| |
| compositeMethodsModel.addMethod( methodComposite ); |
| } |
| } |
| } |
| catch( Exception e ) |
| { |
| exceptions.add( e ); |
| } |
| } |
| |
| @SuppressWarnings( "raw" ) |
| private MixinModel implementMethod( Method method, List<Class<?>> mixinDeclarations ) |
| { |
| MixinModel implementationModel = mixinsModel.mixinFor( method ); |
| if( implementationModel != null ) |
| { |
| return implementationModel; |
| } |
| Class mixinClass = findTypedImplementation( method, mixinDeclarations.stream() ); |
| if( mixinClass != null ) |
| { |
| return implementMethodWithClass( method, mixinClass ); |
| } |
| |
| // Check generic implementations |
| mixinClass = findGenericImplementation( method, concat( mixinDeclarations.stream(), of( InterfaceDefaultMethodsMixin.class ) ) ); |
| if( mixinClass != null ) |
| { |
| return implementMethodWithClass( method, mixinClass ); |
| } |
| handleInvalidCompositeType( "No implementation found for method ", null, null, null, null, method, types ); |
| return null; |
| } |
| |
| private Class<?> findTypedImplementation( final Method method, Stream<Class<?>> mixins ) |
| { |
| // Check if mixinClass implements the method. If so, check if the mixinClass is generic or if the filter passes. |
| // If a mixinClass is both generic AND non-generic at the same time, then the filter applies to the non-generic |
| // side only. |
| Predicate<Class<?>> appliesToSpec = item -> helper.appliesTo( item, method, types, item ); |
| return mixins.filter( isAssignableFrom( method.getDeclaringClass() ) |
| .and( GenericPredicate.INSTANCE.or( appliesToSpec ) ) ) |
| .findFirst().orElse( null ); |
| } |
| |
| private Class<?> findGenericImplementation( final Method method, Stream<Class<?>> mixins ) |
| { |
| // Check if mixinClass is generic and the applies-to filter passes |
| Predicate<Class<?>> appliesToSpec = item -> helper.appliesTo( item, method, types, item ); |
| return mixins.filter( GenericPredicate.INSTANCE.and( appliesToSpec ) ).findFirst().orElse( null ); |
| } |
| |
| private MixinModel implementMethodWithClass( Method method, Class mixinClass ) |
| { |
| MixinModel mixinModel = mixinsModel.getMixinModel( mixinClass ); |
| if( mixinModel == null ) |
| { |
| mixinModel = helper.getMixinModel( mixinClass ); |
| mixinsModel.addMixinModel( mixinModel ); |
| } |
| mixinsModel.addMethodMixin( method, mixinModel ); |
| return mixinModel; |
| } |
| |
| private void addState( final List<Class<?>> constraintClasses ) |
| { |
| // Add method state |
| compositeMethodsModel.accept( new HierarchicalVisitorAdapter<Object, Object, RuntimeException>() |
| { |
| @Override |
| public boolean visitEnter( Object visited ) |
| throws RuntimeException |
| { |
| if( visited instanceof CompositeMethodModel ) |
| { |
| CompositeMethodModel methodModel = (CompositeMethodModel) visited; |
| if( methodModel.method().getParameterTypes().length == 0 ) |
| { |
| addStateFor( methodModel.method(), constraintClasses ); |
| } |
| |
| return false; |
| } |
| |
| return super.visitEnter( visited ); |
| } |
| } ); |
| |
| // Add field state |
| mixinsModel.accept( new HierarchicalVisitorAdapter<Object, Object, RuntimeException>() |
| { |
| @Override |
| public boolean visitEnter( Object visited ) |
| throws RuntimeException |
| { |
| if( visited instanceof MixinModel ) |
| { |
| MixinModel model = (MixinModel) visited; |
| Consumer<Field> addState = field -> addStateFor( field, constraintClasses ); |
| Fields.FIELDS_OF.apply( model.mixinClass() ) |
| .filter( Annotations.hasAnnotation( State.class ) ) |
| .forEach( addState ); |
| return false; |
| } |
| return super.visitEnter( visited ); |
| } |
| } ); |
| } |
| |
| private void addStateFor( AccessibleObject accessor, List<Class<?>> constraintClasses ) |
| { |
| String stateName = QualifiedName.fromAccessor( accessor ).name(); |
| |
| if( registeredStateNames.contains( stateName ) ) |
| { |
| return; // Skip already registered names |
| } |
| |
| Class<?> accessorType = Classes.RAW_CLASS.apply( typeOf( accessor ) ); |
| if( Property.class.isAssignableFrom( accessorType ) ) |
| { |
| propertiesModel.addProperty( newPropertyModel( accessor, constraintClasses ) ); |
| registeredStateNames.add( stateName ); |
| } |
| else if( Association.class.isAssignableFrom( accessorType ) ) |
| { |
| associationsModel().addAssociation( newAssociationModel( accessor, constraintClasses ) ); |
| registeredStateNames.add( stateName ); |
| } |
| else if( ManyAssociation.class.isAssignableFrom( accessorType ) ) |
| { |
| manyAssociationsModel().addManyAssociation( newManyAssociationModel( accessor, constraintClasses ) ); |
| registeredStateNames.add( stateName ); |
| } |
| else if( NamedAssociation.class.isAssignableFrom( accessorType ) ) |
| { |
| namedAssociationsModel().addNamedAssociation( newNamedAssociationModel( accessor, constraintClasses ) ); |
| registeredStateNames.add( stateName ); |
| } |
| } |
| |
| protected AssociationsModel associationsModel() |
| { |
| return null; |
| } |
| |
| protected ManyAssociationsModel manyAssociationsModel() |
| { |
| return null; |
| } |
| |
| protected NamedAssociationsModel namedAssociationsModel() |
| { |
| return null; |
| } |
| |
| private PropertyModel newPropertyModel( AccessibleObject accessor, |
| List<Class<?>> constraintClasses |
| ) |
| { |
| List<Annotation> annotations = Annotations.findAccessorAndTypeAnnotationsIn( accessor ); |
| boolean optional = annotations.stream().anyMatch( isType( Optional.class ) ); |
| ValueConstraintsModel valueConstraintsModel = constraintsFor( |
| annotations.stream(), |
| GenericPropertyInfo.propertyTypeOf( accessor ), |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance valueConstraintsInstance = valueConstraintsModel.newInstance(); |
| MetaInfo metaInfo = stateDeclarations.metaInfoFor( accessor ); |
| UseDefaults useDefaultsDeclaration = metaInfo.get( UseDefaults.class ); |
| Object initialValue = stateDeclarations.initialValueOf( accessor ); |
| if( initialValue == null && useDefaultsDeclaration != null ) |
| { |
| initialValue = useDefaultsDeclaration.value(); |
| } |
| boolean useDefaults = useDefaultsDeclaration != null || stateDeclarations.useDefaults( accessor ); |
| boolean immutable = this.immutable || metaInfo.get( Immutable.class ) != null; |
| return new PropertyModel( |
| accessor, |
| immutable, |
| useDefaults, |
| valueConstraintsInstance, |
| metaInfo, |
| initialValue |
| ); |
| } |
| |
| // Model |
| private ConstraintsModel constraintsFor( Method method, |
| List<Class<?>> constraintClasses |
| ) |
| { |
| List<ValueConstraintsModel> parameterConstraintModels = Collections.emptyList(); |
| |
| Parameter[] parameters = method.getParameters(); |
| Type[] parameterTypes = method.getGenericParameterTypes(); |
| boolean constrained = false; |
| for( int i = 0; i < parameters.length; i++ ) |
| { |
| Parameter param = parameters[i]; |
| |
| Annotation[] parameterAnnotation = param.getAnnotations(); |
| |
| Name nameAnnotation = (Name) of( parameterAnnotation ).filter( isType( Name.class ) ) |
| .findFirst().orElse( null ); |
| String name = nameAnnotation == null ? param.getName() : nameAnnotation.value(); |
| boolean optional = of( parameterAnnotation ) |
| .anyMatch( isType( Optional.class ) ); |
| ValueConstraintsModel parameterConstraintsModel = constraintsFor( |
| Arrays.stream( parameterAnnotation ), |
| parameterTypes[ i ], |
| name, |
| optional, |
| constraintClasses, |
| method ); |
| if( parameterConstraintsModel.isConstrained() ) |
| { |
| constrained = true; |
| } |
| |
| if( parameterConstraintModels.isEmpty() ) |
| { |
| parameterConstraintModels = new ArrayList<>(); |
| } |
| parameterConstraintModels.add( parameterConstraintsModel ); |
| } |
| |
| if( !constrained ) |
| { |
| return new ConstraintsModel( Collections.emptyList() ); |
| } |
| else |
| { |
| return new ConstraintsModel( parameterConstraintModels ); |
| } |
| } |
| |
| private ValueConstraintsModel constraintsFor( |
| Stream<Annotation> constraintAnnotations, |
| Type valueType, |
| String name, |
| boolean optional, |
| Iterable<Class<?>> constraintClasses, |
| AccessibleObject accessor |
| ) |
| { |
| valueType = wrapperClass( valueType ); |
| |
| List<AbstractConstraintModel> constraintModels = new ArrayList<>(); |
| List<Annotation> filtered = constraintAnnotations |
| .filter( typeHasAnnotation( ConstraintDeclaration.class ) ) |
| .collect( Collectors.toList() ); |
| |
| // TODO: This massive block below should be cleaned up. |
| nextConstraint: |
| for( Annotation constraintAnnotation : filtered ) |
| { |
| // Check composite declarations first |
| Class<? extends Annotation> annotationType = constraintAnnotation.annotationType(); |
| for( Class<?> constraint : constraintClasses ) |
| { |
| @SuppressWarnings( "unchecked" ) |
| Class<? extends Constraint<?, ?>> constraintType = (Class<? extends Constraint<?, ?>>) constraint; |
| if( helper.appliesTo( constraintType, annotationType, valueType ) ) |
| { |
| constraintModels.add( new ConstraintModel( constraintAnnotation, constraintType ) ); |
| continue nextConstraint; |
| } |
| } |
| |
| // Check the annotation itself |
| Constraints constraints = annotationType.getAnnotation( Constraints.class ); |
| if( constraints != null ) |
| { |
| for( Class<? extends Constraint<?, ?>> constraintClass : constraints.value() ) |
| { |
| if( helper.appliesTo( constraintClass, annotationType, valueType ) ) |
| { |
| constraintModels.add( new ConstraintModel( constraintAnnotation, constraintClass ) ); |
| continue nextConstraint; |
| } |
| } |
| } |
| |
| // No implementation found! |
| // Check if if it's a composite constraints |
| if( Arrays.stream( annotationType.getAnnotations() ) |
| .anyMatch( typeHasAnnotation( ConstraintDeclaration.class ) ) ) |
| { |
| ValueConstraintsModel valueConstraintsModel = constraintsFor( |
| Arrays.stream( annotationType.getAnnotations() ), |
| valueType, |
| name, |
| optional, |
| constraintClasses, |
| accessor ); |
| CompositeConstraintModel compositeConstraintModel = new CompositeConstraintModel( |
| constraintAnnotation, |
| valueConstraintsModel ); |
| constraintModels.add( compositeConstraintModel ); |
| } |
| else |
| { |
| handleInvalidCompositeType( "Cannot find implementation of constraint @", null, annotationType, null, valueType, (Member) accessor, types ); |
| } |
| } |
| return new ValueConstraintsModel( constraintModels, name, optional ); |
| } |
| |
| private ConcernsModel concernsFor( Method method, |
| Class<?> mixinClass, |
| Stream<Class<?>> concernClasses |
| ) |
| { |
| List<ConcernModel> concernsFor = new ArrayList<>(); |
| concernClasses.forEach( concern -> |
| { |
| if( helper.appliesTo( concern, method, types, mixinClass ) ) |
| { |
| addConcernOrRepositionIfExists( concernsFor, helper.getConcernModel( concern ) ); |
| } |
| else |
| { |
| // Lookup method in mixin |
| if( !InvocationHandler.class.isAssignableFrom( mixinClass ) ) |
| { |
| try |
| { |
| Method mixinMethod = mixinClass.getMethod( method.getName(), method.getParameterTypes() ); |
| if( helper.appliesTo( concern, mixinMethod, types, mixinClass ) ) |
| { |
| addConcernOrRepositionIfExists( concernsFor, helper.getConcernModel( concern ) ); |
| } |
| } |
| catch( NoSuchMethodException e ) |
| { |
| // Ignore |
| } |
| } |
| } |
| } ); |
| |
| for( Annotation annotation : method.getAnnotations() ) |
| { |
| if( annotation instanceof Concerns ) |
| { |
| // Check @Concerns annotations directly on methods |
| Concerns concerns = (Concerns) annotation; |
| addConcerns( method, mixinClass, concernsFor, concerns ); |
| } |
| else |
| { |
| // Check annotations on method that have @Concerns annotations themselves |
| Concerns concerns = annotation.annotationType().getAnnotation( Concerns.class ); |
| addConcerns( method, mixinClass, concernsFor, concerns ); |
| } |
| } |
| |
| if( concernsFor.isEmpty() ) |
| { |
| return ConcernsModel.EMPTY_CONCERNS; |
| } |
| else |
| { |
| return new ConcernsModel( concernsFor ); |
| } |
| } |
| |
| private void addConcerns( Method method, Class<?> mixinClass, List<ConcernModel> concernsFor, Concerns concerns ) |
| { |
| if( concerns != null ) |
| { |
| for( Class<?> concern : concerns.value() ) |
| { |
| if( helper.appliesTo( concern, method, types, mixinClass ) ) |
| { |
| ConcernModel concernModel = helper.getConcernModel( concern ); |
| addConcernOrRepositionIfExists( concernsFor, concernModel ); |
| } |
| } |
| } |
| } |
| |
| private void addConcernOrRepositionIfExists( List<ConcernModel> concernsFor, ConcernModel concernModel ) |
| { |
| // This remove/add is to allow re-ordering of the concerns |
| concernsFor.remove( concernModel ); |
| concernsFor.add( concernModel ); |
| } |
| |
| private SideEffectsModel sideEffectsFor( Method method, |
| Class<?> mixinClass, |
| Stream<Class<?>> sideEffectClasses |
| ) |
| { |
| List<SideEffectModel> sideEffectsFor = new ArrayList<>(); |
| sideEffectClasses.forEach( sideEffect -> |
| { |
| SideEffectModel sideEffectModel = helper.getSideEffectModel( sideEffect ); |
| if( helper.appliesTo( sideEffect, method, types, mixinClass ) ) |
| { |
| addSideEffectOrRepositionIfExists( sideEffectsFor, sideEffectModel ); |
| } |
| else |
| { |
| // Lookup method in mixin |
| if( !InvocationHandler.class.isAssignableFrom( mixinClass ) ) |
| { |
| try |
| { |
| Method mixinMethod = mixinClass.getMethod( method.getName(), method.getParameterTypes() ); |
| if( helper.appliesTo( sideEffect, mixinMethod, types, mixinClass ) ) |
| { |
| addSideEffectOrRepositionIfExists( sideEffectsFor, sideEffectModel ); |
| } |
| } |
| catch( NoSuchMethodException e ) |
| { |
| // Ignore |
| } |
| } |
| } |
| } ); |
| |
| for( Annotation annotation : method.getAnnotations() ) |
| { |
| if( annotation instanceof SideEffects ) |
| { |
| // Check SideEffects annotation on method |
| SideEffects sideEffects = (SideEffects) annotation; |
| addSideEffects( method, mixinClass, sideEffectsFor, sideEffects ); |
| } |
| else |
| { |
| // Check annotations on method that have @SideEffects annotations themselves |
| SideEffects sideEffects = annotation.annotationType().getAnnotation( SideEffects.class ); |
| addSideEffects( method, mixinClass, sideEffectsFor, sideEffects ); |
| } |
| } |
| |
| if( sideEffectsFor.isEmpty() ) |
| { |
| return SideEffectsModel.EMPTY_SIDEEFFECTS; |
| } |
| else |
| { |
| return new SideEffectsModel( sideEffectsFor ); |
| } |
| } |
| |
| private void addSideEffects( Method method, Class<?> mixinClass, List<SideEffectModel> sideEffectsFor, SideEffects sideEffects ) |
| { |
| if( sideEffects != null ) |
| { |
| for( Class<?> sideEffect : sideEffects.value() ) |
| { |
| if( helper.appliesTo( sideEffect, method, types, mixinClass ) ) |
| { |
| SideEffectModel sideEffectModel = helper.getSideEffectModel( sideEffect ); |
| addSideEffectOrRepositionIfExists( sideEffectsFor, sideEffectModel ); |
| } |
| } |
| } |
| } |
| |
| private void addSideEffectOrRepositionIfExists( List<SideEffectModel> sideEffectsFor, |
| SideEffectModel sideEffectModel |
| ) |
| { |
| // This add/remove is to allow reording of SideEffects. |
| sideEffectsFor.remove( sideEffectModel ); |
| sideEffectsFor.add( sideEffectModel ); |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private Stream<Class<?>> constraintDeclarations( Class<?> type ) |
| { |
| Stream<? extends Type> types = getTypes( type ); |
| return constraintDeclarations( types ); |
| } |
| |
| private Stream<Class<?>> constraintDeclarations( Stream<? extends Type> types ) |
| { |
| return types |
| .filter( mixinType -> Annotations.annotationOn( mixinType, Constraints.class ) != null ) |
| .flatMap( mixinType -> Arrays.stream( Annotations.annotationOn( mixinType, Constraints.class ).value() ) ); |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private Stream<Class<?>> concernDeclarations( Class<?> type ) |
| { |
| Stream<? extends Type> types = getTypes( type ); |
| return concernDeclarations( types ); |
| } |
| |
| private Stream<Class<?>> concernDeclarations( Stream<? extends Type> types ) |
| { |
| return types |
| .filter( mixinType -> Annotations.annotationOn( mixinType, Concerns.class ) != null ) |
| .flatMap( mixinType -> Arrays.stream( Annotations.annotationOn( mixinType, Concerns.class ).value() ) ); |
| } |
| |
| @SuppressWarnings( "unchecked" ) |
| private Stream<Class<?>> sideEffectDeclarations( Class<?> type ) |
| { |
| Stream<? extends Type> types = getTypes( type ); |
| return sideEffectDeclarations( types ); |
| } |
| |
| private Stream<Class<?>> sideEffectDeclarations( Stream<? extends Type> types ) |
| { |
| return types |
| .filter( mixinType -> Annotations.annotationOn( mixinType, SideEffects.class ) != null ) |
| .flatMap( mixinType -> Arrays.stream( Annotations.annotationOn( mixinType, SideEffects.class ).value() ) ); |
| } |
| |
| private Stream<Class<?>> mixinDeclarations( Class<?> type ) |
| { |
| //Stream<? extends Type> types = typesOf( type ); |
| return mixinDeclarations( of( type ) ); |
| } |
| |
| private Stream<Class<?>> mixinDeclarations( Stream<? extends Class> types ) |
| { |
| return types.flatMap( this::getTypes ).flatMap( Classes::typesOf ) |
| .filter( mixinType -> Annotations.annotationOn( mixinType, Mixins.class ) != null ) |
| .flatMap( mixinType -> Arrays.stream( Annotations.annotationOn( mixinType, Mixins.class ).value() ) ); |
| } |
| |
| private Stream<Class> getAllTypes() |
| { |
| return this.types.stream().flatMap( this::getTypes ); |
| } |
| |
| private Stream<Class> getTypes( Class<?> clazz ) |
| { |
| if( clazz.isInterface() ) |
| { |
| return typesOf( clazz ).map( Classes.RAW_CLASS ); |
| } |
| else |
| { |
| return classHierarchy( clazz ).map( Classes.RAW_CLASS ); |
| } |
| } |
| |
| private AssociationModel newAssociationModel( AccessibleObject accessor, |
| List<Class<?>> constraintClasses |
| ) |
| { |
| List<Annotation> annotations = Annotations.findAccessorAndTypeAnnotationsIn( accessor ); |
| boolean optional = annotations.stream().anyMatch( isType( Optional.class ) ); |
| |
| // Constraints for Association references |
| ValueConstraintsModel constraintsModel = |
| constraintsFor( annotations.stream(), |
| GenericAssociationInfo.associationTypeOf( accessor ), |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance valueConstraintsInstance = constraintsModel.newInstance(); |
| |
| // Constraints for the Association itself |
| constraintsModel = constraintsFor( annotations.stream(), |
| Association.class, |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance associationValueConstraintsInstance = constraintsModel.newInstance(); |
| |
| MetaInfo metaInfo = stateDeclarations.metaInfoFor( accessor ); |
| return new AssociationModel( accessor, valueConstraintsInstance, associationValueConstraintsInstance, metaInfo ); |
| } |
| |
| private ManyAssociationModel newManyAssociationModel( AccessibleObject accessor, |
| List<Class<?>> constraintClasses |
| ) |
| { |
| List<Annotation> annotations = Annotations.findAccessorAndTypeAnnotationsIn( accessor ); |
| boolean optional = annotations.stream().anyMatch( isType( Optional.class ) ); |
| |
| // Constraints for entities in ManyAssociation |
| ValueConstraintsModel valueConstraintsModel = |
| constraintsFor( annotations.stream(), |
| GenericAssociationInfo.associationTypeOf( accessor ), |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance valueConstraintsInstance = valueConstraintsModel.newInstance(); |
| |
| // Constraints for the ManyAssociation itself |
| valueConstraintsModel = constraintsFor( annotations.stream(), |
| ManyAssociation.class, |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance manyValueConstraintsInstance = valueConstraintsModel.newInstance(); |
| |
| MetaInfo metaInfo = stateDeclarations.metaInfoFor( accessor ); |
| return new ManyAssociationModel( accessor, valueConstraintsInstance, manyValueConstraintsInstance, metaInfo ); |
| } |
| |
| private NamedAssociationModel newNamedAssociationModel( AccessibleObject accessor, |
| List<Class<?>> constraintClasses |
| ) |
| { |
| List<Annotation> annotations = Annotations.findAccessorAndTypeAnnotationsIn( accessor ); |
| boolean optional = annotations.stream().anyMatch( isType( Optional.class ) ); |
| |
| // Constraints for entities in NamedAssociation |
| ValueConstraintsModel valueConstraintsModel = |
| constraintsFor( annotations.stream(), |
| GenericAssociationInfo.associationTypeOf( accessor ), |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance valueConstraintsInstance = valueConstraintsModel.newInstance(); |
| |
| // Constraints for the NamedAssociation itself |
| valueConstraintsModel = constraintsFor( annotations.stream(), |
| NamedAssociation.class, |
| ( (Member) accessor ).getName(), |
| optional, |
| constraintClasses, |
| accessor ); |
| ValueConstraintsInstance namedValueConstraintsInstance = valueConstraintsModel.newInstance(); |
| |
| MetaInfo metaInfo = stateDeclarations.metaInfoFor( accessor ); |
| return new NamedAssociationModel( accessor, valueConstraintsInstance, namedValueConstraintsInstance, metaInfo ); |
| } |
| } |