/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org. All rights reserved. * This code is licensed under the GPL 2.0 license, availible at the root * application directory. */ package org.geoserver.ows.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import org.geoserver.platform.ServiceException; import org.geotools.util.SoftValueHashMap; /** * Utility class for performing reflective operations and other ows utility functions. * * @author Justin Deoliveira, The Open Planning Project * */ public class OwsUtils { /** * Reflectively sets a property on an object. * <p> * This method uses {@link #setter(Class, String, Class)} to locate teh setter * method for the property and then invokes it with teh specified <tt>value</tt>. * </p> * <p> * The <tt>property</tt> parameter may be specified as a "path" of the form "prop1.prop2". * If any of the resulting properties along the path result in null this method will throw * {@link NullPointerException} * </p> * @param object The target object. * @param property The property to set. * @param value The value to set, may be <code>null</code>. * * @throws IllegalArgumentException If no such property exists. * @throws RuntimeException If an error occurs setting the property * @throws NullPointerException If the property specifies a property that results in null. */ public static void set( Object object, String property, Object value ) throws IllegalArgumentException { String[] props = property.split("\\."); Method s = null; if (props.length > 1) { for (int i = 0; i < props.length-1 && object != null; i++) { object = get(object, props[i]); } if (object == null) { throw new NullPointerException( "Property '" + property + "' is null for object " + object ); } s = setter( object.getClass(), props[props.length-1], value != null ? value.getClass() : null ); } else { s = setter( object.getClass(), property, value != null ? value.getClass() : null ); } if ( s == null ) { throw new IllegalArgumentException( "No such property '" + property + "' for object " + object ); } try { s.invoke( object, value ); } catch( Exception e ) { throw new RuntimeException( e ); } } /** * Cache of reflection information about a class, keyed by class. */ static Map<Class, ClassProperties> classPropertiesCache = new SoftValueHashMap<Class, ClassProperties>(); /** * Accessor for the class to property info cache. */ static ClassProperties classProperties(Class clazz) { // SoftValueHashMap is thread safe, no need to synch ClassProperties properties = classPropertiesCache.get(clazz); if(properties == null) { properties = new ClassProperties(clazz); classPropertiesCache.put(clazz, properties); } return properties; } /** * Returns the properties object describing the properties of a class. */ public static ClassProperties getClassProperties( Class clazz ) { return classProperties(clazz); } /** * Returns a setter method for a property of java bean. * <p> * The <tt>type</tt> parameter may be <code>null</code> to indicate the * the setter for the property should be returned regardless of the type. If * not null it will be used to filter the returned method. * </p> * @param clazz The type of the bean. * @param property The property name. * @param type The type of the property, may be <code>null</code>. * * @return The setter method, or <code>null</code> if not found. */ public static Method setter(Class clazz, String property, Class type) { return classProperties(clazz).setter(property, type); } /** * Reflectively determines if an object has a specified property. * @param object The target object. * @param property The property to lookup. * * @return True if the property exists, otherwise false. */ public static boolean has(Object object, String property) { return getter(object.getClass(), property, null) != null; } /** * Reflectively gets a property from an object. * <p> * This method uses {@link #getter(Class, String, Class)} to locate the getter * method for the property and then invokes it. * </p> * <p> * The <tt>property</tt> parameter may be specified as a "path" of the form "prop1.prop2". * If any of the resulting properties along the path result in null this method will return * null. * </p> * @param object The target object. * @param property The property to set. * * @throws IllegalArgumentException If no such property exists. * @throws RuntimeException If an error occurs getting the property */ public static Object get(Object object, String property) { String[] props = property.split("\\."); Object result = object; for (int i = 0; i < props.length && result != null; i++) { String prop = props[i]; Method g = getter( result.getClass(), props[i], null ); if ( g == null ) { throw new IllegalArgumentException("No such property '" + prop + "' for object " + result ); } try { result = g.invoke( result, null ); } catch( Exception e ) { throw new RuntimeException( e ); } } return result; } /** * Reflectively puts a key, value into a Map property. * * @param object The target object. * @param property The Map property. * @param key The key to place into the map. * @param value The value to place into the map. * * @throws IllegalArgumentException If the property specified is not a map * @throws NullPointerException If the property specifies is null */ public static void put(Object object, String property, Object key, Object value) { Object o = get(object, property); if (!(o instanceof Map)) { throw new IllegalArgumentException("Property " + property + " is not a map"); } if (o == null) { throw new NullPointerException("Property " + property + " is null"); } ((Map)o).put(key, value); } /** * Returns a getter method for a property of java bean. * * @param clazz The type of the bean. * @param property The property name. * @param type The type of the property, may be null. * * @return The setter method, or <code>null</code> if not found. */ public static Method getter(Class clazz, String property, Class type) { return classProperties(clazz).getter(property, type); } /** * Reflectivley retreives a propety from a java bean. * * @param object The java bean. * @param property The property to retreive. * @param type Teh type of the property to retreive. * * @return The property, or null if it could not be found.. */ public static Object property(Object object, String property, Class type) { Method getter = getter(object.getClass(), property, type); if (getter != null) { try { return getter.invoke(object, null); } catch (Exception e) { //TODO: log this } } return null; } /** * Returns a method with a pariticular name of a class, ignoring method * paramters. * * @param clazz The class * @param name The name of the method. * * @return The method, or <code>null</code> if it could not be found. */ public static Method method(Class clazz, String name) { return classProperties(clazz).method( name ); } /** * Returns an object of a particular type in a list of objects of * various types. * * @param parameters A list of objects, of various types. * @param type The type of paramter to be returned. * * @return The object of the specified type, or <code>null</code> */ @SuppressWarnings("unchecked") public static <T extends Object> T parameter(Object[] parameters, Class<T> type) { for (int i = 0; i < parameters.length; i++) { Object parameter = parameters[i]; if ((parameter != null) && type.isAssignableFrom(parameter.getClass())) { return (T) parameter; } } return null; } /** * Dumps a stack of service exception messages to a string buffer. * */ public static void dumpExceptionMessages(ServiceException e, StringBuffer s, boolean xmlEscape) { Throwable ex = e; do { Throwable cause = ex.getCause(); final String message = ex.getMessage(); String lastMessage = message; if(!"".equals(message)) { if(xmlEscape) s.append(ResponseUtils.encodeXML(message)); else s.append(message); if(ex instanceof ServiceException) { for ( Iterator t = ((ServiceException) ex).getExceptionText().iterator(); t.hasNext(); ) { s.append("\n"); String msg = (String) t.next(); if(!lastMessage.equals(msg)) { if(xmlEscape) s.append(ResponseUtils.encodeXML(msg)); else s.append(msg); lastMessage = msg; } } } if(cause != null) s.append("\n"); } // avoid infinite loop if someone did the very stupid thing of setting // the cause as the exception itself (I only found this situation once, but...) if(ex == cause || cause == null) break; else ex = cause; } while(true); } /** * Copies properties from one object to another. * * @param source The source object. * @param target The target object. * @param clazz The class of source and target. */ public static <T> void copy(T source, T target, Class<T> clazz) { ClassProperties properties = getClassProperties(clazz); for (String p : properties.properties()) { Method getter = properties.getter(p, null); if (getter == null) { continue; // should not really happen } Class type = getter.getReturnType(); Method setter = properties.setter(p, type); // do a check for read only before calling the getter to avoid an uneccesary call if (setter == null && !(Collection.class.isAssignableFrom(type) || Map.class .isAssignableFrom(type))) { // read only continue; } try { Object newValue = getter.invoke(source, null); if (newValue == null) { continue; // TODO: make this a flag whether to overwrite with null values } if (setter == null) { if (Collection.class.isAssignableFrom(type)) { updateCollectionProperty(target, (Collection) newValue, getter); } else if (Map.class.isAssignableFrom(type)) { updateMapProperty(target, (Map) newValue, getter); } continue; } setter.invoke(target, newValue); } catch (Exception e) { throw new RuntimeException(e); } } } /** * Reflectively sets all collections when they are null. */ public static void resolveCollections(Object object) { ClassProperties properties = OwsUtils.getClassProperties( object.getClass() ); for ( String property : properties.properties() ) { Method g = properties.getter( property, null ); if ( g == null ) { continue; } Class type = g.getReturnType(); //only continue if this is a collection or a map if ( !(Map.class.isAssignableFrom( type ) || Collection.class.isAssignableFrom( type ) ) ) { continue; } //only continue if there is also a setter as well Method s = properties.setter( property, null ); if ( s == null ) { continue; } //if the getter returns null, call the setter try { Object value = g.invoke( object, null ); if ( value == null ) { //first attempt to instantiate the type directly in case the method declares // a non interface or abstract class if (!type.isInterface()) { try { value = type.getConstructor().newInstance(); } catch(Exception e) { //fall through to defaults } } if (value == null) { if ( Map.class.isAssignableFrom( type ) ) { value = new HashMap(); } else if ( List.class.isAssignableFrom( type ) ) { value = new ArrayList(); } else if ( Set.class.isAssignableFrom( type ) ) { value = new HashSet(); } else { throw new RuntimeException( "Unknown collection type:" + type.getName() ); } } //initialize s.invoke( object, value ); } } catch (Exception e) { throw new RuntimeException( e ); } } } /** * Helper method for updating a collection based property. */ static void updateCollectionProperty(Object object, Collection newValue, Method getter) throws Exception { Collection oldValue = (Collection) getter.invoke(object, null); oldValue.clear(); oldValue.addAll(newValue); } /** * Helper method for updating a map based property. */ static void updateMapProperty(Object object, Map newValue, Method getter) throws Exception { Map oldValue = (Map) getter.invoke(object, null); oldValue.clear(); oldValue.putAll(newValue); } }