All Downloads are FREE. Search and download functionalities are using the official Maven repository.

jodd.bean.BeanUtilBean Maven / Gradle / Ivy

Go to download

Easy Redis Java client and Real-Time Data Platform. Valkey compatible. Sync/Async/RxJava3/Reactive API. Client side caching. Over 50 Redis based Java objects and services: JCache API, Apache Tomcat, Hibernate, Spring, Set, Multimap, SortedSet, Map, List, Queue, Deque, Semaphore, Lock, AtomicLong, Map Reduce, Bloom filter, Scheduler, RPC

There is a newer version: 3.40.2
Show newest version
// Copyright (c) 2003-present, Jodd Team (http://jodd.org)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

package jodd.bean;

import jodd.bean.exception.ForcedBeanException;
import jodd.bean.exception.InvalidPropertyBeanException;
import jodd.bean.exception.InvokePropertyBeanException;
import jodd.bean.exception.PropertyNotFoundBeanException;
import jodd.bean.exception.NullPropertyBeanException;
import jodd.introspector.Getter;
import jodd.introspector.Setter;
import jodd.util.ClassUtil;
import jodd.util.StringUtil;

import java.lang.reflect.Array;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Instantiable version of {@link BeanUtil}.
 */
public class BeanUtilBean extends BeanUtilUtil implements BeanUtil {

	/**
	 * Sets the declared flag.
	 */
	public BeanUtilBean declared(final boolean declared) {
		this.isDeclared = declared;
		return this;
	}

	/**
	 * Sets the forced flag.
	 */
	public BeanUtilBean forced(final boolean forced) {
		this.isForced = forced;
		return this;
	}

	/**
	 * Sets the silent flag.
	 */
	public BeanUtilBean silent(final boolean silent) {
		this.isSilent = silent;
		return this;
	}

	// ---------------------------------------------------------------- internal resolver

	/**
	 * Resolves nested property name to the very last indexed property.
	 * If forced, null or non-existing properties will be created.
	 */
	protected void resolveNestedProperties(final BeanProperty bp) {
		String name = bp.name;
		int dotNdx;
		while ((dotNdx = indexOfDot(name)) != -1) {
			bp.last = false;
			bp.setName(name.substring(0, dotNdx));
			bp.updateBean(getIndexProperty(bp));
			name = name.substring(dotNdx + 1);
		}
		bp.last = true;
		bp.setName(name);
	}

	// used only for hasProperty & getPropertyType (!)
	// it continues to work even there is no bean instance!
	protected boolean resolveExistingNestedProperties(final BeanProperty bp) {
		String name = bp.name;
		int dotNdx;
		while ((dotNdx = indexOfDot(name)) != -1) {
			bp.last = false;
			bp.setName(name.substring(0, dotNdx));
			final String temp = bp.name;
			if (!hasIndexProperty(bp)) {
				return false;
			}
			bp.setName(temp);

			final Object indexProperty = bp.bean != null ? getIndexProperty(bp) : null;
			if (indexProperty != null) {
				// regular case, when there is an instance
				bp.updateBean(indexProperty);
			}
			else {
				// when bean is null, continue with the type
				bp.updateBeanClassFromProperty();
			}

			name = name.substring(dotNdx + 1);
		}
		bp.last = true;
		bp.setName(name);
		return true;
	}


	// ---------------------------------------------------------------- simple property

	@Override
	public boolean hasSimpleProperty(final Object bean, final String property) {
		return hasSimpleProperty(new BeanProperty(this, bean, property, false));
	}

	protected boolean hasSimpleProperty(final BeanProperty bp) {
//		if (bp.bean == null) {
//			return false;
//		}

		// try: getter
		final Getter getter = bp.getGetter(isDeclared);
		if (getter != null) {
			return true;
		}

		// try: (Map) get("property")
		if (bp.isMap()) {
			final Map map = (Map) bp.bean;
			if (map.containsKey(bp.name)) {
				return true;
			}
		}

		return false;
	}

