001/* XMLEncoder.java
002 Copyright (C) 2004, 2005 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING.  If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library.  Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module.  An independent module is a module which is not derived from
033 or based on this library.  If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so.  If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039package java.beans;
040
041import gnu.java.beans.encoder.ScanEngine;
042
043import java.io.OutputStream;
044
045/**
046 * This class uses the {@link PersistenceDelegate} and {@link Encoder}
047 * infrastructure to generate an XML representation of the objects it
048 * serializes.
049 *
050 * @author Robert Schuster (robertschuster@fsfe.org)
051 * @since 1.4
052 */
053public class XMLEncoder
054  extends Encoder
055  implements AutoCloseable
056{
057  Object owner;
058
059  Exception exception;
060
061  ScanEngine scanEngine;
062
063  private int accessCounter = 0;
064
065  public XMLEncoder(OutputStream os)
066  {
067    scanEngine = new ScanEngine(os);
068  }
069
070  public void close()
071  {
072    if (scanEngine != null)
073      {
074        scanEngine.close();
075        scanEngine = null;
076      }
077  }
078
079  public void flush()
080  {
081    scanEngine.flush();
082  }
083
084  public void writeExpression(Expression expr)
085  {
086    // Implementation note: Why is this method overwritten and nearly exactly
087    // reimplemented as in Encoder?
088    // The Encoder class can (and should be) subclassed by users outside of the
089    // java.beans package. While I have doubts that this is possible from an
090    // API design point of view I tried to replicate the Encoder's behavior
091    // in the JDK as exactly as possible. This strictness however made it
092    // extremely complicated to implement the XMLEncoder's backend. Therefore
093    // I decided to copy the Encoder's implementation and make all changes
094    // I needed for a succesfull operation of XMLEncoder.
095    //
096    // The same is true for the writeStatement method.
097
098    //  Silently ignore out of bounds calls.
099    if (accessCounter <= 0)
100      return;
101
102    scanEngine.writeExpression(expr);
103
104
105    Object target = expr.getTarget();
106    Object value = null;
107    Object newValue = null;
108
109    try
110      {
111        value = expr.getValue();
112      }
113    catch (Exception e)
114      {
115        getExceptionListener().exceptionThrown(e);
116        return;
117      }
118
119
120    newValue = get(value);
121
122    if (newValue == null)
123      {
124        Object newTarget = get(target);
125        if (newTarget == null)
126          {
127            writeObject(target);
128            newTarget = get(target);
129
130            // May happen if exception was thrown.
131            if (newTarget == null)
132              {
133                return;
134              }
135          }
136
137        Object[] args = expr.getArguments();
138        Object[] newArgs = new Object[args.length];
139
140        for (int i = 0; i < args.length; i++)
141          {
142            newArgs[i] = get(args[i]);
143            if (newArgs[i] == null || isImmutableType(args[i].getClass()))
144              {
145                writeObject(args[i]);
146                newArgs[i] = get(args[i]);
147              }
148          }
149
150        Expression newExpr = new Expression(newTarget, expr.getMethodName(),
151                                            newArgs);
152
153        // Fakes the result of Class.forName(<primitiveType>) to make it possible
154        // to hand such a type to the encoding process.
155        if (value instanceof Class && ((Class) value).isPrimitive())
156          newExpr.setValue(value);
157
158        // Instantiates the new object.
159        try
160          {
161            newValue = newExpr.getValue();
162
163            putCandidate(value, newValue);
164          }
165        catch (Exception e)
166          {
167            getExceptionListener().exceptionThrown(e);
168
169            // In Statement.writeExpression we had no possibility to flags
170            // an erroneous state to the ScanEngine without behaving different
171            // to the JDK.
172            scanEngine.revoke();
173
174            return;
175          }
176
177        writeObject(value);
178
179      }
180    else if(value.getClass() == String.class || value.getClass() == Class.class)
181      {
182        writeObject(value);
183      }
184
185    scanEngine.end();
186  }
187
188  public void writeStatement(Statement stmt)
189  {
190    // In case of questions have a at the implementation note in
191    // writeExpression.
192
193    scanEngine.writeStatement(stmt);
194
195    //  Silently ignore out of bounds calls.
196    if (accessCounter <= 0)
197      return;
198
199    Object target = stmt.getTarget();
200
201    Object newTarget = get(target);
202    if (newTarget == null)
203      {
204        writeObject(target);
205        newTarget = get(target);
206      }
207
208    Object[] args = stmt.getArguments();
209    Object[] newArgs = new Object[args.length];
210
211    for (int i = 0; i < args.length; i++)
212      {
213        // Here is the difference to the original writeStatement
214        // method in Encoder. In case that the object is known or
215        // not an immutable we put it directly into the ScanEngine
216        // which will then generate an object reference for it.
217        newArgs[i] = get(args[i]);
218        if (newArgs[i] == null || isImmutableType(args[i].getClass()))
219          {
220            writeObject(args[i]);
221            newArgs[i] = get(args[i]);
222          }
223        else
224          scanEngine.writeObject(args[i]);
225      }
226
227    Statement newStmt = new Statement(newTarget, stmt.getMethodName(), newArgs);
228
229    try
230      {
231        newStmt.execute();
232      }
233    catch (Exception e)
234      {
235        getExceptionListener().exceptionThrown(e);
236
237        // In Statement.writeStatement we had no possibility to flags
238        // an erroneous state to the ScanEngine without behaving different
239        // to the JDK.
240        scanEngine.revoke();
241        return;
242      }
243
244    scanEngine.end();
245  }
246
247  public void writeObject(Object o)
248  {
249    accessCounter++;
250
251    scanEngine.writeObject(o);
252
253    if (get(o) == null)
254      super.writeObject(o);
255
256    accessCounter--;
257  }
258
259  public void setOwner(Object o)
260  {
261    owner = o;
262  }
263
264  public Object getOwner()
265  {
266    return owner;
267  }
268
269}