Frames | No Frames |
1: /* java.beans.XMLDecoder -- 2: Copyright (C) 2004, 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.beans; 40: 41: import gnu.java.beans.DefaultExceptionListener; 42: import gnu.java.beans.decoder.PersistenceParser; 43: 44: import java.io.IOException; 45: import java.io.InputStream; 46: import java.util.Iterator; 47: import java.util.NoSuchElementException; 48: 49: /** 50: * The XMLDecoder reads XML data that is structured according to 51: * <a href="http://java.sun.com/products/jfc/tsc/articles/persistence3/javabeans.dtd">this</a> DTD 52: * and creates objects according to the content. Usually such data is generated using the 53: * {@link XMLEncoder} class. 54: * <p> 55: * An example XML document might look like this: 56: * <code> 57: * <java> 58: * <string>Hello World</string> 59: * <int>200</int> 60: * </java> 61: * </code> 62: * <p>To read the <code>String</code> and the <code>Integer</code> instance the following can be used (assume 63: * the XML data can be obtained from the InputStream):</p> 64: * <code> 65: * XMLDecoder decoder = new XMLDecoder(inputStreamContainingXMLData); 66: * String message = (String) decoder.readObject(); 67: * Integer number = (Integer) decoder.readObject(); 68: * </code> 69: * <p>Besides this basic functionality the <code>XMLDecoder</code> has some more features that might come 70: * handy in certain situations:</p> 71: * <p>An owner object can be set using the <code>setOwner</code> method which can then be accessed when 72: * decoding. This feature is only useful if the XML data is aware of the owner object. Such data may 73: * look like this (assume that the owner object is a JFrame instance):</p> 74: * <code> 75: * <java> 76: * <void method="getOwner"> 77: * <void method="setVisible"> 78: * <boolean>true<boolean> 79: * </void> 80: * </void> 81: * </java> 82: * </code> 83: * This accesses the <code>JFrame</code> and makes it visible using the <code>setVisible</code> method. 84: * <p>Please note that changing the owner <b>after</b> the having read the first object has no effect, 85: * because all object have been decoded then.</p> 86: * <p>If the <code>XMLDecoder</code> is created with no {@link ExceptionListener} instance a default one 87: * is used that prints an error message to <code>System.err</code> whenever a recoverable exception 88: * is thrown. Recovarable exceptions occur when the XML data cannot be interpreted correctly (e.g 89: * unknown classes or methods, invocation on null, ...). In general be very careful when the 90: * <code>XMLDecoder</code> provoked such exceptions because the resulting object(s) may be in an 91: * undesirable state.</p> 92: * <p>Note that changing the ExceptionListener instance after <code>readObject</code> has been called 93: * once has no effect because the decoding is completed then.</p> 94: * <p>At last one can provide a specific <code>ClassLoader</code> which is then used when <code>Class</code> 95: * objects are accessed. See {@link java.lang.Class#forName(String, boolean, ClassLoader)} for details 96: * on this.</p> 97: * <p>Note: If the <code>InputStream</code> instance given to any of the constructors is <code>null</code> 98: * the resulting <code>XMLDecoder</code> will be silently (without any exception) useless. Each call 99: * to <code>readObject</code> will return <code>null</code> and never throws an 100: * <code>ArrayIndexOutOfBoundsException</code>.</p> 101: * 102: * @author Robert Schuster 103: * @since 1.4 104: * @status updated to 1.5 105: */ 106: public class XMLDecoder 107: implements AutoCloseable 108: { 109: private Object owner; 110: 111: private ExceptionListener exceptionListener; 112: 113: private InputStream inputStream; 114: 115: private boolean isStreamClosed; 116: 117: private ClassLoader classLoader; 118: 119: private Iterator iterator; 120: 121: /** Creates a XMLDecoder instance that parses the XML data of the given input stream. 122: * Using this constructor no special ClassLoader, a default ExceptionListener 123: * and no owner object is used. 124: * 125: * @param in InputStream to read XML data from. 126: */ 127: public XMLDecoder(InputStream in) 128: { 129: this(in, null); 130: } 131: 132: /** Creates a XMLDecoder instance that parses the XML data of the given input stream. 133: * Using this constructor no special ClassLoader and a default ExceptionListener 134: * is used. 135: * 136: * @param in InputStream to read XML data from. 137: * @param owner Owner object which can be accessed and modified while parsing. 138: */ 139: public XMLDecoder(InputStream in, Object owner) 140: { 141: this(in, owner, null); 142: } 143: 144: /** Creates a XMLDecoder instance that parses the XML data of the given input stream. 145: * If the ExceptionListener argument is null a default implementation is used. 146: * 147: * @param in InputStream to read XML data from. 148: * @param owner Owner object which can be accessed and modified while parsing. 149: * @param exceptionListener ExceptionListener instance to which exception notifications are send. 150: */ 151: public XMLDecoder( 152: InputStream in, 153: Object owner, 154: ExceptionListener exceptionListener) 155: { 156: this( 157: in, 158: owner, 159: exceptionListener, 160: Thread.currentThread().getContextClassLoader()); 161: } 162: 163: /** Creates a XMLDecoder instance that parses the XML data of the given input stream. 164: * If the ExceptionListener argument is null a default implementation is used. 165: * 166: * @param in InputStream to read XML data from. 167: * @param owner Owner object which can be accessed and modified while parsing. 168: * @param listener ExceptionListener instance to which exception notifications are send. 169: * @param cl ClassLoader instance that is used for calls to <code>Class.forName(String, boolean, ClassLoader)</code> 170: * @since 1.5 171: */ 172: public XMLDecoder( 173: InputStream in, 174: Object owner, 175: ExceptionListener listener, 176: ClassLoader cl) 177: { 178: // initially here was a check for the validity of the InputStream argument but some 179: // great engineers decided that this API should silently discard this and behave rather 180: // odd: readObject will always return null ... 181: inputStream = in; 182: 183: setExceptionListener(listener); 184: 185: // validity of this object is checked in Class.forName() and therefore may be null 186: classLoader = cl; 187: 188: this.owner = owner; 189: } 190: 191: /** Closes the stream associated with this decoder. This should be done after having read all 192: * decoded objects. 193: * <p>See the description of the {@link #readObject()} for the effect caused by <code>close</code>.</p> 194: */ 195: public void close() 196: { 197: if (isStreamClosed) 198: { 199: return; 200: } 201: 202: try 203: { 204: inputStream.close(); 205: isStreamClosed = true; 206: } 207: catch (IOException e) 208: { 209: // bad style forced by original API design ... 210: } 211: } 212: 213: /** Returns the ExceptionListener instance associated with this decoder. 214: * <p>See the description of {@link XMLDecoder} class for more information on the ExceptionListener.</p> 215: * 216: * @return Current ExceptionListener of the decoder. 217: */ 218: public ExceptionListener getExceptionListener() 219: { 220: return exceptionListener; 221: } 222: 223: /** Returns the owner object of the decoder. This method is usually called 224: * from within the parsed XML data. 225: * <p>See the description of {@link XMLDecoder} class for more information on the owner object.</p> 226: * 227: * @return The owner object of this decoder. 228: */ 229: public Object getOwner() 230: { 231: return owner; 232: } 233: 234: /** Returns the next available decoded object. 235: * <p>Note that the actual decoding takes place when the method is called for the first time.</p> 236: * <p>If the <code>close</code> method was already called a <code>NoSuchElementException</code> 237: * is thrown.</p> 238: * <p>If the InputStream instance used in the constructors was <code>null</code> this method 239: * will always return <code>null</code> itself.</p> 240: * 241: * @return The next object in a sequence decoded from XML data. 242: * @throws ArrayIndexOutOfBoundsException When no more objects are available. 243: */ 244: public Object readObject() throws ArrayIndexOutOfBoundsException 245: { 246: // note: the RI does it this way ... 247: if(inputStream == null) { 248: return null; 249: } 250: 251: // note: the original API documentation says nothing on what to do 252: // when the stream was closed before readObject is called but it actually 253: // throws a NoSuchElementException - this behaviour is imitated here 254: if (isStreamClosed) 255: { 256: throw new NoSuchElementException("Cannot read any objects - XMLDecoder was already closed."); 257: } 258: 259: // creates the PersistenceParser (doing the parsing and decoding) and returns its 260: // Iterator on first invocation 261: if (iterator == null) 262: { 263: iterator = 264: new PersistenceParser( 265: inputStream, 266: exceptionListener, 267: classLoader, 268: this) 269: .iterator(); 270: } 271: 272: // note: done according to the official documentation 273: if (!iterator.hasNext()) 274: { 275: throw new ArrayIndexOutOfBoundsException("No more objects available from this XMLDecoder."); 276: } 277: 278: // returns just the next object if there was no problem 279: return iterator.next(); 280: } 281: 282: /** Sets the ExceptionListener instance to which notifications of exceptions are send 283: * while parsing the XML data. 284: * <p>See the description of {@link XMLDecoder} class for more information on the ExceptionListener.</p> 285: * 286: * @param listener 287: */ 288: public void setExceptionListener(ExceptionListener listener) 289: { 290: // uses a default implementation when null 291: if (listener == null) 292: { 293: listener = DefaultExceptionListener.INSTANCE; 294: } 295: exceptionListener = listener; 296: } 297: 298: /** Sets the owner object which can be accessed from the parsed XML data. 299: * <p>See the description of {@link XMLDecoder} class for more information on the owner object.</p> 300: * 301: * @param newOwner 302: */ 303: public void setOwner(Object newOwner) 304: { 305: owner = newOwner; 306: } 307: 308: }