Java tutorial
/* * Flatworm - A Java Flat File Importer/Exporter Copyright (C) 2004 James M. Turner. * Extended by James Lawrence 2005 * Extended by Josh Brackett in 2011 and 2012 * Extended by Alan Henson in 2016 * * 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 com.blackbear.flatworm; import com.blackbear.flatworm.config.CardinalityBO; import com.blackbear.flatworm.errors.FlatwormConfigurationException; import com.blackbear.flatworm.errors.FlatwormParserException; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.lang.StringUtils; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.util.Collection; public class ParseUtils { /** * Create a new instance of the class represented by the {@code beanType} instance. * * @param beanType Represents an instance of the class that is to be newly instantiated. * @return a new instance of the class that the {@code beanType} object represents. * @throws FlatwormParserException should creating the new instance fail for any reason. */ public static Object newBeanInstance(Object beanType) throws FlatwormParserException { try { return beanType.getClass().newInstance(); } catch (Exception e) { throw new FlatwormParserException("Unable to create new instance of bean '" + beanType.getClass() + "'", e); } } /** * Determine how best to add an object to another object based upon the {@link CardinalityBO} configuration. * * @param target The object to be updated. * @param toAdd The object to add to the {@code target} object. * @param cardinality The {@link CardinalityBO} instance that defines how the updates are to occur. * @throws FlatwormParserException should updating the object fail for any reason or if the cardinality rules are violated. */ public static void addObjectToProperty(Object target, Object toAdd, CardinalityBO cardinality) throws FlatwormParserException { if (cardinality.getCardinalityMode() == CardinalityMode.SINGLE) { setProperty(target, cardinality.getPropertyName(), toAdd); } else { addValueToCollection(cardinality, target, toAdd); } } /** * Invoke a setter for a the given {@code propertyName}. * * @param target The {@code Object} that contains the setter property to be invoked. * @param propertyName The name of the property. * @param toAdd The value to pass to the property. * @throws FlatwormParserException should invoking the setter method fail for any reason. */ public static void setProperty(Object target, String propertyName, Object toAdd) throws FlatwormParserException { try { PropertyUtils.setProperty(target, propertyName, toAdd); } catch (Exception e) { throw new FlatwormParserException(e.getMessage(), e); } } /** * Determine how best to add the {@code toAdd} instance to the collection found in {@code target} by seeing if either the {@code * Segment.addMethod} has a value or if {@code Segment.propertyName} has a value. If neither values exist then no action is taken. * * @param cardinality The {@link CardinalityBO} instance containing the configuration information. * @param target The instance with the collection to which the {@code toAdd} instance is to be added. * @param toAdd The instance to be added to the specified collection. * @throws FlatwormParserException should the attempt to add the {@code toAdd} instance to the specified collection fail for any * reason. */ public static void addValueToCollection(CardinalityBO cardinality, Object target, Object toAdd) throws FlatwormParserException { if (cardinality.getCardinalityMode() != CardinalityMode.SINGLE) { boolean addToCollection = true; PropertyDescriptor propDesc; try { propDesc = PropertyUtils.getPropertyDescriptor(target, cardinality.getPropertyName()); } catch (Exception e) { // This should only happen via the XML configuration as the annotation configuration uses reflection to // determine the property name. throw new FlatwormParserException(String.format( "Failed to read property %s on bean %s. Make sure the specified " + "property-name is correct in the configuration for the record-element.", cardinality.getPropertyName(), target.getClass().getName()), e); } if (cardinality.getCardinalityMode() == CardinalityMode.STRICT || cardinality.getCardinalityMode() == CardinalityMode.RESTRICTED) { try { Object currentValue = PropertyUtils.getProperty(target, cardinality.getPropertyName()); int currentSize; if (Collection.class.isAssignableFrom(propDesc.getPropertyType())) { currentSize = Collection.class.cast(currentValue).size(); } else if (propDesc.getPropertyType().isArray()) { currentSize = Array.getLength(currentValue); } else { throw new FlatwormParserException(String.format( "Bean %s has a Cardinality Mode of %s for property %s, " + "suggesting that it is an Array or some instance of java.util.Collection. However, the property type " + "is %s, which is not currently supported.", target.getClass().getName(), cardinality.getCardinalityMode().name(), cardinality.getPropertyName(), propDesc.getPropertyType().getName())); } addToCollection = currentSize < cardinality.getMaxCount() || cardinality.getMaxCount() < 0; } catch (Exception e) { throw new FlatwormParserException(String.format( "Failed to load property %s on bean %s when determining if a " + "value could be added to the collection.", cardinality.getPropertyName(), target.getClass().getName()), e); } if (!addToCollection && cardinality.getCardinalityMode() == CardinalityMode.STRICT) { throw new FlatwormParserException(String.format( "Cardinality limit of %d exceeded for property %s of bean %s " + "with Cardinality Mode set to %s.", cardinality.getMaxCount(), cardinality.getPropertyName(), target.getClass().getName(), cardinality.getCardinalityMode().name())); } } // Add it if we have determined that's allowed. if (addToCollection) { // Need to make sure we have an add method for Arrays. // TODO - add ability to automatically expand an array - for now, use an addMethod or collections. if (StringUtils.isBlank(cardinality.getAddMethod()) && propDesc.getPropertyType().isArray()) { throw new FlatwormParserException(String.format( "Bean %s with property %s is an Array and therefore an Add Method " + "must be specified in the configuration so that an element can be properly added to the array. " + "Auto-expanding an array is not yet supported.", target.getClass().getName(), cardinality.getPropertyName())); } if (!StringUtils.isBlank(cardinality.getAddMethod())) { invokeAddMethod(target, cardinality.getAddMethod(), toAdd); } else if (!StringUtils.isBlank(cardinality.getPropertyName())) { addValueToCollection(target, cardinality.getPropertyName(), toAdd); } } } else { throw new FlatwormParserException(String.format( "Object %s attempted to be added to Object %s as part of a collection," + " but the configuration has it configured as a %s Cardinality Mode.", toAdd.getClass().getName(), target.getClass().getName(), cardinality.getCardinalityMode().name())); } } /** * Invoke an add method on the {@code target} instance by the {@code functionName} and pass it the {@code toAdd} instance to add. * * @param target The instance on which the {@code functionName} (addMethod) will be invoked. * @param methodName The name of the "add" method to be invoked - it should take an Object parameter. * @param toAdd The instance to pass to the {@code functionName} method. * @throws FlatwormParserException should invoking the method fail for any reason. */ public static void invokeAddMethod(Object target, String methodName, Object toAdd) throws FlatwormParserException { try { Method method = target.getClass().getMethod(methodName, toAdd.getClass()); method.invoke(target, toAdd); } catch (Exception e) { throw new FlatwormParserException( String.format("Unable to invoke add method %s on bean %s with object of converterName %s", methodName, target.getClass().getSimpleName(), toAdd.getClass().getSimpleName()), e); } } /** * Attempt to find the collection property on {@code target} indicated by the {@code propertyName} and then see if the "collection" * returned has an {@code add(Object)} method and if it does - invoke it with the {@code toAdd} instance. If any of the parameters are * {@code null} then no action is taken. * * @param target The object that has the collection property for which the {@code toAdd} instance will be added. * @param collectionPropertyName The name of the Java BeanBO property that will return a collection (may not be a {@link * java.util.Collection} so long as there is an {@code add(Object)} method). Note that if this returns a * null value a {@link FlatwormConfigurationException} will be thrown. * @param toAdd The instance to add to the collection indicated. * @throws FlatwormParserException Should the underlying collection referenced by {@code propertyName} be {@code null} or non-existent, * should no {@code add(Object)} method exist on the collection and should any error occur while * invoking the {@code add(Object)} method if it is found (reflection style errors). */ public static void addValueToCollection(Object target, String collectionPropertyName, Object toAdd) throws FlatwormParserException { if (target == null || StringUtils.isBlank(collectionPropertyName) || toAdd == null) return; try { PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(target, collectionPropertyName); if (propertyDescriptor != null) { Object collectionInstance = PropertyUtils.getProperty(target, collectionPropertyName); if (collectionInstance != null) { // Once compiled, generics lose their converterName reference and it defaults to a simple java.lang.Object.class // so that's the method parameter we'll search by. Method addMethod = propertyDescriptor.getPropertyType().getMethod("add", Object.class); if (addMethod != null) { addMethod.invoke(collectionInstance, toAdd); } else { throw new FlatwormParserException(String.format( "The collection instance %s for property %s in class %s does not have an add method.", collectionInstance.getClass().getName(), propertyDescriptor.getName(), target.getClass().getName())); } } else { throw new FlatwormParserException(String.format( "Unable to invoke the add method on collection %s as it is currently null for instance %s.", propertyDescriptor.getName(), target.getClass().getName())); } } else { throw new FlatwormParserException(String.format( "%s does not have a getter for property %s - the %s instance could therefore not be added to the collection.", target.getClass().getName(), collectionPropertyName, toAdd.getClass().getName())); } } catch (Exception e) { throw new FlatwormParserException(String.format( "Unable to invoke the add method on the collection for property %s in bean %s with object of converterName %s", collectionPropertyName, target.getClass().getName(), toAdd.getClass().getName()), e); } } /** * Attempt to determine the {@link CardinalityMode} based upon the {@code fieldType}. {@code Collection} based classes * and {@code Arrays} will return {@code CardinalityMode.LOOSE} - everything else will return {@code CardinalityMode.SINGLE}. * @param fieldType The field type to evaluate. * @return {@code CardinalityMode.LOOSE} when the {@code fieldType} is a implementation of a {@link Collection} interface or if * {@code fieldType} is an {@code Array}. {@code CardinalityMode.SINGLE} will be returned for all other cases. */ public static CardinalityMode resolveCardinality(Class<?> fieldType) { CardinalityMode mode = CardinalityMode.SINGLE; if (fieldType != null && (Collection.class.isAssignableFrom(fieldType) || fieldType.isArray())) { mode = CardinalityMode.LOOSE; } return mode; } }