/* 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. * * */