	@Override
	public  T getSimpleProperty(final Object bean, final String property) {
		final BeanProperty bp = new BeanProperty(this, bean, property, false);
		return (T) getSimpleProperty(bp);
	}

	protected Object getSimpleProperty(final BeanProperty bp) {
		if (bp.name.isEmpty()) {
			if (bp.indexString != null) {
				// index string exist, but property name is missing
				return bp.bean;
			}
			throw new InvalidPropertyBeanException("Empty property name.", bp);
		}

		final Getter getter = bp.getGetter(isDeclared);

		if (getter != null) {
			Object result;
			try {
				result = getter.invokeGetter(bp.bean);
			} catch (final Exception ex) {
				if (isSilent) {
					return null;
				}
				throw new InvokePropertyBeanException("Invoking getter method failed.", bp, ex);
			}

			if ((result == null) && (bp.isForced)) {
				result = createBeanProperty(bp);
			}
			return result;
		}

		// try: (Map) get("property")
		if (bp.isMap()) {
			final Map map = (Map) bp.bean;
			final Object key = convertIndexToMapKey(getter, bp.name);

			if (!map.containsKey(key)) {
				if (!bp.isForced) {
					if (isSilent) {
						return null;
					}
					throw new PropertyNotFoundBeanException("Map key '" +  bp.name + "' not found.", bp);
				}
				final Map value = new HashMap();
				//noinspection unchecked
				map.put(key, value);
				return value;
			}
			return map.get(key);
		}

		// failed
		if (isSilent) {
			return null;
		}

		if (bp.isExistingParentNull() && bp.currentPropertyExistOnParent(isDeclared)) {
			throw new NullPropertyBeanException("Simple property '" + bp.lastName + "' is null.", bp);
		}
		throw new PropertyNotFoundBeanException("Simple property '" + bp.name + "' not found.", bp);
	}

	@Override
	public void setSimpleProperty(final Object bean, final String property, final Object value) {
		setSimpleProperty(new BeanProperty(this, bean, property, true), value);
	}

	/**
	 * Sets a value of simple property.
	 */
	@SuppressWarnings({"unchecked"})
	protected void setSimpleProperty(final BeanProperty bp, final Object value) {
		final Setter setter = bp.getSetter(isDeclared);

		// try: setter
		if (setter != null) {
			invokeSetter(setter, bp, value);
			return;
		}

		// try: put("property", value)
		if (bp.isMap()) {
			((Map) bp.bean).put(bp.name, value);
			return;
		}
		if (isSilent) {
			return;
		}
		if (bp.isExistingParentNull() && bp.currentPropertyExistOnParent(isDeclared)) {
			throw new NullPropertyBeanException("Simple property '" + bp.lastName + "' is null.", bp);
		}
		throw new PropertyNotFoundBeanException("Simple property '" + bp.name + "' not found.", bp);
	}

	// ---------------------------------------------------------------- indexed property

	protected boolean hasIndexProperty(final BeanProperty bp) {
//		if (bp.bean == null) {
//			return false;
//		}
		final String indexString = extractIndex(bp);

		if (indexString == null) {
			return hasSimpleProperty(bp);
		}

		if (bp.bean == null) {
			return false;
		}

		final Object resultBean = getSimpleProperty(bp);

		if (resultBean == null) {
			return false;
		}

		// try: property[index]
		if (resultBean.getClass().isArray()) {
			final int index = parseInt(indexString, bp);
			return (index >= 0) && (index < Array.getLength(resultBean));
		}

		// try: list.get(index)
		if (resultBean instanceof List) {
			final int index = parseInt(indexString, bp);
			return (index >= 0) && (index < ((List)resultBean).size());
		}
		if (resultBean instanceof Map) {
			return ((Map)resultBean).containsKey(indexString);
		}

		// failed
		return false;
	}

