Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
net.sf.mmm.util.pojo.path.base.AbstractPojoPathNavigator Maven / Gradle / Ivy
/* Copyright (c) The m-m-m Team, Licensed under the Apache License, Version 2.0
* http://www.apache.org/licenses/LICENSE-2.0 */
package net.sf.mmm.util.pojo.path.base;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
import net.sf.mmm.util.collection.base.CollectionList;
import net.sf.mmm.util.collection.base.HashKey;
import net.sf.mmm.util.component.base.AbstractLoggableComponent;
import net.sf.mmm.util.exception.api.NlsNullPointerException;
import net.sf.mmm.util.exception.api.ObjectNotFoundException;
import net.sf.mmm.util.lang.api.GenericBean;
import net.sf.mmm.util.pojo.api.PojoFactory;
import net.sf.mmm.util.pojo.base.GuessingPojoFactory;
import net.sf.mmm.util.pojo.path.api.IllegalPojoPathException;
import net.sf.mmm.util.pojo.path.api.PojoPathAccessException;
import net.sf.mmm.util.pojo.path.api.PojoPathContext;
import net.sf.mmm.util.pojo.path.api.PojoPathConversionException;
import net.sf.mmm.util.pojo.path.api.PojoPathCreationException;
import net.sf.mmm.util.pojo.path.api.PojoPathException;
import net.sf.mmm.util.pojo.path.api.PojoPathFunction;
import net.sf.mmm.util.pojo.path.api.PojoPathFunctionManager;
import net.sf.mmm.util.pojo.path.api.PojoPathMode;
import net.sf.mmm.util.pojo.path.api.PojoPathNavigator;
import net.sf.mmm.util.pojo.path.api.PojoPathRecognizer;
import net.sf.mmm.util.pojo.path.api.PojoPathSegmentIsNullException;
import net.sf.mmm.util.pojo.path.api.PojoPathUnsafeException;
import net.sf.mmm.util.reflect.api.CollectionReflectionUtil;
import net.sf.mmm.util.reflect.api.GenericType;
import net.sf.mmm.util.reflect.api.ReflectionUtil;
import net.sf.mmm.util.reflect.base.CollectionReflectionUtilImpl;
import net.sf.mmm.util.reflect.base.ReflectionUtilImpl;
import net.sf.mmm.util.reflect.impl.SimpleGenericTypeImpl;
import net.sf.mmm.util.value.api.ComposedValueConverter;
import net.sf.mmm.util.value.impl.DefaultComposedValueConverter;
/**
* This is the abstract base implementation of the {@link PojoPathNavigator}.
*
* @author Joerg Hohwiller (hohwille at users.sourceforge.net)
* @since 1.1.0
*/
public abstract class AbstractPojoPathNavigator extends AbstractLoggableComponent implements PojoPathNavigator {
/**
* The reserved {@link net.sf.mmm.util.pojo.path.api.PojoPath}-suffix used to cache a {@link CollectionList} .
*/
private static final String PATH_SUFFIX_COLLECTION_LIST = "._collection2list";
private ReflectionUtil reflectionUtil;
private CollectionReflectionUtil collectionReflectionUtil;
private PojoPathFunctionManager functionManager;
private ComposedValueConverter valueConverter;
private PojoFactory pojoFactory;
/**
* The constructor.
*/
public AbstractPojoPathNavigator() {
super();
}
/**
* This method gets the optional {@link PojoPathFunctionManager} for {@link PojoPathFunction}s that are global for
* this {@link PojoPathNavigator} instance.
* ATTENTION:
* {@link PojoPathFunction}s provided by this {@link PojoPathFunctionManager} need to be stateless / thread-safe.
*
* @return the {@link PojoPathFunctionManager} or {@code null} if NOT available.
*/
protected PojoPathFunctionManager getFunctionManager() {
return this.functionManager;
}
/**
* This method sets the {@link #getFunctionManager() function-manager} used for global
* {@link net.sf.mmm.util.pojo.path.api.PojoPathFunction}s.
*
* @param functionManager is the {@link PojoPathFunctionManager}.
*/
@Inject
public void setFunctionManager(PojoPathFunctionManager functionManager) {
getInitializationState().requireNotInitilized();
this.functionManager = functionManager;
}
/**
* This method gets the {@link ComposedValueConverter} used by default to convert values that are NOT compatible.
*
* @see PojoPathContext#getAdditionalConverter()
*
* @return the valueConverter
*/
protected ComposedValueConverter getValueConverter() {
return this.valueConverter;
}
/**
* This method sets the {@link #getValueConverter() value-converter} used by default.
*
* @param valueConverter is the {@link ComposedValueConverter} to set.
*/
@Inject
public void setValueConverter(ComposedValueConverter valueConverter) {
getInitializationState().requireNotInitilized();
this.valueConverter = valueConverter;
}
/**
* This method gets the {@link CollectionReflectionUtil} instance to use.
*
* @return the {@link CollectionReflectionUtil} to use.
*/
public CollectionReflectionUtil getCollectionReflectionUtil() {
return this.collectionReflectionUtil;
}
/**
* @param collectionUtil is the collectionUtil to set
*/
@Inject
public void setCollectionReflectionUtil(CollectionReflectionUtil collectionUtil) {
getInitializationState().requireNotInitilized();
this.collectionReflectionUtil = collectionUtil;
}
/**
* This method gets the {@link ReflectionUtil} instance to use.
*
* @return the {@link ReflectionUtil} to use.
*/
public ReflectionUtil getReflectionUtil() {
return this.reflectionUtil;
}
/**
* @param reflectionUtil is the reflectionUtil to set
*/
@Inject
public void setReflectionUtil(ReflectionUtil reflectionUtil) {
getInitializationState().requireNotInitilized();
this.reflectionUtil = reflectionUtil;
}
/**
* This method gets the {@link PojoFactory} instance used to {@link PojoPathMode#CREATE_IF_NULL create} new
* {@link net.sf.mmm.util.pojo.api.Pojo}s.
*
* @see PojoPathContext#getPojoFactory()
*
* @return the {@link PojoFactory} to use.
*/
protected PojoFactory getPojoFactory() {
return this.pojoFactory;
}
/**
* This method sets the {@link #getPojoFactory() PojoFactory to use}.
*
* @param pojoFactory is the {@link PojoFactory} to use.
*/
@Inject
public void setPojoFactory(PojoFactory pojoFactory) {
this.pojoFactory = pojoFactory;
}
@Override
protected void doInitialize() {
super.doInitialize();
if (this.reflectionUtil == null) {
this.reflectionUtil = ReflectionUtilImpl.getInstance();
}
if (this.collectionReflectionUtil == null) {
this.collectionReflectionUtil = CollectionReflectionUtilImpl.getInstance();
}
if (this.valueConverter == null) {
this.valueConverter = DefaultComposedValueConverter.getInstance();
}
if (this.pojoFactory == null) {
GuessingPojoFactory factory = new GuessingPojoFactory();
factory.initialize();
this.pojoFactory = factory;
}
}
/**
* This method gets the {@link PojoPathState} for the given {@code context}.
*
* @param initialPojo is the initial {@link net.sf.mmm.util.pojo.api.Pojo} this {@link PojoPathNavigator} was invoked
* with.
* @param pojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} to navigate.
* @param mode is the {@link PojoPathMode mode} that determines how to deal {@code null} values.
* @param context is the {@link PojoPathContext context} for this operation.
* @return the {@link PojoPathState} or {@code null} if caching is disabled.
*/
protected PojoPathState createState(Object initialPojo, String pojoPath, PojoPathMode mode,
PojoPathContext context) {
if (mode == null) {
throw new NlsNullPointerException("mode");
}
Map rawCache = context.getCache();
if (rawCache == null) {
CachingPojoPath rootPath = new CachingPojoPath(initialPojo, initialPojo.getClass());
return new PojoPathState(rootPath, mode, pojoPath);
}
HashKey hashKey = new HashKey<>(initialPojo);
PojoPathCache masterCache = (PojoPathCache) rawCache.get(hashKey);
if (masterCache == null) {
masterCache = new PojoPathCache(initialPojo);
rawCache.put(hashKey, masterCache);
}
return masterCache.createState(mode, pojoPath);
}
/**
* This method gets the {@link PojoPathState} for the given {@code context}.
*
* @param initialPojoType is the initial pojo-type this {@link PojoPathNavigator} was invoked with.
* @param pojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} to navigate.
* @param mode is the {@link PojoPathMode mode} that determines how to deal with unsafe
* {@link net.sf.mmm.util.pojo.path.api.PojoPath} s.
* @param context is the {@link PojoPathContext context} for this operation.
* @return the {@link PojoPathState} or {@code null} if caching is disabled.
*/
@SuppressWarnings("rawtypes")
protected PojoPathState createStateByType(GenericType initialPojoType, String pojoPath, PojoPathMode mode,
PojoPathContext context) {
Class> initialPojoClass = initialPojoType.getRetrievalClass();
Map rawCache = context.getCache();
if (rawCache == null) {
CachingPojoPath rootPath = new CachingPojoPath(null, initialPojoClass, initialPojoType);
return new PojoPathState(rootPath, mode, pojoPath);
}
PojoPathCache masterCache = (PojoPathCache) rawCache.get(initialPojoType);
if (masterCache == null) {
masterCache = new PojoPathCache(initialPojoClass, initialPojoType);
rawCache.put(initialPojoType, masterCache);
}
return masterCache.createState(mode, pojoPath);
}
@Override
public Object get(Object pojo, String pojoPath, PojoPathMode mode, PojoPathContext context) {
CachingPojoPath path = getPath(pojo, pojoPath, mode, context);
return path.pojo;
}
/**
* This method contains the internal implementation of {@link #get(Object, String, PojoPathMode, PojoPathContext)}.
*
* @param pojo is the initial {@link net.sf.mmm.util.pojo.api.Pojo} to operate on.
* @param pojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} to navigate.
* @param mode is the {@link PojoPathMode mode} that determines how to deal with {@code null} values.
* @param context is the {@link PojoPathContext} for this operation.
* @return the {@link CachingPojoPath} for the given {@code pojoPath}.
*/
private CachingPojoPath getPath(Object pojo, String pojoPath, PojoPathMode mode, PojoPathContext context) {
if (pojo == null) {
if (mode == PojoPathMode.RETURN_IF_NULL) {
return null;
} else if (mode == PojoPathMode.RETURN_IF_NULL) {
throw new PojoPathCreationException(null, pojoPath);
} else {
throw new PojoPathSegmentIsNullException(null, pojoPath);
}
}
PojoPathState state = createState(pojo, pojoPath, mode, context);
return getRecursive(pojoPath, context, state);
}
@Override
@SuppressWarnings("unchecked")
public TYPE get(Object pojo, String pojoPath, PojoPathMode mode, PojoPathContext context,
Class targetClass) {
CachingPojoPath path = getPath(pojo, pojoPath, mode, context);
return (TYPE) convert(path, context, path.pojo, targetClass, null);
}
/**
* This method recursively navigates the given {@code pojoPath}.
*
* @param pojoPath is the current {@link net.sf.mmm.util.pojo.path.api.PojoPath} to navigate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @return the result of the navigation of the given {@code pojoPath} starting at the given {@code pojo}. It may be
* {@code null} according to the given {@link PojoPathMode mode}.
*/
protected CachingPojoPath getRecursive(String pojoPath, PojoPathContext context, PojoPathState state) {
// try to read from cache...
CachingPojoPath currentPath = state.getCachedPath(pojoPath);
if (currentPath != null) {
if (state.isGetType()) {
// called from getType...
if (currentPath.pojoType != null) {
return currentPath;
}
} else {
// called from get or set...
if (currentPath.pojo != null) {
return currentPath;
}
}
} else {
currentPath = new CachingPojoPath(pojoPath);
}
String parentPathString = currentPath.getParentPath();
CachingPojoPath parentPath;
if (parentPathString == null) {
parentPath = state.rootPath;
} else {
// if our pojoPath is more than a segment, we do a recursive invocation
parentPath = getRecursive(parentPathString, context, state);
}
// connect the path
currentPath.parent = parentPath;
// handle null value according to mode...
if ((parentPath.pojo == null) && (!state.isGetType())) {
switch (state.mode) {
case FAIL_IF_NULL:
throw new PojoPathSegmentIsNullException(state.rootPath.pojo, pojoPath);
case RETURN_IF_NULL:
return currentPath;
default:
// this is actually an internal error
throw new PojoPathSegmentIsNullException(state.rootPath.pojo, pojoPath);
}
}
// now we start our actual evaluation...
if ((parentPath.pojoType == null) && (state.isGetType())) {
// deal with "unsafe" path...
if (state.mode == PojoPathMode.FAIL_IF_NULL) {
throw new PojoPathSegmentIsNullException(state.rootPath.pojo, pojoPath);
}
} else {
Object result = get(currentPath, context, state);
currentPath.pojo = result;
state.setCachedPath(pojoPath, currentPath);
if (result == null) {
// creation has already taken place...
if (state.mode != PojoPathMode.RETURN_IF_NULL) {
if (state.isGetType()) {
if ((currentPath.pojoType == null)) {
throw new PojoPathUnsafeException(state.rootPath.pojoType, pojoPath);
}
} else {
if (pojoPath != state.pojoPath) {
throw new PojoPathSegmentIsNullException(state.rootPath.pojo, pojoPath);
}
}
}
} else {
PojoPathRecognizer recognizer = context.getRecognizer();
if (recognizer != null) {
recognizer.recognize(result, currentPath);
}
}
}
return currentPath;
}
/**
* This method gets the value for the single {@link CachingPojoPath#getSegment() segment} of the given
* {@code currentPath} from the {@link CachingPojoPath#getPojo() pojo} of its {@link CachingPojoPath#getParent()
* parent}.
* If the {@code state} {@link PojoPathState#isGetType() indicates} an invocation from
* {@link #getType(GenericType, String, boolean, PojoPathContext) getType}, only the
* {@link CachingPojoPath#setPojoType(GenericType) pojo-type} should be determined. Otherwise if the result is
* {@code null} and {@link PojoPathState#getMode() mode} is {@link PojoPathMode#CREATE_IF_NULL} it creates and
* attaches (sets) the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) cache} to use or
* {@code null} to disable caching.
* @return the result of the evaluation of the given {@code pojoPath} starting at the given {@code pojo}. It may be
* {@code null} according to the given {@link PojoPathMode mode}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object get(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state) {
Object result;
String functionName = currentPath.getFunction();
if (functionName != null) {
// current segment is a function...
PojoPathFunction function = getFunction(functionName, context);
if (state.isGetType()) {
result = null;
currentPath.pojoType = new SimpleGenericTypeImpl(function.getValueClass());
} else {
result = getFromFunction(currentPath, context, state, function);
}
} else {
Object parentPojo = currentPath.parent.pojo;
// current segment is NOT a function
if ((parentPojo instanceof Map)
|| (state.isGetType() && Map.class.isAssignableFrom(currentPath.parent.pojoClass))) {
result = getFromMap(currentPath, context, state, (Map) parentPojo);
} else {
Integer index = currentPath.getIndex();
if (index != null) {
// handle indexed segment for list or array...
result = getFromList(currentPath, context, state, index.intValue());
} else {
// in all other cases get via reflection from POJO...
result = getFromPojo(currentPath, context, state);
}
}
}
if (result != null) {
Class> resultType = result.getClass();
if (currentPath.pojoType == null) {
currentPath.pojoType = new SimpleGenericTypeImpl(resultType);
}
if (currentPath.pojoClass != resultType) {
currentPath.pojoClass = resultType;
}
}
return result;
}
/**
* This method {@link PojoPathFunction#get(Object, String, PojoPathContext) gets} the single
* {@link CachingPojoPath#getSegment() segment} of the given {@code currentPath} from the
* {@link net.sf.mmm.util.pojo.api.Pojo} given by {@code parentPojo}. If the result is {@code null} and
* {@link PojoPathState#getMode() mode} is {@link PojoPathMode#CREATE_IF_NULL} it
* {@link PojoPathFunction#create(Object, String, PojoPathContext) creates} the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @param function is the {@link PojoPathFunction} for evaluation.
* @return the result of the evaluation. It might be {@code null} according to the {@link PojoPathState#getMode()
* mode}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object getFromFunction(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
PojoPathFunction function) {
Object parentPojo = currentPath.parent.pojo;
// TODO: convert parentPojo from parentPojoType to function.getInputClass()
// if necessary.
Object result = function.get(parentPojo, currentPath.getFunction(), context);
if ((result == null) && (state.mode == PojoPathMode.CREATE_IF_NULL)) {
result = function.create(parentPojo, currentPath.getFunction(), context);
if (result == null) {
throw new PojoPathCreationException(state.rootPath.pojo, currentPath.getPojoPath());
}
}
if (!function.isDeterministic()) {
state.setCachingDisabled();
}
return result;
}
/**
* This method {@link Map#get(Object) gets} the single {@link CachingPojoPath#getSegment() segment} of the given
* {@code currentPath} from the {@link Map} given by {@code parentPojo}. If the result is {@code null} and
* {@link PojoPathState#getMode() mode} is {@link PojoPathMode#CREATE_IF_NULL} it creates and
* {@link Map#put(Object, Object) attaches} the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @param parentPojo is the parent object to work on.
* @return the result of the evaluation. It might be {@code null} according to the {@link PojoPathState#getMode()
* mode}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object getFromMap(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Map parentPojo) {
// determine pojo type
GenericType pojoType = currentPath.parent.pojoType;
GenericType componentType = pojoType.getComponentType();
if (componentType.getRetrievalClass() == Object.class) {
currentPath.pojoClass = Object.class;
} else {
currentPath.pojoType = componentType;
currentPath.pojoClass = componentType.getAssignmentClass();
}
Object result = null;
if (!state.isGetType()) {
result = parentPojo.get(currentPath.getSegment());
if ((result == null) && (state.mode == PojoPathMode.CREATE_IF_NULL)) {
result = create(currentPath, context, state, currentPath.pojoClass);
parentPojo.put(currentPath.getSegment(), result);
}
}
return result;
}
/**
* This method {@link CollectionReflectionUtil#get(Object, int) gets} the single {@link CachingPojoPath#getIndex()
* segment} of the given {@code currentPath} from the array or {@link java.util.List} given by {@code parentPojo}. If
* the result is {@code null} and {@link PojoPathState#getMode() mode} is {@link PojoPathMode#CREATE_IF_NULL} it
* creates and {@link CollectionReflectionUtil#set(Object, int, Object) sets} the missing object. For arrays a new
* array has to be created and set in the parent of the {@code parentPojo}.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @param index is the {@link java.util.List}-{@link java.util.List#get(int) index}.
* @return the result of the evaluation. It might be {@code null} according to the {@link PojoPathState#getMode()
* mode}.
*/
protected Object getFromList(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
int index) {
// handle indexed segment for collection/list or array...
currentPath.pojoType = currentPath.parent.pojoType.getComponentType();
if (currentPath.pojoType == null) {
currentPath.pojoType = getReflectionUtil().createGenericType(currentPath.parent.pojoClass)
.getComponentType();
}
currentPath.pojoClass = currentPath.pojoType.getAssignmentClass();
Object result = null;
if (!state.isGetType()) {
Object parentPojo = currentPath.parent.pojo;
Object arrayOrList = convertList(currentPath, context, state, parentPojo);
boolean ignoreIndexOverflow = (state.mode != PojoPathMode.FAIL_IF_NULL);
result = getCollectionReflectionUtil().get(arrayOrList, index, ignoreIndexOverflow);
if ((result == null) && (state.mode == PojoPathMode.CREATE_IF_NULL)) {
result = create(currentPath, context, state, currentPath.pojoClass);
setInList(currentPath, context, state, parentPojo, result, index);
}
}
return result;
}
/**
* This method creates a {@link net.sf.mmm.util.pojo.api.Pojo} of the given {@code pojoType}.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @param pojoClass is the {@link Class} reflecting the {@link net.sf.mmm.util.pojo.api.Pojo} to create.
* @return the created {@link net.sf.mmm.util.pojo.api.Pojo}.
* @throws PojoPathCreationException if the creation failed.
*/
protected Object create(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Class> pojoClass) throws PojoPathCreationException {
if ((pojoClass == null) || Object.class.equals(pojoClass)) {
throw new PojoPathCreationException(state.rootPath.pojo, currentPath.getPojoPath());
}
Object result;
try {
PojoFactory factory = context.getPojoFactory();
if (factory == null) {
factory = this.pojoFactory;
}
result = factory.newInstance(pojoClass);
} catch (RuntimeException e) {
throw new PojoPathCreationException(e, state.rootPath.pojo, currentPath.getPojoPath());
}
if (result == null) {
throw new PojoPathCreationException(state.rootPath.pojo, currentPath.getPojoPath());
}
return result;
}
/**
* This method reflectively gets the single {@link CachingPojoPath#getSegment() segment} of the given
* {@code currentPath} from the {@link net.sf.mmm.util.pojo.api.Pojo} given by {@code parentPojo}. If the result is
* {@code null} and {@code mode} is {@link PojoPathMode#CREATE_IF_NULL} it creates and attaches (sets) the missing
* object.
*
* @param currentPath is the current {@link CachingPojoPath} to evaluate.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} of this operation.
* @return the result of the evaluation. It might be {@code null} according to the {@link PojoPathState#getMode()
* mode}.
*/
protected abstract Object getFromPojo(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state);
/**
* This method gets the {@link PojoPathFunction} for the given {@code functionName}.
*
* @param functionName is the {@link PojoPathFunctionManager#getFunction(String) name} of the requested
* {@link PojoPathFunction}.
* @param context is the {@link PojoPathContext context} for this operation.
* @return the requested {@link PojoPathFunction}.
* @throws ObjectNotFoundException if no {@link PojoPathFunction} is defined for the given {@code functionName}.
*/
@SuppressWarnings("rawtypes")
protected PojoPathFunction getFunction(String functionName, PojoPathContext context)
throws ObjectNotFoundException {
PojoPathFunction function = null;
// context overrides functions...
PojoPathFunctionManager manager = context.getAdditionalFunctionManager();
if (manager != null) {
function = manager.getFunction(functionName);
}
if (function == null) {
// global functions as fallback...
manager = getFunctionManager();
if (manager != null) {
function = manager.getFunction(functionName);
}
}
if (function == null) {
throw new ObjectNotFoundException(PojoPathFunction.class, functionName);
}
return function;
}
@Override
public GenericType> getType(Type pojoType, String pojoPath, boolean failOnUnsafePath, PojoPathContext context)
throws PojoPathException, IllegalPojoPathException, PojoPathUnsafeException {
GenericType> genericType = getReflectionUtil().createGenericType(pojoType);
return getType(genericType, pojoPath, failOnUnsafePath, context);
}
@Override
public GenericType> getType(GenericType> pojoType, String pojoPath, boolean failOnUnsafePath,
PojoPathContext context) {
if (pojoType == null) {
throw new NullPointerException();
}
PojoPathMode mode;
if (failOnUnsafePath) {
mode = PojoPathMode.FAIL_IF_NULL;
} else {
mode = PojoPathMode.RETURN_IF_NULL;
}
PojoPathState state = createStateByType(pojoType, pojoPath, mode, context);
CachingPojoPath path = getRecursive(pojoPath, context, state);
return path.pojoType;
}
@Override
public Object set(Object pojo, String pojoPath, PojoPathMode mode, PojoPathContext context, Object value) {
if (pojo == null) {
if (mode == PojoPathMode.RETURN_IF_NULL) {
return null;
} else if (mode == PojoPathMode.RETURN_IF_NULL) {
throw new PojoPathCreationException(null, pojoPath);
} else {
throw new PojoPathSegmentIsNullException(null, pojoPath);
}
}
Object current = pojo;
boolean cached = true;
PojoPathState state = createState(pojo, pojoPath, mode, context);
CachingPojoPath currentPath = state.getCachedPath(pojoPath);
if (currentPath == null) {
cached = false;
currentPath = new CachingPojoPath(pojoPath);
String parentPathString = currentPath.getParentPath();
if (parentPathString != null) {
CachingPojoPath parentPath = getRecursive(parentPathString, context, state);
current = parentPath.pojo;
currentPath.setParent(parentPath);
}
} else if (currentPath.parent != null) {
current = currentPath.parent.pojo;
}
if (current == null) {
if (mode == PojoPathMode.RETURN_IF_NULL) {
return null;
}
// this applies for all other modes, because otherwise the segment would
// have been created.
throw new PojoPathSegmentIsNullException(pojo, pojoPath);
}
Object old = set(currentPath, context, state, current, value);
// update cache...
if (cached) {
// if we set a value that has been cached before, we need to purge the
// cache. Otherwise after setting an according get would still return the
// old value from the cache. A removal from cache is done for simplicity.
// updating of the cache here might be error-prone as the value could be
// converted (e.g. in a pojo-path-function).
state.removeCachedPath(pojoPath);
}
return old;
}
/**
* This method sets the single {@link CachingPojoPath#getSegment() segment} of the given {@code currentPath} from the
* {@link net.sf.mmm.util.pojo.api.Pojo} given by {@code parentPojo}. If the result is {@code null} and {@code mode}
* is {@link PojoPathMode#CREATE_IF_NULL} it creates and attaches (sets) the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to set.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) cache} to use or
* {@code null} to disable caching.
* @param parentPojo is the parent {@link net.sf.mmm.util.pojo.api.Pojo} to work on.
* @param value is the value to set in {@code parentPojo}.
* @return the replaced value. It may be {@code null}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object set(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Object parentPojo, Object value) {
Object result;
String functionName = currentPath.getFunction();
if (functionName != null) {
// current segment is a function...
PojoPathFunction function = getFunction(functionName, context);
Class> valueClass = function.getValueClass();
currentPath.pojoClass = valueClass;
Object convertedValue = convert(currentPath, context, value, valueClass, null);
result = function.set(parentPojo, functionName, convertedValue, context);
} else {
// current segment is NOT a function
if (parentPojo instanceof Map) {
Map map = (Map) parentPojo;
String key = currentPath.getSegment();
Object convertedKey = key;
Object convertedValue = value;
if (currentPath.parent != null) {
GenericType> mapType = currentPath.parent.pojoType;
if (mapType != null) {
GenericType> valueType = mapType.getComponentType();
convertedValue = convert(currentPath, context, value, valueType.getAssignmentClass(), valueType);
GenericType> keyType = mapType.getKeyType();
convertedKey = convert(currentPath, context, key, keyType.getAssignmentClass(), keyType);
}
}
result = map.put(convertedKey, convertedValue);
} else {
Integer index = currentPath.getIndex();
if (index != null) {
// handle indexed segment for list or array...
result = setInList(currentPath, context, state, parentPojo, value, index.intValue());
} else {
// in all other cases get via reflection from POJO...
result = setInPojo(currentPath, context, state, parentPojo, value);
}
}
}
return result;
}
/**
* This method sets the single {@link CachingPojoPath#getSegment() segment} of the given {@code currentPath} from the
* {@link net.sf.mmm.util.pojo.api.Pojo} given by {@code parentPojo}. If the result is {@code null} and {@code mode}
* is {@link PojoPathMode#CREATE_IF_NULL} it creates and attaches (sets) the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to set.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} to use.
* @param parentPojo is the parent {@link net.sf.mmm.util.pojo.api.Pojo} to work on.
* @param value is the value to set in {@code parentPojo}.
* @return the replaced value. It may be {@code null}.
*/
protected abstract Object setInPojo(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Object parentPojo, Object value);
/**
* This method converts the given {@code pojo} to the given {@code targetClass} (or even {@code targetType}) as
* necessary.
*
* @param currentPath is the current {@link CachingPojoPath} that lead to {@code pojo}.
* @param context is the {@link PojoPathContext context} for this operation.
* @param pojo is the {@link net.sf.mmm.util.pojo.api.Pojo} to convert as necessary.
* @param targetClass is the expected {@link Class}.
* @param targetType is the expected {@link Type}.
* @return the {@code pojo} converted to the {@code targetType} as necessary.
* @throws PojoPathConversionException if the given {@code pojo} is NOT compatible and could NOT be converted.
*/
protected Object convert(CachingPojoPath currentPath, PojoPathContext context, Object pojo, Class> targetClass,
GenericType> targetType) throws PojoPathConversionException {
Type type;
if (targetType != null) {
type = targetType;
} else {
type = targetClass;
}
Object result = pojo;
// null does NOT need to be converted...
if (pojo != null) {
Class> pojoClass = pojo.getClass();
// only convert if NOT compatible
if (!targetClass.isAssignableFrom(pojoClass)) {
// conversion required...
ComposedValueConverter converter = context.getAdditionalConverter();
result = null;
try {
if (converter != null) {
if (targetType != null) {
result = converter.convert(pojo, currentPath, targetType);
} else {
result = converter.convert(pojo, currentPath, targetClass);
}
}
if (result == null) {
if (targetType != null) {
result = this.valueConverter.convert(pojo, currentPath, targetType);
} else {
result = this.valueConverter.convert(pojo, currentPath, targetClass);
}
}
} catch (RuntimeException e) {
throw new PojoPathConversionException(e, currentPath.getPojoPath(), pojoClass, type);
}
if (result == null) {
throw new PojoPathConversionException(currentPath.getPojoPath(), pojoClass, type);
}
if (!targetClass.isAssignableFrom(result.getClass())) {
if (!targetClass.isPrimitive()
|| !this.reflectionUtil.getNonPrimitiveType(targetClass).isAssignableFrom(result.getClass())) {
IllegalStateException illegalState = new IllegalStateException("Illegal conversion!");
throw new PojoPathConversionException(illegalState, currentPath.getPojoPath(), pojoClass, type);
}
}
}
}
return result;
}
/**
* This method converts the given {@code arrayOrCollection} to a {@link List} as necessary.
*
* @param currentPath is the current {@link CachingPojoPath} that lead to {@code arrayOrCollection}.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} to use.
* @param arrayOrCollection is the object to be accessed at a given index.
* @return a {@link List} that adapts {@code arrayOrCollection} if it is a {@link Collection} but NOT a {@link List}.
* Otherwise the given {@code arrayOrCollection} itself.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object convertList(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Object arrayOrCollection) {
Object arrayOrList = arrayOrCollection;
if (arrayOrCollection instanceof Collection) {
if (!(arrayOrCollection instanceof List)) {
// non-list collection (e.g. Set) - create Proxy-List
if (state.cachingDisabled) {
PojoPathCachingDisabledException cachingException = new PojoPathCachingDisabledException(
currentPath.getPojoPath());
throw new PojoPathAccessException(cachingException, currentPath.getPojoPath(),
arrayOrCollection.getClass());
}
String collection2ListPath = currentPath.getPojoPath() + PATH_SUFFIX_COLLECTION_LIST;
CachingPojoPath listPath = state.getCachedPath(collection2ListPath);
if (listPath == null) {
listPath = new CachingPojoPath(collection2ListPath);
listPath.parent = currentPath;
listPath.pojo = new CollectionList((Collection) arrayOrCollection);
state.setCachedPath(collection2ListPath, listPath);
}
arrayOrList = listPath.pojo;
}
}
return arrayOrList;
}
/**
* This method {@link CollectionReflectionUtil#set(Object, int, Object, GenericBean) sets} the single
* {@link CachingPojoPath#getSegment() segment} of the given {@code currentPath} from the array or
* {@link java.util.List} given by {@code parentPojo}. If the result is {@code null} and {@code mode} is
* {@link PojoPathMode#CREATE_IF_NULL} it creates and attaches (sets) the missing object.
*
* @param currentPath is the current {@link CachingPojoPath} to set.
* @param context is the {@link PojoPathContext context} for this operation.
* @param state is the {@link #createState(Object, String, PojoPathMode, PojoPathContext) state} to use.
* @param parentPojo is the parent {@link net.sf.mmm.util.pojo.api.Pojo} to work on.
* @param value is the value to set in {@code parentPojo}.
* @param index is the position of the {@code value} to set in the array or {@link java.util.List} given by
* {@code parentPojo}.
* @return the replaced value. It may be {@code null}.
*/
protected Object setInList(CachingPojoPath currentPath, PojoPathContext context, PojoPathState state,
Object parentPojo, Object value, int index) {
Object arrayOrList = convertList(currentPath, context, state, parentPojo);
GenericBean arrayReceiver = new GenericBean<>();
Object convertedValue = value;
if (currentPath.parent != null) {
GenericType> collectionType = currentPath.parent.pojoType;
if (collectionType != null) {
GenericType> valueType = collectionType.getComponentType();
convertedValue = convert(currentPath, context, value, valueType.getAssignmentClass(), valueType);
}
}
Object result = getCollectionReflectionUtil().set(arrayOrList, index, convertedValue, arrayReceiver);
Object newArray = arrayReceiver.getValue();
if (newArray != null) {
if (currentPath.parent.parent == null) {
throw new PojoPathCreationException(state.rootPath.pojo, currentPath.getPojoPath());
}
Object parentParentPojo;
if (currentPath.parent.parent == null) {
parentParentPojo = state.rootPath.pojo;
} else {
parentParentPojo = currentPath.parent.parent.pojo;
}
set(currentPath.parent, context, state, parentParentPojo, newArray);
}
return result;
}
@Override
public Map pojo2Map(Object pojo) {
return new PojoPathMap(this, pojo);
}
@Override
public Map pojo2Map(Object pojo, PojoPathContext context) {
return new PojoPathMap(this, pojo, context);
}
/**
* This inner class represents the cache for {@link CachingPojoPath}s based on an initial
* {@link net.sf.mmm.util.pojo.api.Pojo}.
*/
protected static class PojoPathCache {
/**
* The actual cache that maps a {@link net.sf.mmm.util.pojo.path.api.PojoPath#getPojoPath() PojoPath} to the
* resulting {@link CachingPojoPath}.
*/
private final Map cache;
/** The root path. */
private CachingPojoPath rootPath;
/**
* The cached {@link Object#hashCode() hash-code} of the {@link CachingPojoPath#pojo} from the {@link #rootPath}.
*/
private int cachedHash;
/**
* The constructor.
*
* @param initialPojo is the initial {@link net.sf.mmm.util.pojo.api.Pojo} for this cache.
*/
public PojoPathCache(Object initialPojo) {
super();
this.rootPath = new CachingPojoPath(initialPojo, initialPojo.getClass());
this.cachedHash = initialPojo.hashCode();
this.cache = new HashMap<>();
}
/**
* The constructor.
*
* @param initialPojoClass is the initial {@link net.sf.mmm.util.pojo.api.Pojo}-class for this cache.
* @param initialPojoType is the initial {@link net.sf.mmm.util.pojo.api.Pojo}-type for this cache.
*/
public PojoPathCache(Class> initialPojoClass, GenericType> initialPojoType) {
super();
this.rootPath = new CachingPojoPath(null, initialPojoClass, initialPojoType);
this.cachedHash = 0;
this.cache = new HashMap<>();
}
/**
* This method creates a new {@link PojoPathState} instance based on this {@link PojoPathCache}.
*
* @param mode is the {@link PojoPathMode mode} that determines how to deal {@code null} values.
* @param pojoPath is the initial pojo-path.
* @return the new {@link PojoPathState} instance.
*/
protected PojoPathState createState(PojoPathMode mode, String pojoPath) {
if (this.rootPath.pojo != null) {
int currentHash = this.rootPath.pojo.hashCode();
if (currentHash != this.cachedHash) {
// initial POJO has changed, lets nuke the cached POJOs...
for (CachingPojoPath path : this.cache.values()) {
path.pojo = null;
path.pojoType = null;
}
this.cachedHash = currentHash;
}
}
return new PojoPathState(this.rootPath, mode, pojoPath, this.cache);
}
}
/**
* This inner class represents the state for a {@link net.sf.mmm.util.pojo.path.api.PojoPath} evaluation.
*/
protected static class PojoPathState {
/**
* The actual cache that maps a {@link net.sf.mmm.util.pojo.path.api.PojoPath#getPojoPath() PojoPath} to the
* resulting {@link net.sf.mmm.util.pojo.api.Pojo}.
*/
private final Map cache;
/** The root path. */
private CachingPojoPath rootPath;
private final PojoPathMode mode;
private final String pojoPath;
private boolean cachingDisabled;
/**
* The constructor for no caching.
*
* @param rootPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} with the initial
* {@link net.sf.mmm.util.pojo.api.Pojo}.
* @param mode is the {@link PojoPathMode mode} that determines how to deal {@code null} values.
* @param pojoPath is the {@link #getPojoPath() pojo-path}.
*/
protected PojoPathState(CachingPojoPath rootPath, PojoPathMode mode, String pojoPath) {
this(rootPath, mode, pojoPath, Collections.EMPTY_MAP);
this.cachingDisabled = true;
}
/**
* The constructor.
*
* @param rootPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} with the initial
* {@link net.sf.mmm.util.pojo.api.Pojo}.
* @param mode is the {@link PojoPathMode mode} that determines how to deal {@code null} values.
* @param pojoPath is the {@link #getPojoPath() pojo-path}.
* @param cache is the underlying {@link Map} used for caching.
*/
protected PojoPathState(CachingPojoPath rootPath, PojoPathMode mode, String pojoPath,
Map cache) {
super();
this.rootPath = rootPath;
this.mode = mode;
this.pojoPath = pojoPath;
this.cache = cache;
}
/**
* This method gets the {@link CachingPojoPath} from the cache.
*
* @param currentPojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} to lookup.
* @return the cached {@link CachingPojoPath} or {@code null} if NOT (yet) cached.
*/
public CachingPojoPath getCachedPath(String currentPojoPath) {
return this.cache.get(currentPojoPath);
}
/**
* This method stored a {@link CachingPojoPath} in the cache. This method will do nothing if this state is
* {@link #isCachingDisabled() disabled}.
*
* @param currentPojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} leading to the given {@code pojo}.
* @param evaluatedPojoPath is the {@link CachingPojoPath} that has been evaluated and should be cached.
*/
public void setCachedPath(String currentPojoPath, CachingPojoPath evaluatedPojoPath) {
if (!this.cachingDisabled) {
this.cache.put(currentPojoPath, evaluatedPojoPath);
}
}
/**
* This method removes a {@link CachingPojoPath} from the cache. This method will do nothing if this state is
* {@link #isCachingDisabled() disabled}.
*
* @param currentPojoPath is the {@link net.sf.mmm.util.pojo.path.api.PojoPath} to purge from the cache.
*/
public void removeCachedPath(String currentPojoPath) {
if (!this.cachingDisabled) {
this.cache.remove(currentPojoPath);
}
}
/**
* @return the rootPath
*/
public CachingPojoPath getRootPath() {
return this.rootPath;
}
/**
* This method gets the {@link PojoPathMode} that determines how to deal with {@code null}-values.
*
* @return the mode.
*/
public PojoPathMode getMode() {
return this.mode;
}
/**
* @return the initial pojoPath
*/
public String getPojoPath() {
return this.pojoPath;
}
/**
* This method determines if we have been invoked from
* {@link PojoPathNavigator#getType(GenericType, String, boolean, PojoPathContext) getType}.
*
* @return {@code true} if invoked via getType, {@code false} if invoked from {@code get} or {@code set}.
*/
public boolean isGetType() {
if (this.rootPath.pojo == null) {
return true;
}
return false;
}
/**
* This method determines if this cache has been {@link #setCachingDisabled() disabled}.
*
* @return {@code true} if this cache is disabled, {@code false} otherwise.
*/
public boolean isCachingDisabled() {
return this.cachingDisabled;
}
/**
* This method disables further
* {@link AbstractPojoPathNavigator.PojoPathState#setCachedPath(String, AbstractPojoPathNavigator.CachingPojoPath)
* caching}.
*/
public void setCachingDisabled() {
this.cachingDisabled = true;
}
}
/**
* This class represents a {@link net.sf.mmm.util.pojo.path.api.PojoPath}. It contains the internal logic to validate
* and parse the {@link net.sf.mmm.util.pojo.path.api.PojoPath}. Additional it can also hold the {@link #getPojo()
* result} of the evaluation and the {@link #getPojoType() generic type}.
*
* @author Joerg Hohwiller (hohwille at users.sourceforge.net)
*/
protected static class CachingPojoPath extends BasicPojoPath {
private CachingPojoPath parent;
private GenericType> pojoType;
private Class> pojoClass;
private Object pojo;
/**
* The constructor.
*
* @param pojoPath is the {@link #getPojoPath() path} to represent.
*/
public CachingPojoPath(String pojoPath) {
super(pojoPath);
}
/**
* The constructor for the root-path.
*
* @param pojo is the initial {@link #getPojo() pojo}. It may be {@code null} if invoked via
* {@link PojoPathNavigator#getType(GenericType, String, boolean, PojoPathContext) getType}.
* @param pojoClass is the initial {@link #getPojoClass() pojo-class}.
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public CachingPojoPath(Object pojo, Class> pojoClass) {
this(pojo, pojoClass, new SimpleGenericTypeImpl(pojoClass));
}
/**
* The constructor for the root-path.
*
* @param pojo is the initial {@link #getPojo() pojo}. It may be {@code null} if invoked via
* {@link PojoPathNavigator#getType(GenericType, String, boolean, PojoPathContext) getType}.
* @param pojoClass is the initial {@link #getPojoClass() pojo-class}.
* @param pojoType is the initial {@link #getPojoType() pojo-type}.
*/
public CachingPojoPath(Object pojo, Class> pojoClass, GenericType> pojoType) {
super("");
this.pojo = pojo;
this.pojoClass = pojoClass;
this.pojoType = pojoType;
}
/**
* @return the parent
*/
public CachingPojoPath getParent() {
return this.parent;
}
/**
* @param parent is the parent to set
*/
public void setParent(CachingPojoPath parent) {
this.parent = parent;
}
/**
* This method gets the {@link Type type} of the {@link #getPojo() Pojo} this
* {@link net.sf.mmm.util.pojo.path.api.PojoPath} is leading to.
*
* @return the pojo-type or {@code null} if NOT set.
*/
public GenericType> getPojoType() {
return this.pojoType;
}
/**
* This method sets the {@link #getPojoType() pojo-type}.
*
* @param pojoType is the pojo-type to set.
*/
public void setPojoType(GenericType> pojoType) {
this.pojoType = pojoType;
}
/**
* This method get the {@link Class} of the {@link #getPojo() Pojo} this
* {@link net.sf.mmm.util.pojo.path.api.PojoPath} is leading to.
*
* @return the pojo-class or {@code null} if NOT set.
*/
public Class> getPojoClass() {
return this.pojoClass;
}
/**
* This method sets the {@link #getPojoClass() pojo-class}.
*
* @param pojoClass is the pojo-class to set.
*/
public void setPojoClass(Class> pojoClass) {
this.pojoClass = pojoClass;
}
/**
* This method gets the {@link net.sf.mmm.util.pojo.api.Pojo} instance this
* {@link net.sf.mmm.util.pojo.path.api.PojoPath} is leading to.
*
* @return the {@link net.sf.mmm.util.pojo.api.Pojo} or {@code null}.
*/
public Object getPojo() {
return this.pojo;
}
/**
* This method sets the {@link #getPojo() pojo-instance}.
*
* @param pojo is the {@link #getPojo() pojo-instance}.
*/
public void setPojo(Object pojo) {
this.pojo = pojo;
}
}
}