| /* |
| * 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.apache.zest.runtime.composite; |
| |
| import java.lang.annotation.Annotation; |
| import java.lang.reflect.Constructor; |
| import java.lang.reflect.Modifier; |
| import java.lang.reflect.ParameterizedType; |
| import java.lang.reflect.Type; |
| import java.lang.reflect.TypeVariable; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.List; |
| import org.apache.zest.api.common.ConstructionException; |
| import org.apache.zest.api.composite.CompositeDescriptor; |
| import org.apache.zest.api.composite.InvalidCompositeException; |
| import org.apache.zest.api.injection.InjectionScope; |
| import org.apache.zest.api.injection.scope.Uses; |
| import org.apache.zest.api.util.Annotations; |
| import org.apache.zest.api.util.Classes; |
| import org.apache.zest.bootstrap.BindingException; |
| import org.apache.zest.functional.Function; |
| import org.apache.zest.functional.HierarchicalVisitor; |
| import org.apache.zest.functional.HierarchicalVisitorAdapter; |
| import org.apache.zest.functional.Iterables; |
| import org.apache.zest.functional.Specifications; |
| import org.apache.zest.functional.VisitableHierarchy; |
| import org.apache.zest.runtime.injection.Dependencies; |
| import org.apache.zest.runtime.injection.DependencyModel; |
| import org.apache.zest.runtime.injection.InjectedParametersModel; |
| import org.apache.zest.runtime.injection.InjectionContext; |
| import org.apache.zest.runtime.injection.ParameterizedTypeInstance; |
| import org.apache.zest.runtime.model.Binder; |
| import org.apache.zest.runtime.model.Resolution; |
| |
| import static org.apache.zest.functional.Iterables.filter; |
| import static org.apache.zest.functional.Iterables.first; |
| import static org.apache.zest.functional.Iterables.iterable; |
| |
| /** |
| * JAVADOC |
| */ |
| public final class ConstructorsModel |
| implements Binder, Dependencies, VisitableHierarchy<Object, Object> |
| { |
| @SuppressWarnings( "raw" ) |
| private final Class fragmentClass; |
| private final List<ConstructorModel> constructorModels; |
| private List<ConstructorModel> boundConstructors; |
| |
| @SuppressWarnings( { "raw", "unchecked" } ) |
| public ConstructorsModel( Class fragmentClass ) |
| { |
| this.fragmentClass = fragmentClass; |
| validate( fragmentClass ); |
| constructorModels = new ArrayList<>(); |
| Constructor[] realConstructors = this.fragmentClass.getDeclaredConstructors(); |
| Class injectionClass = FragmentClassLoader.getSourceClass( fragmentClass ); |
| for( Constructor constructor : realConstructors ) |
| { |
| constructor.setAccessible( true ); |
| try |
| { |
| Constructor injectionConstructor = injectionClass.getDeclaredConstructor( constructor.getParameterTypes() ); |
| injectionConstructor.setAccessible( true ); |
| ConstructorModel constructorModel = newConstructorModel( this.fragmentClass, constructor, |
| injectionConstructor ); |
| if( constructorModel != null ) |
| { |
| constructorModels.add( constructorModel ); |
| } |
| } |
| catch( NoSuchMethodException e ) |
| { |
| // Ignore and continue |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| @SuppressWarnings( "raw" ) |
| private void validate( Class fragmentClass ) |
| { |
| // Ensure that the fragment class is not an inner class, in which case we should give a reasonable exception |
| if( fragmentClass.getDeclaringClass() == null ) |
| { |
| return; |
| } |
| if( Modifier.isStatic( fragmentClass.getModifiers() ) ) |
| { |
| return; |
| } |
| throw new InvalidCompositeException( "Inner classes can not be used. Use static nested classes instead: " + fragmentClass ); |
| } |
| |
| @Override |
| public Iterable<DependencyModel> dependencies() |
| { |
| Function<ConstructorModel, Iterable<DependencyModel>> constructorDependencies = new Function<ConstructorModel, Iterable<DependencyModel>>() |
| { |
| @Override |
| public Iterable<DependencyModel> map( ConstructorModel constructorModel ) |
| { |
| return constructorModel.dependencies(); |
| } |
| }; |
| |
| return Iterables.flattenIterables( Iterables.map( constructorDependencies, boundConstructors == null ? constructorModels : boundConstructors ) ); |
| } |
| |
| @SuppressWarnings( "raw" ) |
| private ConstructorModel newConstructorModel( Class fragmentClass, |
| Constructor realConstructor, |
| Constructor injectedConstructor |
| ) |
| { |
| int idx = 0; |
| InjectedParametersModel parameters = new InjectedParametersModel(); |
| Annotation[][] parameterAnnotations = injectedConstructor.getParameterAnnotations(); |
| for( Type type : injectedConstructor.getGenericParameterTypes() ) |
| { |
| Annotation injectionAnnotation = first( |
| filter( Specifications.translate( Annotations.type(), Annotations.hasAnnotation( InjectionScope.class ) ), iterable( parameterAnnotations[ idx ] ) ) ); |
| |
| if( injectionAnnotation == null ) |
| { |
| if( fragmentClass.getSuperclass().isMemberClass() ) |
| { |
| injectionAnnotation = new Uses() |
| { |
| @Override |
| public Class<? extends Annotation> annotationType() |
| { |
| return Uses.class; |
| } |
| }; |
| } |
| else |
| { |
| return null; // invalid constructor parameter |
| } |
| } |
| |
| boolean optional = DependencyModel.isOptional( injectionAnnotation, parameterAnnotations[ idx ] ); |
| |
| Type genericType = type; |
| if( genericType instanceof ParameterizedType ) |
| { |
| genericType = new ParameterizedTypeInstance( ( (ParameterizedType) genericType ).getActualTypeArguments(), ( (ParameterizedType) genericType ) |
| .getRawType(), ( (ParameterizedType) genericType ).getOwnerType() ); |
| |
| for( int i = 0; i < ( (ParameterizedType) genericType ).getActualTypeArguments().length; i++ ) |
| { |
| Type typeArg = ( (ParameterizedType) genericType ).getActualTypeArguments()[ i ]; |
| if( typeArg instanceof TypeVariable ) |
| { |
| typeArg = Classes.resolveTypeVariable( (TypeVariable) typeArg, realConstructor.getDeclaringClass(), fragmentClass ); |
| ( (ParameterizedType) genericType ).getActualTypeArguments()[ i ] = typeArg; |
| } |
| } |
| } |
| |
| DependencyModel dependencyModel = new DependencyModel( injectionAnnotation, genericType, fragmentClass, optional, |
| parameterAnnotations[ idx ] ); |
| parameters.addDependency( dependencyModel ); |
| idx++; |
| } |
| return new ConstructorModel( realConstructor, parameters ); |
| } |
| |
| @Override |
| public <ThrowableType extends Throwable> boolean accept( HierarchicalVisitor<? super Object, ? super Object, ThrowableType> visitor ) |
| throws ThrowableType |
| { |
| if( visitor.visitEnter( this ) ) |
| { |
| if( boundConstructors != null ) |
| { |
| for( ConstructorModel constructorModel : boundConstructors ) |
| { |
| if( !constructorModel.accept( visitor ) ) |
| { |
| break; |
| } |
| } |
| } |
| else |
| { |
| for( ConstructorModel constructorModel : constructorModels ) |
| { |
| if( !constructorModel.accept( visitor ) ) |
| { |
| break; |
| } |
| } |
| } |
| } |
| return visitor.visitLeave( this ); |
| } |
| |
| // Binding |
| @Override |
| public void bind( final Resolution resolution ) |
| throws BindingException |
| { |
| boundConstructors = new ArrayList<>(); |
| for( ConstructorModel constructorModel : constructorModels ) |
| { |
| try |
| { |
| constructorModel.accept( new HierarchicalVisitorAdapter<Object, Object, BindingException>() |
| { |
| @Override |
| public boolean visit( Object visitor ) |
| throws BindingException |
| { |
| if( visitor instanceof Binder ) |
| { |
| ( (Binder) visitor ).bind( resolution ); |
| } |
| return true; |
| } |
| } ); |
| boundConstructors.add( constructorModel ); |
| } |
| catch( Exception e ) |
| { |
| // Ignore |
| e.printStackTrace(); |
| } |
| } |
| |
| if( boundConstructors.isEmpty() ) |
| { |
| StringBuilder messageBuilder = new StringBuilder( "Found no constructor that could be bound: " ); |
| if( resolution.model() instanceof CompositeDescriptor ) |
| { |
| messageBuilder.append( fragmentClass.getName() ) |
| .append( " in " ) |
| .append( resolution.model().toString() ); |
| } |
| else |
| { |
| messageBuilder.append( resolution.model().toString() ); |
| } |
| |
| if( messageBuilder.indexOf( "$" ) >= 0 ) |
| { |
| // This could be ok if instance is created manually |
| return; |
| // messageBuilder.append( "\nInner classes can not be used." ); |
| } |
| String message = messageBuilder.toString(); |
| throw new BindingException( message ); |
| } |
| |
| // Sort based on parameter count |
| Collections.sort( boundConstructors, new Comparator<ConstructorModel>() |
| { |
| @Override |
| public int compare( ConstructorModel o1, ConstructorModel o2 ) |
| { |
| Integer model2ParametersCount = o2.constructor().getParameterTypes().length; |
| int model1ParametersCount = o1.constructor().getParameterTypes().length; |
| return model2ParametersCount.compareTo( model1ParametersCount ); |
| } |
| } ); |
| } |
| |
| public Object newInstance( InjectionContext injectionContext ) |
| { |
| // Try all bound constructors, in order |
| ConstructionException exception = null; |
| for( ConstructorModel constructorModel : boundConstructors ) |
| { |
| try |
| { |
| return constructorModel.newInstance( injectionContext ); |
| } |
| catch( ConstructionException e ) |
| { |
| exception = e; |
| } |
| } |
| |
| throw exception; |
| } |
| } |