	@Override
	public  T getIndexProperty(final Object bean, final String property, final int index) {
		final BeanProperty bp = new BeanProperty(this, bean, property, false);

		bp.indexString = bp.index = String.valueOf(index);

		final Object value = _getIndexProperty(bp);

		bp.indexString = null;

		return (T) value;
	}

	/**
	 * Get non-nested property value: either simple or indexed property.
	 * If forced, missing bean will be created if possible.
	 */
	protected Object getIndexProperty(final BeanProperty bp) {
		bp.indexString = extractIndex(bp);

		final Object value = _getIndexProperty(bp);

		bp.indexString = null;

		return value;
	}

	private Object _getIndexProperty(final BeanProperty bp) {
		final Object resultBean = getSimpleProperty(bp);
		final Getter getter = bp.getGetter(isDeclared);
		if (bp.indexString == null) {
			return resultBean;
		}
		if (resultBean == null) {
			if (isSilent) {
				return null;
			}
			throw new NullPropertyBeanException("Index property '" + bp.name + "' is null.", bp);
		}

		// try: property[index]
		if (resultBean.getClass().isArray()) {
			final int index = parseInt(bp.indexString, bp);
			if (bp.isForced) {
				return arrayForcedGet(bp, resultBean, index);
			} else {
				return Array.get(resultBean, index);
			}
		}

		// try: list.get(index)
		if (resultBean instanceof List) {
			final int index = parseInt(bp.indexString, bp);
			final List list = (List) resultBean;
			if (!bp.isForced) {
				return list.get(index);
			}
			if (!bp.last) {
				ensureListSize(list, index);
			}
			Object value = list.get(index);
			if (value == null) {
				Class listComponentType = extractGenericComponentType(getter);
				if (listComponentType == Object.class) {
					// not an error: when component type is unknown, use Map as generic bean
					listComponentType = Map.class;
				}
				try {
					value = ClassUtil.newInstance(listComponentType);
				} catch (final Exception ex) {
					if (isSilent) {
						return null;
					}
					throw new ForcedBeanException("Invalid list element: " + bp.name + '[' + index + "].", bp, ex);
				}
				//noinspection unchecked
				list.set(index, value);
			}
			return value;
		}

		// try: map.get('index')
		if (resultBean instanceof Map) {
			final Map map = (Map) resultBean;
			final Object key = convertIndexToMapKey(getter, bp.indexString);

			if (!bp.isForced) {
				return map.get(key);
			}
			Object value = map.get(key);
			if (!bp.last) {
				if (value == null) {
					Class mapComponentType = extractGenericComponentType(getter);
					if (mapComponentType == Object.class) {
						mapComponentType = Map.class;
					}
					try {
						value = ClassUtil.newInstance(mapComponentType);
					} catch (final Exception ex) {
						if (isSilent) {
							return null;
						}
						throw new ForcedBeanException("Invalid map element: " + bp.name + '[' + bp.indexString + "].", bp, ex);
					}

					//noinspection unchecked
					map.put(key, value);
				}
			}
			return value;
		}

		// failed
		if (isSilent) {
			return null;
		}
		throw new InvalidPropertyBeanException("Index property '" + bp.name + "' is not an array, list or map.", bp);
	}

	@Override
	public void setIndexProperty(final Object bean, final String property, final int index, final Object value) {
		final BeanProperty bp = new BeanProperty(this, bean, property, true);

		bp.indexString = bp.index = String.valueOf(index);

		_setIndexProperty(bp, value);

		bp.indexString = null;
	}

	/**
	 * Sets indexed or regular properties (no nested!).
	 */
	protected void setIndexProperty(final BeanProperty bp, final Object value) {
		bp.indexString = extractIndex(bp);

		_setIndexProperty(bp, value);

		bp.indexString = null;
	}

