JDK-4864117 : RFE: Make XMLDecoder API more reusable
  • Type: Enhancement
  • Component: client-libs
  • Sub-Component: java.beans
  • Affected Version: 1.4.0,1.4.2
  • Priority: P4
  • Status: Closed
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2003-05-14
  • Updated: 2017-05-16
  • Resolved: 2011-03-08
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 7
7 b46Fixed
Related Reports
Relates :  
Relates :  
Relates :  
Relates :  
Description
Name: rmT116609			Date: 05/14/2003


A DESCRIPTION OF THE REQUEST :
The XMLDecoder is a very useful unit of code. It is unfortunate that it is very inflexible in its use and API. The main problem lies with the use of an InputStream as the primary source of XML for parsing. In addition, the package protected nature of the XMLDecoders internals makes it impossible to customize without rewriting the entire beans package (which is bad because so much is static).

JUSTIFICATION :
If XML persisted beans reside in another XML document, in order to decode them one must either:
a) store them in a CDATA block, buffer the characters, turn them into bytes and then create an XMLDecoder using a ByteArrayInputStream
b) store them as normal elements in the document (taking care to remove the redundant <?xml version> element that the XMLEncoder places everytime it writes), reconstruct elements from the handler, buffer the elements, turn them into bytes, then create an XMLDecoder using ByteArrayInputStream

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
1) Make XMLEncoder flexible so that it does not always output the ever-so-useful <?xml version> element
2) Make XMLDecoder take a Reader as an argument so that if the data is ALREADY in character form, I don't have to waste time and memory converting it to bytes...
3) Restructure XMLDecoder so that sub-classes can have access to some internals. I understand the desire to keep JAXP classes out of the public XMLDecoder API, but for subclasses, access to a ContentHandler would make dealing with embedded bean documents much easier.
ACTUAL -
If one wants to embed a XMLEncoded bean in another document, one must take care to either
a) write the persisted bean within a CDATA section
b) remove the <?xml version> element the XMLEncoder produces

If one wants to decode an element which represents an encoded bean, one must buffer all the content, turn it into bytes, and pass the bytes to the XMLDecoder.

---------- BEGIN SOURCE ----------
//Encoder for embedding XML persisted beans in document
import java.beans.XMLEncoder;
import java.io.*;
public class MixedEncoder {
  
  PrintStream writer;
  TrickyOutputStream tricky;
  
  static final class TrickyOutputStream extends FilterOutputStream {
    boolean ignore = false;
    public TrickyOutputStream(OutputStream out) {
      super(out);
    }
    
    public void write(byte[] b) throws IOException {
      if (ignore) return;
      out.write(b);
    }
    
    public void write(byte[] b,int off,int len) throws IOException {
      if (ignore) return;
      out.write(b,off,len);
    }
    
    public void write(int i) throws IOException {
      if (ignore) return;
      out.write(i);
    }
    
    public void close() throws IOException {
       
    }
  }
  
  /** Creates a new instance of MixedEncoder */
  public MixedEncoder(OutputStream out) throws Exception {
    writer = new PrintStream(out,false,"UTF-8");
    tricky = new TrickyOutputStream(writer);
    writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
    writer.println("<specialDocument>");
  }
  
  public void writeObject(Object o,String extra) {
    writer.println("<special extra=\"" + extra + "\">");
    writer.println("<java>");
    XMLEncoder encoder = new XMLEncoder(tricky);
    tricky.ignore = true;
    encoder.flush();
    tricky.ignore = false;
    encoder.writeObject(o);
    encoder.flush();
    encoder.close();
    writer.println("</special>");
  }
  
  public void flush() {
    writer.flush();
  }
  
  public void close() {
    writer.println("</specialDocument>");
    writer.flush();
    writer.close();
  }
}

// Class for decoding from mixed document
import java.beans.*;
import java.io.*;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.*;
public class MixedDecoder {
  
  /** Creates a new instance of MixedDecoder */
  public MixedDecoder(InputStream in) throws Exception {
    SAXParserFactory factory = SAXParserFactory.newInstance();
    SAXParser parser = factory.newSAXParser();
    try {
      MixedHandler mh = new MixedHandler();
      parser.parse(in,mh);
    } catch (SAXParseException spe) {
      spe.getException().printStackTrace();
    }
  }
  
  static class MixedHandler extends DefaultHandler {
    
    String message;
    StringBuffer buffer;
    boolean special = false;
    
    public void startElement(String namespaceURI, String localName, String qName, org.xml.sax.Attributes atts) throws org.xml.sax.SAXException {
      if (special)
        appendStart(qName,atts);
      else if (qName.equals("special")) {
        message = atts.getValue("extra");
        special = true;
        buffer = new StringBuffer();
      }
    }
    
    private void appendStart(String qn,Attributes atts) {
      buffer.append('<').append(qn);
      if (atts.getLength() > 0)
        buffer.append(' ');
      for (int i = 0, ii = atts.getLength(); i < ii; i++) {
        buffer.append(atts.getQName(i)).append("=\"").append(atts.getValue(i)).append("\"");
      }
      buffer.append('>');
    }
    
    public void characters(char[] ch, int start, int length) throws org.xml.sax.SAXException {
      if (special) {
        while (start < ch.length && Character.isWhitespace(ch[start])) {start++; length--;}
        buffer.append(ch,start,length);
      }
    }
    
    public void endElement(String namespaceURI, String localName, String qName) throws org.xml.sax.SAXException {
      
      if (qName.equals("special")) {
        XMLDecoder decoder = new XMLDecoder(new ByteArrayInputStream(buffer.toString().getBytes()));
        if (decoder.readObject() == null)
          System.out.println("bogus");
        buffer = null;
        special = false;
      }
      else if (special)
        appendEnd(qName);
    }
    
    private void appendEnd(String qn) {
      buffer.append("</").append(qn).append(">\n");
    }
  }
}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
The hacks presented in the two classes provide a work around for this functionality.
(Review ID: 185723) 
======================================================================

Comments
EVALUATION If the customer wants to remove the <?xml version> declaration he can use the following public constructor since Java 7 XMLEncoder(OutputStream out, String charset, boolean declaration, int indentation) * @param declaration whether the XML declaration should be generated; * set this to <code>false</code> * when embedding the contents in another XML document
05-06-2007