org.apache.myfaces.trinidadinternal.convert.GenericConverterFactory Maven / Gradle / Ivy
Show all versions of trinidad-impl Show documentation
/*
* 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.myfaces.trinidadinternal.convert;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.faces.context.ExternalContext;
import javax.faces.context.FacesContext;
import org.apache.myfaces.trinidad.logging.TrinidadLogger;
/**
* A factory of GenericConverters.
* GenericConverters may be registered with this factory.
* The factory supports converting between the types supported
* by each individual converter. The factory also supports converting
* between types supported by combining individual converters.
*
*/
public class GenericConverterFactory
{
private GenericConverterFactory()
{
_cache = new ConcurrentHashMap(16);
_converters = new ArrayList(3);
_reverseDiscoveryConverters = new ArrayList(2);
registerConverter(new SqlConverter());
registerConverter(new BaseConverter());
registerReverseDiscoveryConverter(new ReflectionConverter());
}
/**
* Gets a converter that is capable of converting from the given
* sourceType to the given targetType.
* This method first searches to see if any of the registered converters
* are capable of making this conversion alone. If one is found, it is
* returned. Otherwise, this method will search to see if some combination
* of registered converters can be used to perform this conversion.
* If so, a composite converter will be returned.
*
* The returned converter (or lack thereof) is cached so that subsequent
* requests for the same source and target types will be fast.
* @return null if there is no such converter.
*/
public TypeConverter getConverter(Class> sourceType, Class> targetType)
{
Key key = new Key(sourceType, targetType);
// check the cache first:
Object cached = _cache.get(key);
if (cached != null)
{
return (cached == _NULL) ? null : (TypeConverter) cached;
}
// we are going to start searching to see if some chain of converters
// can be used to perform this conversion.
// initial node in chain:
Node start = new Node(null, null, sourceType);
LinkedList sourcesToBeSearched = new LinkedList();
sourcesToBeSearched.add(start);
// cache to store all the classes we've tested already. This is to
// avoid our chains from looping indefinitely:
Set> cache = new HashSet>(16);
// Try to find a converter chain without "reverse discovery" type converetrs.
// Our particular implementation of the "reverse discovery" type converter uses Java Reflection,
// so it is better to use a chain of normal generic converters
TypeConverter converter = _findConverter(sourcesToBeSearched, targetType, cache, false);
// If we failed, try to use "reverse discovery" converters
if (converter == null && _reverseDiscoveryConverters.size() > 0)
{
// We will use a "reverse discovery" type converter only if target-to-source conversion
// is available without the use of "reverse discovery" type converters. This is done to ensure
// we do not find a converter dynamically for the target type when the source type is
// not convertable from the target type (converter's availability has to be 'reflective').
// Regular generic converters are pre-defined, so they normally do not have this problem.
TypeConverter reverseConv = null;
reverseConv = _cache.get(new Key(targetType, sourceType));
if (reverseConv == null)
{
cache.clear();
sourcesToBeSearched.add( new Node(null, null, targetType));
reverseConv = _findConverter(sourcesToBeSearched, sourceType, cache, false);
}
if (reverseConv != null)
{
cache.clear();
sourcesToBeSearched.clear();
sourcesToBeSearched.add(start);
converter = _findConverter(sourcesToBeSearched, targetType, cache, true);
}
}
if (converter == null)
{
// cache the fact that no such converter exists:
_cache.put(key, _NULL);
}
else
{
_cache.put(key, converter);
}
return converter;
}
/**
* tries to find a converter, or create a chain of converters that can
* convert from one of the given sourceTypes to the specified targetType.
* @param sourcesToBeSearched each element is a Node. Each Node is a pairing of
* sourceType and the chain of converters needed to produce this sourceType.
* @param targetType the type that is needed
* @param cache used to record which classes we've searched already.
* @param useRevserseDiscovery true if "reverse discovery" converters should be used, false otherwise
* @return null if no converter was found.
*/
private TypeConverter _findConverter(
LinkedList sourcesToBeSearched,
Class> targetType,
Set> cache,
boolean useRevserseDiscovery)
{
while(!sourcesToBeSearched.isEmpty())
{
Node source = sourcesToBeSearched.removeFirst();
TypeConverter match = null;
// loop through all the converters and see what types they can turn
// the current sourceType into
// (the current sourceType is source.targetType):
for(int j=0,jsz=_converters.size(); j sourcesToBeSearched,
Node currentSource,
GenericConverter currentConverter,
Class> searchType,
Set> cache)
{
Class> sourceType = currentSource.targetType;
List> targetTypes = currentConverter.getTargetTypes(sourceType);
for(int i=0,sz=targetTypes.size(); i targetType = targetTypes.get(i);
// check to see if we've seen this targetType before:
if (cache.add(targetType))
{
// check to see if the targetType is a subclass of the searchType:
if (searchType.isAssignableFrom(targetType))
return true;
// create a new node in the chain by adding this targetType and converter
Node newSource = new Node(currentSource, currentConverter, targetType);
// add the new node so that we can continue searching by seeing if
// we can convert the targetType into the searchType using some other
// converter:
sourcesToBeSearched.add(newSource);
}
}
return false;
}
/**
* Finds a suitable converter by searching source types supported for a
* given target type
* @param sourceType - source type
* @param targetType - target type
* @return a suitable TypeConverter is found, null otherwise
*/
private TypeConverter _searchSourceTypes(Class> sourceType, Class> targetType)
{
for (ReverseDiscoveryGenericConverter conv:_reverseDiscoveryConverters)
{
List> sourceTypes = conv.getSourceTypes(targetType);
for (Class> type: sourceTypes)
{
if (type.isAssignableFrom(sourceType))
return conv;
}
}
return null;
}
/**
* Registers a converter. Registering a new converter causes the internal
* cache to be cleared.
*/
public void registerConverter(GenericConverter converter)
{
_converters.add(converter);
_cache.clear();
}
/**
* Registers a "reverse discovery" converter. Registering a new converter causes the internal
* cache to be cleared.
*/
public void registerReverseDiscoveryConverter(ReverseDiscoveryGenericConverter converter)
{
_reverseDiscoveryConverters.add(converter);
_cache.clear();
}
/**
* converts the given source instance into an object of the targetType.
* @param source the object to convert
* @param targetType the required type.
* @return null, if the source is null.
*/
public Object convert(Object source, Class> targetType)
{
if (source == null)
return null;
if (targetType.isAssignableFrom(source.getClass()))
return source;
TypeConverter converter = getConverter(source.getClass(), targetType);
if (converter != null)
{
return converter.convert(source, targetType);
}
throw new TypeConversionException(source, targetType);
}
/**
* Checks to see if it is possible to convert the given instance
* into the specified targetType
* @return true if conversion is possible.
*/
public boolean isConvertible(Object source, Class> targetType)
{
if (source == null)
return false; // bug 4589048
if (targetType.isAssignableFrom(source.getClass()))
return true;
TypeConverter converter = getConverter(source.getClass(), targetType);
return converter != null;
}
private final Map _cache;
private final List _converters;
private final List _reverseDiscoveryConverters;
// 2006-08-02: -= Simon Lessard =-
// Using a GenericConverter null value instead
// of Node.class to be typesafe
private static final TypeConverter _NULL = new TypeConverter()
{
public Object convert(Object source, Class> targetType)
{
return null;
}
};
private static final class Node
{
public Node(Node previous, TypeConverter converter, Class> targetType)
{
this.previous = previous;
this.converter = converter;
this.targetType = targetType;
}
public Object convert(Object source)
{
if (previous != null)
{
source = previous.convert(source);
source = converter.convert(source, targetType);
}
return source;
}
public final Node previous;
public final TypeConverter converter;
public final Class> targetType;
}
private static final class Key
{
public Key(Class> source, Class> target)
{
assert !source.equals(target);
_source = source;
_target = target;
_hc = source.hashCode() + target.hashCode();
}
@Override
public int hashCode()
{
return _hc;
}
@Override
public boolean equals(Object other)
{
if (this == other)
return true;
if (other instanceof Key)
{
Key that = (Key) other;
return (_source.equals(that._source) && _target.equals(that._target));
}
return false;
}
private final int _hc;
private final Class> _source;
private final Class> _target;
}
private static final class CompositeConverter implements TypeConverter
{
public CompositeConverter(Node source, TypeConverter conv, Class> targetType)
{
assert source != null;
_chain = new Node(source, conv, targetType) ;
}
public Object convert(Object source, Class> targetType)
{
if (targetType.isAssignableFrom(_chain.targetType))
{
return _chain.convert(source);
}
else
throw new IllegalArgumentException(_LOG.getMessage(
"CANNOT_CONVERT", new Object[]{source, targetType.getName()}));
}
private final Node _chain;
}
public static GenericConverterFactory getCurrentInstance(ExternalContext extContext)
{
// TODO: once getCurrentInstance() taking ExternalContext is in ADE,
// we need to modify FacesDatabindingConfigurator to pass in the context.
// Then we can switch to storing factory instance on the application map
/*if (extContext == null)
extContext = FacesContext.getCurrentInstance().getExternalContext();
Map appMap = extContext.getApplicationMap();
GenericConverterFactory factory = (GenericConverterFactory)appMap.get(_INSTANCE_KEY);
if (factory == null)
{
factory = new GenericConverterFactory();
appMap.put(_INSTANCE_KEY, factory);
}
return factory;*/
return _INSTANCE;
}
public static GenericConverterFactory getCurrentInstance()
{
return getCurrentInstance(null);
}
private static final TrinidadLogger _LOG = TrinidadLogger.createTrinidadLogger(
GenericConverterFactory.class);
//private static final String _INSTANCE_KEY = "org.apache.myfaces.trinidadinternal.convert.GenericConverterFactory";
private static final GenericConverterFactory _INSTANCE = new GenericConverterFactory();
}