blob: 91b49ff5032ccaf8970c4a866b5e423411495558 [file] [log] [blame]
/*
* Copyright (c) 2008-2012, Rickard Öberg.
* Copyright (c) 2008-2012, Niclas Hedhman.
* Copyright (c) 2012, Paul Merlin.
*
* 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.structure;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.lang.reflect.WildcardType;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.qi4j.api.composite.AmbiguousTypeException;
import org.qi4j.api.composite.ModelDescriptor;
import org.qi4j.api.service.NoSuchServiceException;
import org.qi4j.api.service.ServiceReference;
import org.qi4j.functional.Function;
import org.qi4j.functional.Specification;
import org.qi4j.functional.Specifications;
import org.qi4j.runtime.composite.TransientModel;
import org.qi4j.runtime.entity.EntityModel;
import org.qi4j.runtime.object.ObjectModel;
import org.qi4j.runtime.value.ValueModel;
import org.qi4j.spi.module.ModelModule;
import static org.qi4j.api.common.Visibility.application;
import static org.qi4j.api.common.Visibility.layer;
import static org.qi4j.api.common.Visibility.module;
import static org.qi4j.api.util.Classes.RAW_CLASS;
import static org.qi4j.api.util.Classes.interfacesOf;
import static org.qi4j.functional.Iterables.cast;
import static org.qi4j.functional.Iterables.filter;
import static org.qi4j.functional.Iterables.first;
import static org.qi4j.functional.Iterables.flatten;
import static org.qi4j.functional.Iterables.flattenIterables;
import static org.qi4j.functional.Iterables.iterable;
import static org.qi4j.functional.Iterables.toList;
import static org.qi4j.functional.Iterables.unique;
/**
* Central place for Composite Type lookups.
*/
public class TypeLookup
{
// Constructor parameters
private final ModuleInstance moduleInstance;
// Eager instance objects
private final Map<Class<?>, ModelModule<ObjectModel>> objectModels;
private final Map<Class<?>, ModelModule<TransientModel>> transientModels;
private final Map<Class<?>, ModelModule<ValueModel>> valueModels;
private final Map<Class<?>, Iterable<ModelModule<EntityModel>>> allEntityModels;
private final Map<Class<?>, ModelModule<EntityModel>> unambiguousEntityModels;
private final Map<Type, ServiceReference<?>> serviceReferences;
private final Map<Type, Iterable<ServiceReference<?>>> servicesReferences;
/**
* Create a new TypeLookup bound to the given ModuleInstance.
*
* @param moduleInstance ModuleInstance bound to this TypeLookup
*/
/* package */ TypeLookup( ModuleInstance moduleInstance )
{
// Constructor parameters
this.moduleInstance = moduleInstance;
// Eager instance objects
objectModels = new ConcurrentHashMap<>();
transientModels = new ConcurrentHashMap<>();
valueModels = new ConcurrentHashMap<>();
allEntityModels = new ConcurrentHashMap<>();
unambiguousEntityModels = new ConcurrentHashMap<>();
serviceReferences = new ConcurrentHashMap<>();
servicesReferences = new ConcurrentHashMap<>();
}
/**
* Lookup first Object Model matching the given Type.
*
* <p>First, if Object Models exactly match the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>exact</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Second, if Object Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>assignable</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* @param type Looked up Type
*
* @return First matching Object Model
*/
@SuppressWarnings( { "raw", "unchecked" } )
/* package */ ModelModule<ObjectModel> lookupObjectModel( final Class type )
{
ModelModule<ObjectModel> model = objectModels.get( type );
if( model == null )
{
// Unambiguously and lazily resolve ObjectModel
Iterable<ModelModule<ObjectModel>> flatten = flatten(
ambiguousTypeCheck( type,
findModels( new ExactTypeLookupSpecification( type ),
moduleInstance.visibleObjects( module ),
moduleInstance.layerInstance().visibleObjects( layer ),
moduleInstance.layerInstance().visibleObjects( application ),
moduleInstance.layerInstance()
.usedLayersInstance()
.visibleObjects() ) ),
ambiguousTypeCheck( type,
findModels( new AssignableTypeLookupSpecification( type ),
moduleInstance.visibleObjects( module ),
moduleInstance.layerInstance().visibleObjects( layer ),
moduleInstance.layerInstance().visibleObjects( application ),
moduleInstance.layerInstance()
.usedLayersInstance()
.visibleObjects() ) ) );
model = first( flatten );
if( model != null )
{
objectModels.put( type, model );
}
}
return model;
}
/**
* Lookup first Transient Model matching the given Type.
*
* <p>First, if Transient Models exactly match the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>exact</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Second, if Transient Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>assignable</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* @param type Looked up Type
*
* @return First matching Transient Model
*/
@SuppressWarnings( { "raw", "unchecked" } )
/* package */ ModelModule<TransientModel> lookupTransientModel( final Class type )
{
ModelModule<TransientModel> model = transientModels.get( type );
if( model == null )
{
// Unambiguously and lazily resolve TransientModel
Iterable<ModelModule<TransientModel>> allModels = flatten(
ambiguousTypeCheck( type,
findModels( new ExactTypeLookupSpecification( type ),
moduleInstance.visibleTransients( module ),
moduleInstance.layerInstance().visibleTransients( layer ),
moduleInstance.layerInstance().visibleTransients( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleTransients()
)
),
ambiguousTypeCheck( type,
findModels( new AssignableTypeLookupSpecification( type ),
moduleInstance.visibleTransients( module ),
moduleInstance.layerInstance().visibleTransients( layer ),
moduleInstance.layerInstance().visibleTransients( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleTransients()
)
)
);
model = first( allModels );
if( model != null )
{
transientModels.put( type, model );
}
}
return model;
}
/**
* Lookup first Value Model matching the given Type.
*
* <p>First, if Value Models exactly match the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>exact</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Second, if Value Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>assignable</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* @param type Looked up Type
*
* @return First matching Value Model
*/
@SuppressWarnings( { "raw", "unchecked" } )
public ModelModule<ValueModel> lookupValueModel( final Class type )
{
ModelModule<ValueModel> model = valueModels.get( type );
if( model == null )
{
// Unambiguously and lazily resolve ValueModel
Iterable<ModelModule<ValueModel>> flatten = flatten(
ambiguousTypeCheck( type,
findModels( new ExactTypeLookupSpecification( type ),
moduleInstance.visibleValues( module ),
moduleInstance.layerInstance().visibleValues( layer ),
moduleInstance.layerInstance().visibleValues( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleValues() ) ),
ambiguousTypeCheck( type,
findModels( new AssignableTypeLookupSpecification( type ),
moduleInstance.visibleValues( module ),
moduleInstance.layerInstance().visibleValues( layer ),
moduleInstance.layerInstance().visibleValues( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleValues()
)
)
);
model = first( flatten );
if( model != null )
{
valueModels.put( type, model );
}
}
return model;
}
/**
* Lookup first Entity Model matching the given Type.
*
* <p>First, if Entity Models exactly match the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>exact</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Second, if Entity Models match a type assignable to the given type, the closest one (Visibility then Assembly order) is returned.
* Multiple <b>assignable</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* <p><b>Should be used for creational use cases only.</b> For non-creational use cases see
* {@link #lookupEntityModels(java.lang.Class)}.</p>
*
* @param type Looked up Type
*
* @return First matching Entity Model
*/
@SuppressWarnings( { "raw", "unchecked" } )
/* package */ ModelModule<EntityModel> lookupEntityModel( final Class type )
{
ModelModule<EntityModel> model = unambiguousEntityModels.get( type );
if( model == null )
{
// Unambiguously and lazily resolve EntityModels
Iterable<ModelModule<EntityModel>> allModels = flatten(
ambiguousTypeCheck( type,
findModels( new ExactTypeLookupSpecification( type ),
moduleInstance.visibleEntities( module ),
moduleInstance.layerInstance().visibleEntities( layer ),
moduleInstance.layerInstance().visibleEntities( application ),
moduleInstance.layerInstance()
.usedLayersInstance()
.visibleEntities() ) ),
ambiguousTypeCheck( type,
findModels( new AssignableTypeLookupSpecification( type ),
moduleInstance.visibleEntities( module ),
moduleInstance.layerInstance().visibleEntities( layer ),
moduleInstance.layerInstance().visibleEntities( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleEntities()
)
)
);
model = first( allModels );
if( model != null )
{
unambiguousEntityModels.put( type, model );
}
}
return model;
}
/**
* Lookup all Entity Models matching the given Type.
*
* <p>Returned Iterable contains, in order, Entity Models that: </p>
*
* <ul>
* <li>exactly match the given type, in Visibility then Assembly order ;</li>
* <li>match a type assignable to the given type, in Visibility then Assembly order.</li>
* </ul>
*
* <p>Multiple <b>exact</b> matches with the same Visibility are <b>forbidden</b> and result in an AmbiguousTypeException.</p>
* <p>Multiple <b>assignable</b> matches are <b>allowed</b> to enable polymorphic fetches and queries.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* <p><b>Should be used for non-creational use cases only.</b> For creational use cases see
* {@link #lookupEntityModel(java.lang.Class)}.</p>
*
* @param type Looked up Type
*
* @return All matching Entity Models
*/
@SuppressWarnings( { "raw", "unchecked" } )
/* package */ Iterable<ModelModule<EntityModel>> lookupEntityModels( final Class type )
{
Iterable<ModelModule<EntityModel>> models = allEntityModels.get( type );
if( models == null )
{
// Ambiguously and lasily resolve EntityModels
Iterable<ModelModule<EntityModel>> matchingEntityModels = flatten(
ambiguousTypeCheck( type,
findModels( new ExactTypeLookupSpecification( type ),
moduleInstance.visibleEntities( module ),
moduleInstance.layerInstance().visibleEntities( layer ),
moduleInstance.layerInstance().visibleEntities( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleEntities()
)
),
findModels( new AssignableTypeLookupSpecification( type ),
moduleInstance.visibleEntities( module ),
moduleInstance.layerInstance().visibleEntities( layer ),
moduleInstance.layerInstance().visibleEntities( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleEntities() ) );
// Don't return the same EntityModel multiple times
matchingEntityModels = unique( matchingEntityModels );
models = toList( matchingEntityModels );
allEntityModels.put( type, models );
}
return models;
}
/**
* Lookup first ServiceReference matching the given Type.
*
* <p>Type lookup is done lazily and cached.</p>
*
* <p>See {@link #lookupServiceReferences(java.lang.reflect.Type)}.</p>
*
* @param <T> Service Type
* @param serviceType Looked up Type
*
* @return First matching ServiceReference
*/
/* package */
@SuppressWarnings( "unchecked" )
<T> ServiceReference<T> lookupServiceReference( Type serviceType )
{
ServiceReference<?> serviceReference = serviceReferences.get( serviceType );
if( serviceReference == null )
{
// Lazily resolve ServiceReference
serviceReference = first( lookupServiceReferences( serviceType ) );
if( serviceReference != null )
{
serviceReferences.put( serviceType, serviceReference );
}
}
if( serviceReference == null )
{
throw new NoSuchServiceException( RAW_CLASS.map( serviceType ).getName(), moduleInstance.name() );
}
return (ServiceReference<T>) serviceReference;
}
/**
* Lookup all ServiceReferences matching the given Type.
*
* <p>Returned Iterable contains, in order, ServiceReferences that: </p>
*
* <ul>
* <li>exactly match the given type, in Visibility then Assembly order ;</li>
* <li>match a type assignable to the given type, in Visibility then Assembly order.</li>
* </ul>
*
* <p>Multiple <b>exact</b> matches with the same Visibility are <b>allowed</b> to enable polymorphic lookup/injection.</p>
* <p>Multiple <b>assignable</b> matches with the same Visibility are <b>allowed</b> for the very same reason.</p>
*
* <p>Type lookup is done lazily and cached.</p>
*
* @param <T> Service Type
* @param serviceType Looked up Type
*
* @return All matching ServiceReferences
*/
@SuppressWarnings( "unchecked" )
/* package */ <T> Iterable<ServiceReference<T>> lookupServiceReferences( final Type serviceType )
{
Iterable<ServiceReference<?>> serviceRefs = servicesReferences.get( serviceType );
if( serviceRefs == null )
{
// Lazily resolve ServicesReferences
Iterable<ServiceReference<?>> matchingServices = flatten(
findServiceReferences( new ExactTypeLookupSpecification( serviceType ),
moduleInstance.visibleServices( module ),
moduleInstance.layerInstance().visibleServices( layer ),
moduleInstance.layerInstance().visibleServices( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleServices() ),
findServiceReferences( new AssignableTypeLookupSpecification( serviceType ),
moduleInstance.visibleServices( module ),
moduleInstance.layerInstance().visibleServices( layer ),
moduleInstance.layerInstance().visibleServices( application ),
moduleInstance.layerInstance().usedLayersInstance().visibleServices() ) );
// Don't return the same ServiceReference multiple times
matchingServices = unique( matchingServices );
serviceRefs = toList( matchingServices );
servicesReferences.put( serviceType, serviceRefs );
}
return cast( serviceRefs );
}
@SuppressWarnings( { "raw", "unchecked" } )
private static <T extends ModelDescriptor> Iterable<ModelModule<T>> findModels( Specification<Iterable<Class<?>>> specification,
Iterable<ModelModule<T>>... models
)
{
Specification<ModelModule<T>> spec = Specifications.translate( new ModelModuleTypesFunction(), specification );
Iterable<ModelModule<T>> flattened = flattenIterables( iterable( models ) );
return filter( spec, flattened );
}
@SuppressWarnings( { "raw", "unchecked" } )
private static Iterable<ServiceReference<?>> findServiceReferences( Specification<Iterable<Class<?>>> specification,
Iterable<ServiceReference<?>>... references
)
{
Specification<ServiceReference<?>> spec = Specifications.translate( new ServiceReferenceTypesFunction(), specification );
Iterable<ServiceReference<?>> flattened = flattenIterables( iterable( references ) );
return filter( spec, flattened );
}
/**
* Check if the list of models contains several ones with the same visibility. If yes, then
* throw an AmbiguousTypeException
*/
@SuppressWarnings( "raw" )
private static <T extends ModelDescriptor> Iterable<ModelModule<T>> ambiguousTypeCheck( final Class type,
final Iterable<ModelModule<T>> models
)
{
return new Iterable<ModelModule<T>>()
{
@Override
public Iterator<ModelModule<T>> iterator()
{
ModelModule<T> current = null;
List<ModelModule<T>> ambiguous = null;
List<ModelModule<T>> results = new ArrayList<>();
for( ModelModule<T> model : models )
{
if( current != null && !model.equals( current ) )
{
if( model.model().visibility() == current.model().visibility() )
{
if( ambiguous == null )
{
ambiguous = new ArrayList<>();
}
ambiguous.add( model );
}
}
else
{
current = model;
}
results.add( model );
}
if( ambiguous != null )
{
// Check if we had any ambiguities
ambiguous.add( current );
throw new AmbiguousTypeException( "More than one type matches " + type.getName() + ":" + ambiguous );
}
// Ambiguity check done, and no ambiguities found. Return results
return results.iterator();
}
};
}
private static class ModelModuleTypesFunction<T extends ModelDescriptor>
implements Function<ModelModule<T>, Iterable<Class<?>>>
{
@Override
public Iterable<Class<?>> map( ModelModule<T> modelModule )
{
return modelModule.model().types();
}
}
private static class ServiceReferenceTypesFunction
implements Function<ServiceReference<?>, Iterable<Class<?>>>
{
@Override
public Iterable<Class<?>> map( ServiceReference<?> serviceReference )
{
return serviceReference.types();
}
}
private static abstract class AbstractTypeLookupSpecification
implements Specification<Iterable<Class<?>>>
{
protected final Type lookedUpType;
private AbstractTypeLookupSpecification( Type lookedUpType )
{
this.lookedUpType = lookedUpType;
}
@Override
public final boolean satisfiedBy( Iterable<Class<?>> types )
{
if( lookedUpType instanceof Class )
{
// Straight class assignability check
return checkClassMatch( types, (Class) lookedUpType );
}
else
{
if( lookedUpType instanceof ParameterizedType )
{
// Foo<Bar> check
// First check Foo
ParameterizedType parameterizedType = (ParameterizedType) lookedUpType;
if( !checkClassMatch( types, (Class) parameterizedType.getRawType() ) )
{
return false;
}
// Then check Bar
for( Type intf : interfacesOf( types ) )
{
if( intf.equals( lookedUpType ) )
{
// All parameters are the same - ok!
return true;
}
}
return false;
}
else if( lookedUpType instanceof WildcardType )
{
return true;
}
return false;
}
}
private boolean checkClassMatch( Iterable<Class<?>> candidates, Class<?> lookedUpType )
{
for( Class<?> candidate : candidates )
{
if( checkClassMatch( candidate, lookedUpType ) )
{
return true;
}
}
return false;
}
protected abstract boolean checkClassMatch( Class<?> candidate, Class<?> lookedUpType );
}
private static final class ExactTypeLookupSpecification
extends AbstractTypeLookupSpecification
{
private ExactTypeLookupSpecification( Type lookedupType )
{
super( lookedupType );
}
@Override
protected boolean checkClassMatch( Class<?> candidate, Class<?> lookedUpType )
{
return candidate.equals( lookedUpType );
}
}
private static final class AssignableTypeLookupSpecification
extends AbstractTypeLookupSpecification
{
private AssignableTypeLookupSpecification( Type lookedupType )
{
super( lookedupType );
}
@Override
protected boolean checkClassMatch( Class<?> candidate, Class<?> lookedUpType )
{
return !candidate.equals( lookedUpType ) && lookedUpType.isAssignableFrom( candidate );
}
}
}