// 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.introspector.Getter; import jodd.introspector.Introspector; import jodd.introspector.Setter; import jodd.typeconverter.TypeConverterManager; import jodd.typeconverter.TypeConverterManagerBean; import jodd.util.ClassUtil; import java.lang.reflect.Array; import java.util.Collection; import java.util.List; /** * Various bean property utilities that makes writings of {@link BeanUtil} classes easy. */ abstract class BeanUtilUtil implements BeanUtil { // ---------------------------------------------------------------- flags protected boolean isDeclared = false; protected boolean isForced = false; protected boolean isSilent = false; // ---------------------------------------------------------------- introspector protected Introspector introspector = JoddBean.introspector; protected TypeConverterManagerBean typeConverterManager = TypeConverterManager.getDefaultTypeConverterManager(); /** * Sets {@link Introspector introspector} implementation. */ public void setIntrospector(Introspector introspector) { this.introspector = introspector; } /** * Returns {@link Introspector introspector} implementation. */ @Override public Introspector getIntrospector() { return introspector; } /** * Sets {@link TypeConverterManagerBean type converter manager} implementation. */ public void setTypeConverterManager(TypeConverterManagerBean typeConverterManager) { this.typeConverterManager = typeConverterManager; } /** * Returns {@link TypeConverterManagerBean type converter manager} implementation. */ public TypeConverterManagerBean getTypeConverterManager() { return typeConverterManager; } /** * Converts object to destination type. Invoked before the * value is set into destination. Throws <code>TypeConversionException</code> * if conversion fails. */ @SuppressWarnings("unchecked") protected Object convertType(Object value, Class type) { return typeConverterManager.convertType(value, type); } /** * Convert to collection. */ protected Object convertToCollection(Object value, Class destinationType, Class componentType) { return typeConverterManager.convertToCollection(value, destinationType, componentType); } // ---------------------------------------------------------------- accessors /** * Invokes setter, but first converts type to match the setter type. */ protected Object invokeSetter(Setter setter, BeanProperty bp, Object value) { try { Class type = setter.getSetterRawType(); if (ClassUtil.isTypeOf(type, Collection.class)) { Class componentType = setter.getSetterRawComponentType(); value = convertToCollection(value, type, componentType); } else { // no collections value = convertType(value, type); } setter.invokeSetter(bp.bean, value); } catch (Exception ex) { if (isSilent) { return null; } throw new BeanException("Setter failed: " + setter, ex); } return value; } // ---------------------------------------------------------------- forced /** * Returns the element of an array forced. If value is <code>null</code>, it will be instantiated. * If not the last part of indexed bean property, array will be expanded to the index if necessary. */ protected Object arrayForcedGet(BeanProperty bp, Object array, int index) { Class componentType = array.getClass().getComponentType(); if (!bp.last) { array = ensureArraySize(bp, array, componentType, index); } Object value = Array.get(array, index); if (value == null) { try { value = ClassUtil.newInstance(componentType); } catch (Exception ex) { if (isSilent) { return null; } throw new BeanException("Invalid array element: " + bp.name + '[' + index + ']', bp, ex); } Array.set(array, index, value); } return value; } /** * Sets the array element forced. If index is greater then arrays length, array will be expanded to the index. * If speed is critical, it is better to allocate an array with proper size before using this method. */ protected void arrayForcedSet(BeanProperty bp, Object array, int index, Object value) { Class componentType = array.getClass().getComponentType(); array = ensureArraySize(bp, array, componentType, index); value = convertType(value, componentType); Array.set(array, index, value); } @SuppressWarnings({"SuspiciousSystemArraycopy"}) protected Object ensureArraySize(BeanProperty bp, Object array, Class componentType, int index) { int len = Array.getLength(array); if (index >= len) { Object newArray = Array.newInstance(componentType, index + 1); System.arraycopy(array, 0, newArray, 0, len); Setter setter = bp.getSetter(true); if (setter == null) { // no point to check for bp.silent, throws NPE later throw new BeanException("Setter or field not found: " + bp.name, bp); } newArray = invokeSetter(setter, bp, newArray); array = newArray; } return array; } @SuppressWarnings({"unchecked"}) protected void ensureListSize(List list, int size) { int len = list.size(); while (size >= len) { list.add(null); len++; } } // ---------------------------------------------------------------- index /** * Finds the very first next dot. Ignores dots between index brackets. * Returns <code>-1</code> when dot is not found. */ protected int indexOfDot(String name) { int ndx = 0; int len = name.length(); boolean insideBracket = false; while (ndx < len) { char c = name.charAt(ndx); if (insideBracket) { if (c == ']') { insideBracket = false; } } else { if (c == '.') { return ndx; } if (c == '[') { insideBracket = true; } } ndx++; } return -1; } /** * Extract index string from non-nested property name. * If index is found, it is stripped from bean property name. * If no index is found, it returns <code>null</code>. */ protected String extractIndex(BeanProperty bp) { bp.index = null; String name = bp.name; int lastNdx = name.length() - 1; if (lastNdx < 0) { return null; } if (name.charAt(lastNdx) == ']') { int leftBracketNdx = name.lastIndexOf('['); if (leftBracketNdx != -1) { bp.setName(name.substring(0, leftBracketNdx)); bp.index = name.substring(leftBracketNdx + 1, lastNdx); return bp.index; } } return null; } protected int parseInt(String indexString, BeanProperty bp) { try { return Integer.parseInt(indexString); } catch (NumberFormatException nfex) { // no point to use bp.silent, as will throw exception throw new BeanException("Invalid index: " + indexString, bp, nfex); } } // ---------------------------------------------------------------- create property /** * Creates new instance for current property name through its setter. * It uses default constructor! */ protected Object createBeanProperty(BeanProperty bp) { Setter setter = bp.getSetter(true); if (setter == null) { return null; } Class type = setter.getSetterRawType(); Object newInstance; try { newInstance = ClassUtil.newInstance(type); } catch (Exception ex) { if (isSilent) { return null; } throw new BeanException("Invalid property: " + bp.name, bp, ex); } newInstance = invokeSetter(setter, bp, newInstance); return newInstance; } // ---------------------------------------------------------------- generic and type /** * Extracts generic component type of a property. Returns <code>Object.class</code> * when property does not have component. */ protected Class extractGenericComponentType(Getter getter) { Class componentType = null; if (getter != null) { componentType = getter.getGetterRawComponentType(); } if (componentType == null) { componentType = Object.class; } return componentType; } /** * Converts <b>Map</b> index to key type. If conversion fails, original value will be returned. */ protected Object convertIndexToMapKey(Getter getter, Object index) { Class indexType = null; if (getter != null) { indexType = getter.getGetterRawKeyComponentType(); } // check if set if (indexType == null) { indexType = Object.class; // marker for no generic type } if (indexType == Object.class) { return index; } try { return convertType(index, indexType); } catch (Exception ignore) { return index; } } /** * Extracts type of current property. */ protected Class extractType(BeanProperty bp) { Getter getter = bp.getGetter(isDeclared); if (getter != null) { if (bp.index != null) { Class type = getter.getGetterRawComponentType(); return type == null ? Object.class : type; } return getter.getGetterRawType(); } return null; // this should not happens } }