	@SuppressWarnings({"unchecked"})
	private void _setIndexProperty(final BeanProperty bp, Object value) {
		if (bp.indexString == null) {
			setSimpleProperty(bp, value);
			return;
		}

		// try: getInner()
		final Object nextBean = getSimpleProperty(bp);
		final Getter getter = bp.getGetter(isDeclared);

		if (nextBean == null) {
			if (isSilent) {
				return;
			}
			throw new NullPropertyBeanException("Index property '" + bp.name + " is null.", bp);
		}

		// inner bean found
		if (nextBean.getClass().isArray()) {
			final int index = parseInt(bp.indexString, bp);
			if (bp.isForced) {
				arrayForcedSet(bp, nextBean, index, value);
			} else {
				Array.set(nextBean, index, value);
			}
			return;
		}

		if (nextBean instanceof List) {
			final int index = parseInt(bp.indexString, bp);
			final Class listComponentType = extractGenericComponentType(getter);
			if (listComponentType != Object.class) {
				value = convertType(value, listComponentType);
			}
			final List list = (List) nextBean;
			if (bp.isForced) {
				ensureListSize(list, index);
			}
			list.set(index, value);
			return;
		}
		if (nextBean instanceof Map) {
			final Map map = (Map) nextBean;
			final Object key = convertIndexToMapKey(getter, bp.indexString);

			final Class mapComponentType = extractGenericComponentType(getter);
			if (mapComponentType != Object.class) {
				value = convertType(value, mapComponentType);
			}
			map.put(key, value);
			return;
		}

		// failed
		if (isSilent) {
			return;
		}
		throw new InvalidPropertyBeanException("Index property '" + bp.name + "' is not an array, list or map.", bp);
	}


	// ---------------------------------------------------------------- SET

	@Override
	public void setProperty(final Object bean, final String name, final Object value) {
		final BeanProperty beanProperty = new BeanProperty(this, bean, name, true);

		if (!isSilent) {
			resolveNestedProperties(beanProperty);
			setIndexProperty(beanProperty, value);
		}
		else {
			try {
				resolveNestedProperties(beanProperty);
				setIndexProperty(beanProperty, value);
			}
			catch (final Exception ignore) {}
		}
	}

	// ---------------------------------------------------------------- GET

	/**
	 * Returns value of bean's property.
	 */
	@Override
	public  T getProperty(final Object bean, final String name) {
		final BeanProperty beanProperty = new BeanProperty(this, bean, name, false);
		if (!isSilent) {
			resolveNestedProperties(beanProperty);
			return (T) getIndexProperty(beanProperty);
		}
		else {
			try {
				resolveNestedProperties(beanProperty);
				return (T) getIndexProperty(beanProperty);
			}
			catch (final Exception ignore) {
				return null;
			}
		}
	}

	// ---------------------------------------------------------------- HAS

	@Override
	public boolean hasProperty(final Object bean, final String name) {
		final BeanProperty beanProperty = new BeanProperty(this, bean, name, false);
		if (!resolveExistingNestedProperties(beanProperty)) {
			return false;
		}
		return hasIndexProperty(beanProperty);
	}

	@Override
	public boolean hasRootProperty(final Object bean, String name) {
		final int dotNdx = indexOfDot(name);
		if (dotNdx != -1) {
			name = name.substring(0, dotNdx);
		}
		final BeanProperty beanProperty = new BeanProperty(this, bean, name, false);
		extractIndex(beanProperty);
		return hasSimpleProperty(beanProperty);
	}

	// ---------------------------------------------------------------- type

	@Override
	public Class getPropertyType(final Object bean, final String name) {
		final BeanProperty beanProperty = new BeanProperty(this, bean, name, false);
		if (!resolveExistingNestedProperties(beanProperty)) {
			return null;
		}
		hasIndexProperty(beanProperty);
		return extractType(beanProperty);
	}

	// ---------------------------------------------------------------- utilities

	private static final char[] INDEX_CHARS = new char[] {'.', '['};

	/**
	 * Extract the first name of this reference.
	 */
	@Override
	public String extractThisReference(final String propertyName) {
		final int ndx = StringUtil.indexOfChars(propertyName, INDEX_CHARS);
		if (ndx == -1) {
			return propertyName;
		}
		return propertyName.substring(0, ndx);
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy