/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Michael Powers This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.web.xml; import java.io.IOException; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Stack; import net.wotonomy.foundation.internal.Introspector; import net.wotonomy.foundation.internal.ValueConverter; import net.wotonomy.foundation.internal.WotonomyException; import org.xml.sax.Attributes; import org.xml.sax.InputSource; import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.SAXParseException; import org.xml.sax.helpers.DefaultHandler; /** * Used by XMLDecoder to implement the necessary interfaces * required by the jclark xp parser. * This class is not thread safe. */ class XMLRPCDecoderHelper extends DefaultHandler { protected final Object nilMarker = new Object(); protected Stack valueStack; protected String methodName; protected int faultCode; protected String faultString; protected List parameters; protected StringBuffer cdataBuffer; public XMLRPCDecoderHelper() { valueStack = new Stack(); parameters = new LinkedList(); cdataBuffer = new StringBuffer(); reset(); } public void reset() { valueStack.clear(); parameters.clear(); cdataBuffer.setLength( 0 ); methodName = null; faultCode = 0; faultString = null; } public boolean isRequest() { return ( methodName != null ); } public boolean isResponse() { return ( methodName == null ); } public boolean isFault() { // faults are responses return ( isResponse() ) && ( faultString != null ); } public int getFaultCode() { return faultCode; } public String getFaultString() { return faultString; } public String getMethodName() { return methodName; } public Object[] getParameters() { return parameters.toArray(); } public void endDocument() throws SAXException { // TODO Auto-generated method stub super.endDocument(); } public void startDocument() throws SAXException { // TODO Auto-generated method stub super.startDocument(); reset(); } public Object getResult() { if ( valueStack.empty() ) return null; Object result = valueStack.peek(); if ( result == nilMarker ) result = null; return result; } public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); if ( XMLRPCEncoder.VALUE.equals( localName ) ) { ValueMarker marker = new ValueMarker(); String classname = attributes.getValue( uri, XMLRPCEncoder.CLASS ); if ( classname != null ) { try { Class c = Class.forName( classname ); if ( c != null ) { marker.setMarkerClass( c ); } } catch ( Exception exc ) { System.out.println( "XMLRPCDecoderHelper.startElement: " + "Can't find class: " + classname ); } } valueStack.push( marker ); } } public void characters(char[] ch, int start, int length) throws SAXException { super.characters(ch, start, length); char[] someChars = new char[((start+length)<=ch.length) ? length : ch.length-start]; for (int i = 0; i < someChars.length; i++ ) { someChars[i] = ch[start+i]; } cdataBuffer.append(someChars); } public void endElement(String uri, String localName, String qName) throws SAXException { super.endElement(uri, localName, qName); // if any cdata is buffered or if string value if ( ( XMLRPCEncoder.STRING.equals( localName ) ) || ( cdataBuffer.length() > 0 ) ) { // push value on the stack valueStack.push( cdataBuffer.toString() ); cdataBuffer.setLength( 0 ); } if ( XMLRPCEncoder.VALUE.equals( localName ) ) { Object value = valueStack.pop(); try { // ValueMarker marker = (ValueMarker) valueStack.pop(); ValueMarker marker = null; Object markerValue = valueStack.pop(); if ( markerValue instanceof ValueMarker ) { marker = (ValueMarker) markerValue; } else { throw new WotonomyException( "Expected value marker, found" + markerValue.getClass() + " : " + markerValue ); } //System.out.println( "getMarkerClass: " + marker.getMarkerClass() + " : " + value ); //System.out.println( valueStack ); if ( marker.getMarkerClass() != null ) { // apply introspection if ( value instanceof Map ) { Map map = (Map)value; Map.Entry entry; value = marker.getMarkerClass().newInstance(); Iterator it = map.entrySet().iterator(); Object entryValue; while( it.hasNext() ) { entry = (Map.Entry) it.next(); entryValue = entry.getValue(); if ( entryValue == nilMarker ) entryValue = null; Introspector.set( value, entry.getKey().toString(), entryValue ); } } if ( ! ( value.getClass().equals( marker.getMarkerClass() ) ) ) { Object converted = ValueConverter.convertObjectToClass( value, marker.getMarkerClass() ); if ( converted != null ) { value = converted; } } } } catch ( Exception exc ) { // fall back on unconverted value } valueStack.push( value ); // System.out.println( "convertedValue: " + value + "("+ value.getClass() +")" ); } else if ( XMLRPCEncoder.MEMBER.equals( localName ) ) // Map.Entry { // leave key and value to be handled by struct } else if ( XMLRPCEncoder.STRUCT.equals( localName ) ) // write Entries to map or object { // write values to array (reverse the order) Object value; Map map = new HashMap(); while ( ( ! valueStack.empty() ) && ( ! ( valueStack.peek() instanceof ValueMarker ) ) ) { value = valueStack.pop(); map.put( valueStack.pop(), value ); } // push the list on the stack valueStack.push( map ); } else if ( XMLRPCEncoder.ARRAY.equals( localName ) ) { //System.out.println( "ended ARRAY: " + valueStack.size() ); // write values to array (reverse the order) Object value; LinkedList list = new LinkedList(); while ( ( ! valueStack.empty() ) && ( ! ( valueStack.peek() instanceof ValueMarker ) ) ) { value = valueStack.pop(); if ( value == nilMarker ) value = null; list.addFirst( value ); } // push the list on the stack valueStack.push( list ); } else if ( XMLRPCEncoder.INT.equals( localName ) ) { Object value = valueStack.pop(); try { valueStack.push( new Integer( value.toString() ) ); } catch ( NumberFormatException exc ) { throw new WotonomyException( "Invalid double format: " + value.toString() ); } } else if ( XMLRPCEncoder.I4.equals( localName ) ) { Object value = valueStack.pop(); try { valueStack.push( new Integer( value.toString() ) ); } catch ( NumberFormatException exc ) { throw new WotonomyException( "Invalid double format: " + value.toString() ); } } else if ( XMLRPCEncoder.NIL.equals( localName ) ) { valueStack.push( nilMarker ); } else if ( XMLRPCEncoder.DOUBLE.equals( localName ) ) { Object value = valueStack.pop(); try { valueStack.push( new Double( value.toString() ) ); } catch ( NumberFormatException exc ) { throw new WotonomyException( "Invalid double format: " + value.toString() ); } } else if ( XMLRPCEncoder.DATE.equals( localName ) ) { Object value = valueStack.pop(); try { valueStack.push( XMLRPCEncoder.DATEFORMAT8601.parseObject( value.toString() ) ); } catch ( Exception exc ) { throw new WotonomyException( "Invalid date format: " + value ); } } else if ( XMLRPCEncoder.BOOLEAN.equals( localName ) ) { Object value = valueStack.pop(); if ( XMLRPCEncoder.TRUE.equals( value ) ) { valueStack.push( Boolean.TRUE ); } else if ( XMLRPCEncoder.FALSE.equals( value ) ) { valueStack.push( Boolean.FALSE ); } else { throw new WotonomyException( "Invalid boolean format: " + value ); } } else if ( XMLRPCEncoder.BASE64.equals( localName ) ) { throw new WotonomyException( "Not implemented yet." ); } else if ( XMLRPCEncoder.FAULT.equals( localName ) ) { Map faultMap = (Map) valueStack.pop(); try { faultCode = ((Integer) faultMap.get( XMLRPCEncoder.FAULTCODE )).intValue(); faultString = (String) faultMap.get( XMLRPCEncoder.FAULTSTRING ); } catch ( Exception exc ) { throw new WotonomyException( "Invalid fault format: " + faultMap ); } } else if ( XMLRPCEncoder.METHODNAME.equals( localName ) ) { methodName = (String) valueStack.pop(); } else if ( XMLRPCEncoder.PARAM.equals( localName ) ) { //NOTE: this leaves the parameter on the stack parameters.add( getResult() ); } } public void endPrefixMapping(String prefix) throws SAXException { // TODO Auto-generated method stub super.endPrefixMapping(prefix); } public void error(SAXParseException e) throws SAXException { // TODO Auto-generated method stub super.error(e); } public void fatalError(SAXParseException e) throws SAXException { // TODO Auto-generated method stub super.fatalError(e); } public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException { // TODO Auto-generated method stub super.ignorableWhitespace(ch, start, length); } public void notationDecl(String name, String publicId, String systemId) throws SAXException { // TODO Auto-generated method stub super.notationDecl(name, publicId, systemId); } public void processingInstruction(String target, String data) throws SAXException { // TODO Auto-generated method stub super.processingInstruction(target, data); } public InputSource resolveEntity(String publicId, String systemId) throws SAXException { // NOTE: Sun accepted an incompatible api difference. The (false) should be tossed by hotspot try { if (false) throw new IOException("Fake exception to make it compile in both 1.4 and 1.5"); return super.resolveEntity(publicId, systemId); } catch (IOException e) { throw new SAXException(e.getClass().getName() + " thrown while resolving entity.",e); } } public void setDocumentLocator(Locator locator) { // TODO Auto-generated method stub super.setDocumentLocator(locator); } public void skippedEntity(String name) throws SAXException { // TODO Auto-generated method stub super.skippedEntity(name); } public void startPrefixMapping(String prefix, String uri) throws SAXException { // TODO Auto-generated method stub super.startPrefixMapping(prefix, uri); } public void unparsedEntityDecl(String name, String publicId, String systemId, String notationName) throws SAXException { // TODO Auto-generated method stub super.unparsedEntityDecl(name, publicId, systemId, notationName); } public void warning(SAXParseException e) throws SAXException { // TODO Auto-generated method stub super.warning(e); } // marker class private class ValueMarker { private Class theClass; public ValueMarker() { theClass = null; } public void setMarkerClass( Class aClass ) { theClass = aClass; } public Class getMarkerClass() { return theClass; } public String toString() { return "[ValueMarker: " + theClass + "]"; } } } /* * $Log$ * Revision 1.1 2006/02/19 01:44:03 cgruber * Add xmlrpc files * Remove jclark and replace with dom4j and javax.xml.sax stuff * Re-work dependencies and imports so it all compiles. * * Revision 1.1 2006/02/16 13:22:22 cgruber * Check in all sources in eclipse-friendly maven-enabled packages. * * Revision 1.7 2003/08/06 23:07:53 chochos * general code cleanup (mostly, removing unused imports) * * Revision 1.6 2001/03/03 15:16:35 mpowers * Fixed bug in decoding empty strings: string handler did nothing and * empty cdatas were ignored, so no value was placed on the stack. * * Revision 1.5 2001/02/17 16:52:06 mpowers * Changes in imports to support building with jdk1.1 collections. * * Revision 1.4 2001/02/09 15:51:39 mpowers * Fixed a pernicious bug: I was using the character event entirely * incorrectly, but the problem only exhibited itself with large data * and even then only randomly. Now using a string buffer. * * Revision 1.3 2001/02/07 19:24:28 mpowers * Moved XML classes to separate package. * * Revision 1.2 2001/02/06 14:34:23 mpowers * Forgot to rename the package declarations. * * Revision 1.1 2001/02/06 14:31:19 mpowers * Moving XML utilities from util to xml package. * * Revision 1.1.1.1 2000/12/21 15:52:39 mpowers * Contributing wotonomy. * * Revision 1.2 2000/12/20 16:25:48 michael * Added log to all files. * * */