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 * DefaultModelReader.java
029 * -----------------------
030 * (C)opyright 2003, 2004, by Thomas Morgner and Contributors.
031 *
032 * Original Author:  Thomas Morgner;
033 * Contributor(s):   David Gilbert (for Object Refinery Limited);
034 *
035 * $Id: DefaultModelReader.java,v 1.2 2005/10/18 13:32:20 mungady Exp $
036 *
037 * Changes
038 * -------
039 * 12-Nov-2003 : Initial version (TM);
040 * 26-Nov-2003 : Updated header and Javadocs (DG);
041 *
042 */
043
044package org.jfree.xml.generator;
045
046import java.beans.BeanInfo;
047import java.beans.IntrospectionException;
048import java.beans.Introspector;
049import java.beans.PropertyDescriptor;
050import java.io.File;
051import java.io.IOException;
052import java.net.URL;
053import java.util.ArrayList;
054
055import org.jfree.io.IOUtils;
056import org.jfree.xml.generator.model.ClassDescription;
057import org.jfree.xml.generator.model.Comments;
058import org.jfree.xml.generator.model.DescriptionModel;
059import org.jfree.xml.generator.model.IgnoredPropertyInfo;
060import org.jfree.xml.generator.model.ManualMappingInfo;
061import org.jfree.xml.generator.model.MultiplexMappingInfo;
062import org.jfree.xml.generator.model.PropertyInfo;
063import org.jfree.xml.generator.model.PropertyType;
064import org.jfree.xml.generator.model.TypeInfo;
065import org.jfree.xml.util.AbstractModelReader;
066import org.jfree.xml.util.ObjectDescriptionException;
067
068/**
069 * A reader for the class model.
070 */
071public class DefaultModelReader extends AbstractModelReader {
072
073    /** A model containing classes and the corresponding class descriptions. */
074    private DescriptionModel model;
075
076    /** The class description under construction. */
077    private ClassDescription currentClassDescription;
078
079    /** Information about the class being processed. */
080    private BeanInfo currentBeanInfo;
081
082    /** The base URL. */
083    private URL baseURL;
084    
085    /** The source. */
086    private String source;
087    
088    /** The multiplex mapping info. */
089    private MultiplexMappingInfo multiplexInfo;
090    
091    /** The multiplex type info.*/
092    private ArrayList multiplexTypeInfos;
093
094    /** Storage for the properties of the current class. */
095    private ArrayList propertyList;
096
097    /** Storage for the constructors of the current class. */
098    private ArrayList constructorList;
099
100    /**
101     * Creates a new model reader.
102     */
103    public DefaultModelReader() {
104        super();
105    }
106
107    /**
108     * Loads a description model.
109     * 
110     * @param file  the file name.
111     * 
112     * @return A description model.
113     * 
114     * @throws IOException  if there is an I/O problem.
115     * @throws ObjectDescriptionException  if there is a problem reading the object descriptions.
116     */
117    public synchronized DescriptionModel load(final String file) throws IOException,
118                                                                  ObjectDescriptionException {
119        
120        this.model = new DescriptionModel();
121        this.baseURL = new File (file).toURL();
122        parseXml(this.baseURL);
123        fillSuperClasses();
124        return this.model;
125        
126    }
127
128    /**
129     * Iterates through all the class descriptions in the model, setting the superclass
130     * attribute in all cases where the superclass definitions are contained in the model.
131     */
132    protected void fillSuperClasses() {
133        for (int i = 0; i < this.model.size(); i++) {
134            final ClassDescription cd = this.model.get(i);
135            final Class parent = cd.getObjectClass().getSuperclass();
136            if (parent == null) {
137                continue;
138            }
139            final ClassDescription superCD = this.model.get(parent);
140            if (superCD != null) {
141                cd.setSuperClass(superCD.getObjectClass());
142            }
143        }
144    }
145
146    /**
147     * Begin processing an object definition element.
148     *
149     * @param className  the class name.
150     * @param register  the register name (<code>null</code> permitted).
151     * @param ignore  ??
152     *
153     * @return <code>true</code> if the class is available, and <code>false</code> otherwise.
154     */
155    protected boolean startObjectDefinition(final String className, final String register, final boolean ignore) {
156        final Class c = loadClass(className);
157        if (c == null) {
158            return false;
159        }
160        this.currentClassDescription = new ClassDescription(c);
161        this.currentClassDescription.setPreserve(ignore);
162        this.currentClassDescription.setRegisterKey(register);
163        try {
164            this.currentBeanInfo = Introspector.getBeanInfo(c, Object.class);
165        }
166        catch (IntrospectionException ie) {
167            return false;
168        }
169        this.propertyList = new java.util.ArrayList();
170        this.constructorList = new java.util.ArrayList();
171        return true;
172    }
173
174    /**
175     * Finishes processing an object definition (sets the constructor and property info for the
176     * class description, and adds the class description to the model).
177     * 
178     * @throws ObjectDescriptionException if there is a problem with the object description.
179     */
180    protected void endObjectDefinition() throws ObjectDescriptionException {
181        final PropertyInfo[] pis = (PropertyInfo[])
182            this.propertyList.toArray(new PropertyInfo[this.propertyList.size()]);
183        this.currentClassDescription.setProperties(pis);
184
185        final TypeInfo[] tis = (TypeInfo[])
186        this.constructorList.toArray(new TypeInfo[this.constructorList.size()]);
187
188        this.currentClassDescription.setConstructorDescription(tis);
189        this.currentClassDescription.setComments
190            (new Comments(getOpenComment(), getCloseComment()));
191        this.currentClassDescription.setSource(this.source);
192
193        this.model.addClassDescription(this.currentClassDescription);
194
195        this.propertyList = null;
196        this.currentBeanInfo = null;
197        this.currentClassDescription = null;
198    }
199
200    /**
201     * Handles the description of an attribute within an object definition.
202     *
203     * @param name  the name.
204     * @param attribName  the attribute name.
205     * @param handlerClass  the fully qualified class name for the attribute handler.
206     * 
207     * @throws ObjectDescriptionException if there is a problem with the object description.
208     */
209    protected void handleAttributeDefinition(final String name, final String attribName, final String handlerClass)
210        throws ObjectDescriptionException {
211
212        final PropertyInfo propertyInfo = ModelBuilder.getInstance().createSimplePropertyInfo
213            (getPropertyDescriptor(name));
214
215        if (propertyInfo == null) {
216            throw new ObjectDescriptionException("Unable to load property " + name);
217        }
218
219        propertyInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
220        propertyInfo.setPropertyType(PropertyType.ATTRIBUTE);
221        propertyInfo.setXmlName(attribName);
222        propertyInfo.setXmlHandler(handlerClass);
223        this.propertyList.add(propertyInfo);
224    }
225
226    /**
227     * Handles the constructor definition.
228     * 
229     * @param tagName  the tag name.
230     * @param parameterClass  the parameter class.
231     * 
232     * @throws ObjectDescriptionException if there is a problem with the object description.
233     */
234    protected void handleConstructorDefinition(final String tagName, final String parameterClass)
235        throws ObjectDescriptionException {
236
237        final Class c = loadClass(parameterClass);
238        if (c == null) {
239            throw new ObjectDescriptionException("Failed to load class " + parameterClass);
240        }
241        final TypeInfo ti = new TypeInfo(tagName, c);
242        ti.setComments(new Comments(getOpenComment(), getCloseComment()));
243        this.constructorList.add (ti);
244    }
245
246    /**
247     * Handles the description of an element within an object definition.
248     *
249     * @param name  the property name.
250     * @param element  the element name.
251     * 
252     * @throws ObjectDescriptionException if there is a problem with the object description.
253     */
254    protected void handleElementDefinition(final String name, final String element)
255        throws ObjectDescriptionException {
256
257        final PropertyInfo propertyInfo = ModelBuilder.getInstance().createSimplePropertyInfo
258            (getPropertyDescriptor(name));
259
260        if (propertyInfo == null) {
261            throw new ObjectDescriptionException("Unable to load property " + name);
262        }
263
264        propertyInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
265        propertyInfo.setPropertyType(PropertyType.ELEMENT);
266        propertyInfo.setXmlName(element);
267        propertyInfo.setXmlHandler(null);
268        this.propertyList.add(propertyInfo);
269
270    }
271
272    /**
273     * Handles a lookup definition.
274     * 
275     * @param name  the name.
276     * @param lookupKey  the lookup key.
277     * 
278     * @throws ObjectDescriptionException if there is a problem with the object description.
279     */
280    protected void handleLookupDefinition(final String name, final String lookupKey)
281        throws ObjectDescriptionException {
282        final PropertyInfo propertyInfo = ModelBuilder.getInstance().createSimplePropertyInfo
283            (getPropertyDescriptor(name));
284
285        if (propertyInfo == null) {
286            throw new ObjectDescriptionException("Unable to load property " + name);
287        }
288
289        propertyInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
290        propertyInfo.setPropertyType(PropertyType.LOOKUP);
291        propertyInfo.setXmlName(lookupKey);
292        propertyInfo.setXmlHandler(null);
293        this.propertyList.add(propertyInfo);
294    }
295
296    /**
297     * Returns a property descriptor for the named property, or <code>null</code> if there is
298     * no descriptor with the given name.
299     *
300     * @param propertyName  the property name.
301     *
302     * @return a property descriptor.
303     */
304    protected PropertyDescriptor getPropertyDescriptor(final String propertyName) {
305        final PropertyDescriptor[] pds = this.currentBeanInfo.getPropertyDescriptors();
306        for (int i = 0; i < pds.length; i++) {
307            if (pds[i].getName().equals(propertyName)) {
308                return pds[i];
309            }
310        }
311        return null;
312    }
313
314    /**
315     * Handles an ignored property.
316     * 
317     * @param name  the name.
318     */
319    protected void handleIgnoredProperty(final String name) {
320        final IgnoredPropertyInfo propertyInfo = new IgnoredPropertyInfo(name);
321        propertyInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
322        this.propertyList.add(propertyInfo);
323    }
324
325    /**
326     * Handles a manual mapping.
327     *
328     * @param className  the class name.
329     * @param readHandler  the read handler.
330     * @param writeHandler  the write handler.
331     * 
332     * @return A boolean.
333     * 
334     * @throws ObjectDescriptionException if there is a problem with the object description.
335     */
336    protected boolean handleManualMapping(final String className, final String readHandler, final String writeHandler)
337        throws ObjectDescriptionException {
338
339        final ManualMappingInfo manualMappingInfo =
340            new ManualMappingInfo(loadClass(className),
341                loadClass(readHandler), loadClass(writeHandler));
342        manualMappingInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
343        manualMappingInfo.setSource(this.source);
344        this.model.getMappingModel().addManualMapping(manualMappingInfo);
345        return true;
346    }
347
348    /**
349     * Start a multiplex mapping.
350     * 
351     * @param className  the class name.
352     * @param typeAttr  the type.
353     */
354    protected void startMultiplexMapping(final String className, final String typeAttr) {
355        this.multiplexInfo = new MultiplexMappingInfo(loadClass(className), typeAttr);
356        this.multiplexInfo.setSource(this.source);
357        this.multiplexTypeInfos = new ArrayList();
358    }
359
360    /**
361     * Handles a multiplex mapping.
362     * 
363     * @param typeName  the type name.
364     * @param className  the class name.
365     * 
366     * @throws ObjectDescriptionException if there is a problem with the object description.
367     */
368    protected void handleMultiplexMapping(final String typeName, final String className)
369        throws ObjectDescriptionException {
370        final TypeInfo info = new TypeInfo(typeName, loadClass(className));
371        info.setComments(new Comments(getOpenComment(), getCloseComment()));
372        this.multiplexTypeInfos.add (info);
373    }
374
375    /**
376     * Ends a multiplex mapping.
377     * 
378     * @throws ObjectDescriptionException if there is a problem with the object description. 
379     */
380    protected void endMultiplexMapping() throws ObjectDescriptionException {
381        final TypeInfo[] typeInfos = (TypeInfo[]) this.multiplexTypeInfos.toArray(
382            new TypeInfo[this.multiplexTypeInfos.size()]
383        );
384        this.multiplexInfo.setComments(new Comments(getOpenComment(), getCloseComment()));
385        this.multiplexInfo.setChildClasses(typeInfos);
386        this.model.getMappingModel().addMultiplexMapping(this.multiplexInfo);
387        this.multiplexInfo = null;
388    }
389
390    /**
391     * Starts include handling.
392     * 
393     * @param resource  the URL.
394     */
395    protected void startIncludeHandling(final URL resource) {
396        this.source = IOUtils.getInstance().createRelativeURL(resource, this.baseURL);
397        this.model.addSource(this.source);
398        this.model.addIncludeComment(
399            this.source, new Comments(getOpenComment(), getCloseComment())
400        );
401    }
402
403    /**
404     * Ends include handling.
405     */
406    protected void endIncludeHandling() {
407        this.source = "";
408    }
409
410    /**
411     * Starts the root document.
412     */
413    protected void startRootDocument() {
414        this.source = "";
415    }
416
417    /**
418     * Ends the root document.
419     */
420    protected void endRootDocument() {
421        this.model.setModelComments(new Comments(getOpenComment(), getCloseComment()));
422    }
423}