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 * ModelWriter.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: ModelWriter.java,v 1.3 2005/10/18 13:32:20 mungady Exp $
036 *
037 * Changes
038 * -------------------------
039 * 21.06.2003 : Initial version
040 *
041 */
042
043package org.jfree.xml.generator;
044
045import java.io.IOException;
046import java.io.Writer;
047
048import org.jfree.xml.generator.model.ClassDescription;
049import org.jfree.xml.generator.model.Comments;
050import org.jfree.xml.generator.model.DescriptionModel;
051import org.jfree.xml.generator.model.IgnoredPropertyInfo;
052import org.jfree.xml.generator.model.ManualMappingInfo;
053import org.jfree.xml.generator.model.MultiplexMappingInfo;
054import org.jfree.xml.generator.model.PropertyInfo;
055import org.jfree.xml.generator.model.PropertyType;
056import org.jfree.xml.generator.model.TypeInfo;
057import org.jfree.xml.util.ClassModelTags;
058import org.jfree.xml.writer.AttributeList;
059import org.jfree.xml.writer.SafeTagList;
060import org.jfree.xml.writer.XMLWriterSupport;
061
062/**
063 * A model writer.
064 */
065public class ModelWriter {
066
067    /** The tags that can be split. */
068    private static SafeTagList safeTags;
069
070    /**
071     * Returns the safe tag list.
072     * 
073     * @return The safe tag list.
074     */
075    public static SafeTagList getSafeTags() {
076        if (safeTags == null) {
077            safeTags = new SafeTagList();
078            safeTags.add(ClassModelTags.OBJECTS_TAG);
079            safeTags.add(ClassModelTags.OBJECT_TAG);
080            safeTags.add(ClassModelTags.CONSTRUCTOR_TAG);
081            safeTags.add(ClassModelTags.ELEMENT_PROPERTY_TAG);
082            safeTags.add(ClassModelTags.LOOKUP_PROPERTY_TAG);
083            safeTags.add(ClassModelTags.ATTRIBUTE_PROPERTY_TAG);
084            safeTags.add(ClassModelTags.PARAMETER_TAG);
085            safeTags.add(ClassModelTags.INCLUDE_TAG);
086            safeTags.add(ClassModelTags.IGNORED_PROPERTY_TAG);
087            safeTags.add(ClassModelTags.MANUAL_TAG);
088            safeTags.add(ClassModelTags.MAPPING_TAG);
089            safeTags.add(ClassModelTags.TYPE_TAG);
090        }
091        return safeTags;
092    }
093
094    /** A support class for writing XML tags. */
095    private XMLWriterSupport writerSupport;
096    
097    /** A model containing class descriptions. */
098    private DescriptionModel model;
099
100    /**
101     * Creates a new model writer instance.
102     */
103    public ModelWriter() {
104        this.writerSupport = new XMLWriterSupport(getSafeTags(), 0);
105    }
106
107    /**
108     * Returns the model.
109     * 
110     * @return The model.
111     */
112    public DescriptionModel getModel() {
113        return this.model;
114    }
115
116    /**
117     * Sets the model to be written.
118     * 
119     * @param model  the model.
120     */
121    public void setModel(final DescriptionModel model) {
122        this.model = model;
123    }
124
125    /**
126     * Writes an XML header.
127     * 
128     * @param writer  the writer.
129     * 
130     * @throws IOException if there is an I/O problem.
131     */
132    public static void writeXMLHeader(final Writer writer) throws IOException {
133        writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
134        writer.write(XMLWriterSupport.getLineSeparator());
135    }
136
137    /**
138     * Writes a set of comments.
139     * 
140     * @param writer  the writer.
141     * @param comments  a set of comments.
142     * 
143     * @throws IOException if there is an I/O problem.
144     */
145    protected void writeStandardComment(final Writer writer, final Comments comments) throws IOException {
146        if ((comments == null) || (comments.getOpenTagComment() == null)) {
147            writer.write(
148                "<!-- CVSTag: $Id: ModelWriter.java,v 1.3 2005/10/18 13:32:20 mungady Exp $ " 
149                + comments + " -->"
150            );
151            writer.write(XMLWriterSupport.getLineSeparator());
152        }
153        else {
154            writeComment(writer, comments.getOpenTagComment());
155        }
156    }
157
158    /**
159     * Writes a sequence of comments.
160     * 
161     * @param writer  the writer.
162     * @param comments  the comments (<code>null</code> ignored).
163     * 
164     * @throws IOException if there is an I/O problem.
165     */
166    protected void writeComment(final Writer writer, final String[] comments) throws IOException {
167        if (comments == null) {
168            return;
169        }
170        for (int i = 0; i < comments.length; i++) {
171            this.writerSupport.indent(writer, XMLWriterSupport.INDENT_ONLY);
172            writer.write("<!--");
173            writer.write(comments[i]);
174            writer.write("-->");
175            writer.write(XMLWriterSupport.getLineSeparator());
176        }
177    }
178
179    /**
180     * Writes the open comments from a set of comments.
181     * 
182     * @param writer  the writer.
183     * @param comments  the set of comments.
184     * 
185     * @throws IOException if there is an I/O problem.
186     */
187    protected void writeOpenComment(final Writer writer, final Comments comments) throws IOException {
188        if (comments == null) {
189            return;
190        }
191        if (comments.getOpenTagComment() == null) {
192            return;
193        }
194        writeComment(writer, comments.getOpenTagComment());
195    }
196
197    /**
198     * Writes the close comments from a set of comments.
199     * 
200     * @param writer  the writer.
201     * @param comments  the set of comments.
202     * 
203     * @throws IOException if there is an I/O problem.
204     */
205    protected void writeCloseComment(final Writer writer, final Comments comments) throws IOException {
206        if (comments == null) {
207            return;
208        }
209        if (comments.getCloseTagComment() == null) {
210            return;
211        }
212        writeComment(writer, comments.getCloseTagComment());
213    }
214
215    /**
216     * Writes a closed (short) tag with eventually nested comments.
217     *
218     * @param writer  the writer.
219     * @param tagName  the tag name.
220     * @param attributes  the attributes.
221     * @param comments  the comments.
222     * 
223     * @throws IOException if there is an I/O problem.
224     */
225    protected void writeTag(final Writer writer, 
226                            final String tagName,
227                            final AttributeList attributes,
228                            final Comments comments) throws IOException {
229        if (comments == null) {
230            this.writerSupport.writeTag(writer, tagName, attributes, XMLWriterSupport.CLOSE);
231        }
232        else {
233            writeOpenComment(writer, comments);
234            if (comments.getCloseTagComment() != null) {
235                this.writerSupport.writeTag(writer, tagName, attributes, XMLWriterSupport.OPEN);
236                writeCloseComment(writer, comments);
237                this.writerSupport.writeCloseTag(writer, tagName);
238            }
239            else {
240                this.writerSupport.writeTag(writer, tagName, attributes, XMLWriterSupport.CLOSE);
241            }
242        }
243    }
244
245    /**
246     * Writes a closed (short) tag with eventually nested comments.
247     *
248     * @param writer  the writer.
249     * @param tagName  the tag name.
250     * @param attribute  the attribute name.
251     * @param value  the attribute value.
252     * @param comments  the comments.
253     * 
254     * @throws IOException if there is an I/O problem.
255     */
256    protected void writeTag(final Writer writer, 
257                            final String tagName,
258                            final String attribute, 
259                            final String value,
260                            final Comments comments) throws IOException {
261        if (comments == null) {
262            this.writerSupport.writeTag(writer, tagName, attribute, value , XMLWriterSupport.CLOSE);
263        }
264        else {
265            writeOpenComment(writer, comments);
266            if (comments.getCloseTagComment() != null) {
267                this.writerSupport.writeTag(
268                    writer, tagName, attribute, value, XMLWriterSupport.OPEN
269                );
270                writeCloseComment(writer, comments);
271                this.writerSupport.writeCloseTag(writer, tagName);
272            }
273            else {
274                this.writerSupport.writeTag(
275                    writer, tagName, attribute, value, XMLWriterSupport.CLOSE
276                );
277            }
278        }
279    }
280
281    /**
282     * Writes a model to the specified writer.
283     * 
284     * @param writer  the writer.
285     * 
286     * @throws IOException if there is an I/O problem.
287     */
288    public void write(final Writer writer) throws IOException {
289        
290        writeStandardComment(writer, this.model.getModelComments());
291        this.writerSupport.writeTag(writer, ClassModelTags.OBJECTS_TAG);
292        final String[] sources = this.model.getSources();
293        for (int i = 0; i < sources.length; i++) {
294            final Comments comments = this.model.getIncludeComment(sources[i]);
295            writeTag(
296                writer, 
297                ClassModelTags.INCLUDE_TAG, ClassModelTags.SOURCE_ATTR, sources[i], comments
298            );
299        }
300
301        for (int i = 0; i < this.model.size(); i++) {
302            final ClassDescription cd = this.model.get(i);
303            writeClassDescription(writer, cd);
304        }
305
306        final ManualMappingInfo[] mappings = getModel().getMappingModel().getManualMapping();
307        for (int i = 0; i < mappings.length; i++) {
308            final ManualMappingInfo mi = mappings[i];
309            writeManualMapping(writer, mi);
310        }
311
312        final MultiplexMappingInfo[] mmappings = getModel().getMappingModel().getMultiplexMapping();
313        for (int i = 0; i < mmappings.length; i++) {
314            final MultiplexMappingInfo mi = mmappings[i];
315            writeMultiplexMapping(writer, mi);
316        }
317
318        writeCloseComment(writer, this.model.getModelComments());
319        this.writerSupport.writeCloseTag(writer, ClassModelTags.OBJECTS_TAG);
320        
321    }
322
323    /**
324     * Writes a manual mapping to the XML output.
325     * 
326     * @param writer  the writer.
327     * @param mi  the mapping info.
328     * 
329     * @throws IOException if there is an I/O problem.
330     */
331    protected void writeManualMapping(final Writer writer, final ManualMappingInfo mi) throws IOException {
332        final AttributeList al = new AttributeList();
333        al.setAttribute(ClassModelTags.CLASS_ATTR, mi.getBaseClass().getName());
334        al.setAttribute(ClassModelTags.READ_HANDLER_ATTR, mi.getReadHandler().getName());
335        al.setAttribute(ClassModelTags.WRITE_HANDLER_ATTR, mi.getWriteHandler().getName());
336        writeTag(writer, ClassModelTags.MANUAL_TAG, al, mi.getComments());
337    }
338
339    /**
340     * Writes a multiplex mapping to the XML output.
341     * 
342     * @param writer  the writer.
343     * @param mi  the mapping info.
344     * 
345     * @throws IOException if there is an I/O problem.
346     */
347    protected void writeMultiplexMapping(final Writer writer, final MultiplexMappingInfo mi)
348        throws IOException {
349        
350        final TypeInfo[] tis = mi.getChildClasses();
351
352        final AttributeList al = new AttributeList();
353        al.setAttribute(ClassModelTags.BASE_CLASS_ATTR, mi.getBaseClass().getName());
354        al.setAttribute(ClassModelTags.TYPE_ATTR, mi.getTypeAttribute());
355        getWriterSupport().writeTag(writer, ClassModelTags.MAPPING_TAG, al, XMLWriterSupport.OPEN);
356
357        for (int j = 0; j < tis.length; j++) {
358            final AttributeList tiAttr = new AttributeList();
359            tiAttr.setAttribute(ClassModelTags.NAME_ATTR, tis[j].getName());
360            tiAttr.setAttribute(ClassModelTags.CLASS_ATTR, tis[j].getType().getName());
361            writeTag(writer, ClassModelTags.TYPE_TAG, tiAttr, tis[j].getComments());
362        }
363
364        getWriterSupport().writeCloseTag(writer, ClassModelTags.MAPPING_TAG);
365    }
366
367    /**
368     * Writes a class description.
369     * 
370     * @param writer  the writer.
371     * @param cd  the class description.
372     * 
373     * @throws IOException if there is an I/O problem.
374     */
375    protected void writeClassDescription(final Writer writer, final ClassDescription cd) throws IOException {
376
377        if (cd.isUndefined()) {
378            return;
379        }
380
381        final AttributeList al = new AttributeList();
382        al.setAttribute(ClassModelTags.CLASS_ATTR, cd.getName());
383        if (cd.getRegisterKey() != null) {
384            al.setAttribute(ClassModelTags.REGISTER_NAMES_ATTR, cd.getRegisterKey());
385        }
386        if (cd.isPreserve()) {
387            al.setAttribute(ClassModelTags.IGNORE_ATTR, "true");
388        }
389        this.writerSupport.writeTag(writer, ClassModelTags.OBJECT_TAG, al, XMLWriterSupport.OPEN);
390
391        final TypeInfo[] constructorInfo = cd.getConstructorDescription();
392        if (constructorInfo != null && constructorInfo.length != 0) {
393            this.writerSupport.writeTag(writer, ClassModelTags.CONSTRUCTOR_TAG);
394            for (int i = 0; i < constructorInfo.length; i++) {
395                final AttributeList constructorList = new AttributeList();
396                constructorList.setAttribute(
397                    ClassModelTags.CLASS_ATTR, constructorInfo[i].getType().getName()
398                );
399                constructorList.setAttribute(
400                    ClassModelTags.PROPERTY_ATTR, constructorInfo[i].getName()
401                );
402                writeTag(writer, ClassModelTags.PARAMETER_TAG, constructorList, 
403                         constructorInfo[i].getComments());
404            }
405            this.writerSupport.writeCloseTag(writer, ClassModelTags.CONSTRUCTOR_TAG);
406        }
407
408        final PropertyInfo[] properties = cd.getProperties();
409        for (int i = 0; i < properties.length; i++) {
410            writePropertyInfo(writer, properties[i]);
411        }
412
413        this.writerSupport.writeCloseTag(writer, ClassModelTags.OBJECT_TAG);
414    }
415
416    /**
417     * Writes a property info element.
418     * 
419     * @param writer  the writer.
420     * @param ipi  the property info.
421     * 
422     * @throws IOException if there is an I/O problem.
423     */
424    private void writePropertyInfo(final Writer writer, final PropertyInfo ipi) throws IOException {
425        final AttributeList props = new AttributeList();
426        props.setAttribute(ClassModelTags.NAME_ATTR, ipi.getName());
427
428        if (ipi instanceof IgnoredPropertyInfo) {
429            writeTag(writer, ClassModelTags.IGNORED_PROPERTY_TAG, props, ipi.getComments());
430            return;
431        }
432
433        if (ipi.getPropertyType().equals(PropertyType.ATTRIBUTE)) {
434            props.setAttribute(ClassModelTags.ATTRIBUTE_ATTR, ipi.getXmlName());
435            props.setAttribute(ClassModelTags.ATTRIBUTE_HANDLER_ATTR, ipi.getXmlHandler());
436            writeTag(writer, ClassModelTags.ATTRIBUTE_PROPERTY_TAG, props, ipi.getComments());
437        }
438        else if (ipi.getPropertyType().equals(PropertyType.ELEMENT)) {
439            if (ipi.getComments() == null || ipi.getComments().getOpenTagComment() == null)
440            {
441                this.writerSupport.indent(writer, XMLWriterSupport.INDENT_ONLY);
442                writer.write("<!-- property type is " + ipi.getType() + " -->");
443                writer.write(System.getProperty("line.separator", "\n"));
444            }
445            props.setAttribute(ClassModelTags.ELEMENT_ATTR, ipi.getXmlName());
446            writeTag(writer, ClassModelTags.ELEMENT_PROPERTY_TAG, props, ipi.getComments());
447        }
448        else {
449            props.setAttribute(ClassModelTags.LOOKUP_ATTR, ipi.getXmlName());
450            writeTag(writer, ClassModelTags.LOOKUP_PROPERTY_TAG, props, ipi.getComments());
451        }
452    }
453
454    /**
455     * Returns the writer support object.
456     * 
457     * @return The writer support object.
458     */
459    public XMLWriterSupport getWriterSupport() {
460        return this.writerSupport;
461    }
462}