001/* ========================================================================
002 * JCommon : a free general purpose class library for the Java(tm) platform
003 * ========================================================================
004 *
005 * (C) Copyright 2000-2005, by Object Refinery Limited and Contributors.
006 * 
007 * Project Info:  http://www.jfree.org/jcommon/index.html
008 *
009 * This library is free software; you can redistribute it and/or modify it 
010 * under the terms of the GNU Lesser General Public License as published by 
011 * the Free Software Foundation; either version 2.1 of the License, or 
012 * (at your option) any later version.
013 *
014 * This library is distributed in the hope that it will be useful, but 
015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 
016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 
017 * License for more details.
018 *
019 * You should have received a copy of the GNU Lesser General Public
020 * License along with this library; if not, write to the Free Software
021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, 
022 * USA.  
023 *
024 * [Java is a trademark or registered trademark of Sun Microsystems, Inc. 
025 * in the United States and other countries.]
026 * 
027 * --------------------------
028 * BeanObjectDescription.java
029 * --------------------------
030 * (C)opyright 2003-2005, by Thomas Morgner and Contributors.
031 *
032 * Original Author:  Thomas Morgner;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: BeanObjectDescription.java,v 1.6 2005/10/18 13:31:58 mungady Exp $
036 *
037 * Changes (from 19-Feb-2003)
038 * -------------------------
039 * 19-Feb-2003 : Added standard header and Javadocs (DG);
040 * 29-Apr-2003 : Distilled from the JFreeReport project and moved into 
041 *               JCommon (TM);
042 * 18-Aug-2005 : Added casts to suppress compiler warnings, as suggested in 
043 *               patch 1260622 (DG);
044 *
045 */
046
047package org.jfree.xml.factory.objects;
048
049import java.lang.reflect.Method;
050import java.lang.reflect.Modifier;
051import java.util.Iterator;
052import java.util.TreeSet;
053import java.util.HashMap;
054import java.beans.Introspector;
055import java.beans.IntrospectionException;
056import java.beans.BeanInfo;
057import java.beans.PropertyDescriptor;
058import java.io.ObjectInputStream;
059import java.io.IOException;
060
061import org.jfree.util.Log;
062
063/**
064 * An object-description for a bean object. This object description
065 * is very dangerous, if the bean contains properties with undefined
066 * types.
067 *
068 * @author Thomas Morgner
069 */
070public class BeanObjectDescription extends AbstractObjectDescription {
071
072    private TreeSet ignoredParameters;
073    private transient HashMap properties;
074
075    /**
076     * Creates a new object description.
077     *
078     * @param className  the class.
079     */
080    public BeanObjectDescription(final Class className) {
081        this(className, true);
082    }
083
084    /**
085     * Creates a new object description.
086     *
087     * @param className  the class.
088     * @param init  set to true, to autmaoticly initialise the object 
089     *              description. If set to false, the initialisation is 
090     *              elsewhere.
091     */
092    public BeanObjectDescription(final Class className, final boolean init) {
093        super(className);
094        // now create some method descriptions ..
095        this.ignoredParameters = new TreeSet();
096        readBeanDescription(className, init);
097    }
098
099    private boolean isValidMethod (final Method method, final int parCount)
100    {
101        if (method == null) {
102            return false;
103        }
104        if (!Modifier.isPublic(method.getModifiers())) {
105            return false;
106        }
107        if (Modifier.isStatic(method.getModifiers())) {
108            return false;
109        }
110        if (method.getParameterTypes().length != parCount) {
111            return false;
112        }
113        return true;
114    }
115
116    /**
117     * Creates an object based on this description.
118     *
119     * @return The object.
120     */
121    public Object createObject() {
122        try {
123            final Object o = getObjectClass().newInstance();
124            // now add the various parameters ...
125
126            final Iterator it = getParameterNames();
127            while (it.hasNext()) {
128                final String name = (String) it.next();
129
130                if (isParameterIgnored(name)) {
131                    continue;
132                }
133
134                final Method method = findSetMethod(name);
135                final Object parameterValue = getParameter(name);
136                if (parameterValue == null) {
137                    // Log.debug ("Parameter: " + name + " is null");
138                }
139                else {
140                    method.invoke(o, new Object[]{parameterValue});
141                }
142            }
143            return o;
144        }
145        catch (Exception e) {
146            Log.error("Unable to invoke bean method", e);
147        }
148        return null;
149    }
150
151    /**
152     * Finds a set method in the bean.
153     *
154     * @param parameterName  the parameter name.
155     *
156     * @return The method.
157     */
158    private Method findSetMethod(final String parameterName) {
159        final PropertyDescriptor descriptor 
160            = (PropertyDescriptor) this.properties.get(parameterName);
161        return descriptor.getWriteMethod();
162    }
163
164    /**
165     * Finds a get method in the bean.
166     *
167     * @param parameterName  the paramater name.
168     * @return The method.
169     */
170    private Method findGetMethod(final String parameterName) {
171        final PropertyDescriptor descriptor 
172            = (PropertyDescriptor) this.properties.get(parameterName);
173        return descriptor.getReadMethod();
174    }
175
176    /**
177     * Sets the parameters in the description to match the supplied object.
178     *
179     * @param o  the object (<code>null</code> not allowed).
180     *
181     * @throws ObjectFactoryException if there is a problem.
182     */
183    public void setParameterFromObject(final Object o)
184        throws ObjectFactoryException {
185        if (o == null) {
186            throw new NullPointerException("Given object is null");
187        }
188        final Class c = getObjectClass();
189        if (!c.isInstance(o)) {
190            throw new ObjectFactoryException("Object is no instance of " + c 
191                + "(is " + o.getClass() + ")");
192        }
193
194        final Iterator it = getParameterNames();
195        while (it.hasNext()) {
196            final String propertyName = (String) it.next();
197
198            if (isParameterIgnored(propertyName)) {
199                continue;
200            }
201
202            try {
203                final Method method = findGetMethod(propertyName);
204                final Object retval = method.invoke(o, (Object[]) null);
205                if (retval != null) {
206                    setParameter(propertyName, retval);
207                }
208            }
209            catch (Exception e) {
210                Log.info("Exception on method invokation.", e);
211            }
212
213        }
214    }
215
216    /**
217     * Adds a parameter to the ignored parameters.
218     * 
219     * @param parameter  the parameter.
220     */
221    protected void ignoreParameter(final String parameter) {
222        this.ignoredParameters.add (parameter);
223    }
224
225    /**
226     * Returns a flag that indicates whether or not the specified parameter is 
227     * ignored.
228     * 
229     * @param parameter  the parameter.
230     * 
231     * @return The flag.
232     */
233    protected boolean isParameterIgnored (final String parameter) {
234        return this.ignoredParameters.contains(parameter);
235    }
236
237  private void readObject(final ObjectInputStream in)
238      throws IOException, ClassNotFoundException {
239    in.defaultReadObject();
240    readBeanDescription(getObjectClass(), false);
241  }
242
243  private void readBeanDescription(final Class className, final boolean init) {
244    try {
245        this.properties = new HashMap();
246
247        final BeanInfo bi = Introspector.getBeanInfo(className);
248        final PropertyDescriptor[] propertyDescriptors 
249            = bi.getPropertyDescriptors();
250        for (int i = 0; i < propertyDescriptors.length; i++)
251        {
252            final PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
253            final Method readMethod = propertyDescriptor.getReadMethod();
254            final Method writeMethod = propertyDescriptor.getWriteMethod();
255            if (isValidMethod(readMethod, 0) && isValidMethod(writeMethod, 1))
256            {
257                final String name = propertyDescriptor.getName();
258                this.properties.put(name, propertyDescriptor);
259                if (init) {
260                    super.setParameterDefinition(name, 
261                            propertyDescriptor.getPropertyType());
262                }
263            }
264        }
265    }
266    catch (IntrospectionException e) {
267        Log.error ("Unable to build bean description", e);
268    }
269  }
270}