1 /*
   2  * Copyright (c) 2000, 2008, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package java.beans;
  26 
  27 import com.sun.beans.decoder.DocumentHandler;
  28 
  29 import java.io.Closeable;
  30 import java.io.InputStream;
  31 import java.io.IOException;
  32 
  33 import org.xml.sax.InputSource;
  34 import org.xml.sax.helpers.DefaultHandler;
  35 
  36 /**
  37  * The <code>XMLDecoder</code> class is used to read XML documents
  38  * created using the <code>XMLEncoder</code> and is used just like
  39  * the <code>ObjectInputStream</code>. For example, one can use
  40  * the following fragment to read the first object defined
  41  * in an XML document written by the <code>XMLEncoder</code>
  42  * class:
  43  * <pre>
  44  *       XMLDecoder d = new XMLDecoder(
  45  *                          new BufferedInputStream(
  46  *                              new FileInputStream("Test.xml")));
  47  *       Object result = d.readObject();
  48  *       d.close();
  49  * </pre>
  50  *
  51  *<p>
  52  * For more information you might also want to check out
  53  * <a
  54  href="http://java.sun.com/products/jfc/tsc/articles/persistence3">Long Term Persistence of JavaBeans Components: XML Schema</a>,
  55  * an article in <em>The Swing Connection.</em>
  56  * @see XMLEncoder
  57  * @see java.io.ObjectInputStream
  58  *
  59  * @since 1.4
  60  *
  61  * @author Philip Milne
  62  */
  63 public class XMLDecoder {
  64     private final DocumentHandler handler = new DocumentHandler();
  65     private final InputSource input;
  66     private Object owner;
  67     private Object[] array;
  68     private int index;
  69 
  70     /**
  71      * Creates a new input stream for reading archives
  72      * created by the <code>XMLEncoder</code> class.
  73      *
  74      * @param in The underlying stream.
  75      *
  76      * @see XMLEncoder#XMLEncoder(java.io.OutputStream)
  77      */
  78     public XMLDecoder(InputStream in) {
  79         this(in, null);
  80     }
  81 
  82     /**
  83      * Creates a new input stream for reading archives
  84      * created by the <code>XMLEncoder</code> class.
  85      *
  86      * @param in The underlying stream.
  87      * @param owner The owner of this stream.
  88      *
  89      */
  90     public XMLDecoder(InputStream in, Object owner) {
  91         this(in, owner, null);
  92     }
  93 
  94     /**
  95      * Creates a new input stream for reading archives
  96      * created by the <code>XMLEncoder</code> class.
  97      *
  98      * @param in the underlying stream.
  99      * @param owner the owner of this stream.
 100      * @param exceptionListener the exception handler for the stream;
 101      *        if <code>null</code> the default exception listener will be used.
 102      */
 103     public XMLDecoder(InputStream in, Object owner, ExceptionListener exceptionListener) {
 104         this(in, owner, exceptionListener, null);
 105     }
 106 
 107     /**
 108      * Creates a new input stream for reading archives
 109      * created by the <code>XMLEncoder</code> class.
 110      *
 111      * @param in the underlying stream.  <code>null</code> may be passed without
 112      *        error, though the resulting XMLDecoder will be useless
 113      * @param owner the owner of this stream.  <code>null</code> is a legal
 114      *        value
 115      * @param exceptionListener the exception handler for the stream, or
 116      *        <code>null</code> to use the default
 117      * @param cl the class loader used for instantiating objects.
 118      *        <code>null</code> indicates that the default class loader should
 119      *        be used
 120      * @since 1.5
 121      */
 122     public XMLDecoder(InputStream in, Object owner,
 123                       ExceptionListener exceptionListener, ClassLoader cl) {
 124         this(new InputSource(in), owner, exceptionListener, cl);
 125     }
 126 
 127 
 128     /**
 129      * Creates a new decoder to parse XML archives
 130      * created by the {@code XMLEncoder} class.
 131      * If the input source {@code is} is {@code null},
 132      * no exception is thrown and no parsing is performed.
 133      * This behavior is similar to behavior of other constructors
 134      * that use {@code InputStream} as a parameter.
 135      *
 136      * @param is  the input source to parse
 137      *
 138      * @since 1.7
 139      */
 140     public XMLDecoder(InputSource is) {
 141         this(is, null, null, null);
 142     }
 143 
 144     /**
 145      * Creates a new decoder to parse XML archives
 146      * created by the {@code XMLEncoder} class.
 147      *
 148      * @param is     the input source to parse
 149      * @param owner  the owner of this decoder
 150      * @param el     the exception handler for the parser,
 151      *               or {@code null} to use the default exception handler
 152      * @param cl     the class loader used for instantiating objects,
 153      *               or {@code null} to use the default class loader
 154      *
 155      * @since 1.7
 156      */
 157     private XMLDecoder(InputSource is, Object owner, ExceptionListener el, ClassLoader cl) {
 158         this.input = is;
 159         this.owner = owner;
 160         setExceptionListener(el);
 161         this.handler.setClassLoader(cl);
 162         this.handler.setOwner(this);
 163     }
 164 
 165     /**
 166      * This method closes the input stream associated
 167      * with this stream.
 168      */
 169     public void close() {
 170         if (parsingComplete()) {
 171             close(this.input.getCharacterStream());
 172             close(this.input.getByteStream());
 173         }
 174     }
 175 
 176     private void close(Closeable in) {
 177         if (in != null) {
 178             try {
 179                 in.close();
 180             }
 181             catch (IOException e) {
 182                 getExceptionListener().exceptionThrown(e);
 183             }
 184         }
 185     }
 186 
 187     private boolean parsingComplete() {
 188         if (this.input == null) {
 189             return false;
 190         }
 191         if (this.array == null) {
 192             this.handler.parse(this.input);
 193             this.array = this.handler.getObjects();
 194         }
 195         return true;
 196     }
 197 
 198     /**
 199      * Sets the exception handler for this stream to <code>exceptionListener</code>.
 200      * The exception handler is notified when this stream catches recoverable
 201      * exceptions.
 202      *
 203      * @param exceptionListener The exception handler for this stream;
 204      * if <code>null</code> the default exception listener will be used.
 205      *
 206      * @see #getExceptionListener
 207      */
 208     public void setExceptionListener(ExceptionListener exceptionListener) {
 209         if (exceptionListener == null) {
 210             exceptionListener = Statement.defaultExceptionListener;
 211         }
 212         this.handler.setExceptionListener(exceptionListener);
 213     }
 214 
 215     /**
 216      * Gets the exception handler for this stream.
 217      *
 218      * @return The exception handler for this stream.
 219      *     Will return the default exception listener if this has not explicitly been set.
 220      *
 221      * @see #setExceptionListener
 222      */
 223     public ExceptionListener getExceptionListener() {
 224         return this.handler.getExceptionListener();
 225     }
 226 
 227     /**
 228      * Reads the next object from the underlying input stream.
 229      *
 230      * @return the next object read
 231      *
 232      * @throws ArrayIndexOutOfBoundsException if the stream contains no objects
 233      *         (or no more objects)
 234      *
 235      * @see XMLEncoder#writeObject
 236      */
 237     public Object readObject() {
 238         return (parsingComplete())
 239                 ? this.array[this.index++]
 240                 : null;
 241     }
 242 
 243     /**
 244      * Sets the owner of this decoder to <code>owner</code>.
 245      *
 246      * @param owner The owner of this decoder.
 247      *
 248      * @see #getOwner
 249      */
 250     public void setOwner(Object owner) {
 251         this.owner = owner;
 252     }
 253 
 254     /**
 255      * Gets the owner of this decoder.
 256      *
 257      * @return The owner of this decoder.
 258      *
 259      * @see #setOwner
 260      */
 261     public Object getOwner() {
 262         return owner;
 263     }
 264 
 265     /**
 266      * Creates a new handler for SAX parser
 267      * that can be used to parse embedded XML archives
 268      * created by the {@code XMLEncoder} class.
 269      *
 270      * The {@code owner} should be used if parsed XML document contains
 271      * the method call within context of the &lt;java&gt; element.
 272      * The {@code null} value may cause illegal parsing in such case.
 273      * The same problem may occur, if the {@code owner} class
 274      * does not contain expected method to call. See details <a
 275      * href="http://java.sun.com/products/jfc/tsc/articles/persistence3/">here</a>.
 276      *
 277      * @param owner  the owner of the default handler
 278      *               that can be used as a value of &lt;java&gt; element
 279      * @param el     the exception handler for the parser,
 280      *               or {@code null} to use the default exception handler
 281      * @param cl     the class loader used for instantiating objects,
 282      *               or {@code null} to use the default class loader
 283      * @return an instance of {@code DefaultHandler} for SAX parser
 284      *
 285      * @since 1.7
 286      */
 287     public static DefaultHandler createHandler(Object owner, ExceptionListener el, ClassLoader cl) {
 288         DocumentHandler handler = new DocumentHandler();
 289         handler.setOwner(owner);
 290         handler.setExceptionListener(el);
 291         handler.setClassLoader(cl);
 292         return handler;
 293     }
 294 }