/** * Copyright (C) 2002-2012 The FreeCol Team * * This file is part of FreeCol. * * FreeCol is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * FreeCol 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with FreeCol. If not, see <http://www.gnu.org/licenses/>. */ package net.sf.freecol.common.networking; import java.io.BufferedInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.util.logging.Level; import java.util.logging.Logger; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import net.sf.freecol.common.debug.FreeColDebugger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.InputSource; import org.xml.sax.SAXException; /** * Class for parsing raw message data into an XML-tree and for creating new * XML-trees. */ public class Message { protected static final Logger logger = Logger.getLogger(Message.class.getName()); private static final String FREECOL_PROTOCOL_VERSION = "0.1.6"; private static final String INVALID_MESSAGE = "invalid"; /** The actual Message data. */ protected Document document; protected Message() { // empty constructor } /** * Constructs a new Message with data from the given String. The * constructor to use if this is an INCOMING message. * * @param msg The raw message data. * @exception IOException should not be thrown. * @exception SAXException if thrown during parsing. */ public Message(String msg) throws SAXException, IOException { this(new InputSource(new StringReader(msg))); } /** * Constructs a new Message with data from the given InputStream. The * constructor to use if this is an INCOMING message. * * @param inputStream The <code>InputStream</code> to get the XML-data * from. * @exception IOException if thrown by the <code>InputStream</code>. * @exception SAXException if thrown during parsing. */ public Message(InputStream inputStream) throws SAXException, IOException { this(new InputSource(inputStream)); } /** * Constructs a new Message with data from the given InputSource. The * constructor to use if this is an INCOMING message. * * @param inputSource The <code>InputSource</code> to get the XML-data * from. * @exception IOException if thrown by the <code>InputSource</code>. * @exception SAXException if thrown during parsing. */ private Message(InputSource inputSource) throws SAXException, IOException { DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); Document tempDocument = null; boolean dumpMsgOnError = FreeColDebugger.isInDebugMode(); if (dumpMsgOnError) { /* * inputSource.setByteStream( new * ReplayableInputStream(inputSource.getByteStream()) ); * */ inputSource.setByteStream(new BufferedInputStream(inputSource.getByteStream())); inputSource.getByteStream().mark(1000000); } try { DocumentBuilder builder = factory.newDocumentBuilder(); tempDocument = builder.parse(inputSource); } catch (ParserConfigurationException pce) { // Parser with specified options can't be built logger.log(Level.WARNING, "Parser error", pce); } catch (SAXException se) { throw se; } catch (IOException ie) { throw ie; } catch (ArrayIndexOutOfBoundsException e) { // Xerces throws ArrayIndexOutOfBoundsException when it barfs on // some FreeCol messages. I'd like to see the messages upon which // it barfs if (dumpMsgOnError) { ByteArrayOutputStream baos = new ByteArrayOutputStream(); inputSource.getByteStream().reset(); while (true) { int i = inputSource.getByteStream().read(); if (-1 == i) { break; } baos.write(i); } logger.severe(baos.toString()); } throw e; } document = tempDocument; } /** * Constructs a new Message with data from the given XML-document. * * @param document The document representing an XML-message. */ public Message(Document document) { this.document = document; } /** * Gets the <code>Document</code> holding the message data. * * @return The <code>Document</code> holding the message data. */ public Document getDocument() { return document; } /** * Gets the type of this Message. * * @return The type of this Message. */ public String getType() { return (document != null && document.getDocumentElement() != null) ? document.getDocumentElement().getTagName() : INVALID_MESSAGE; } /** * Checks if this message is of a given type. * * @param type The type you wish to test against. * @return True if the type of this message equals the given type. */ public boolean isType(String type) { return getType().equals(type); } /** * Gets an attribute from the root element. * * @param key The key of the attribute. * @return The value of the attribute with the given key. */ public String getAttribute(String key) { return document.getDocumentElement().getAttribute(key); } /** * Checks if an attribute is set on the root element. * * @param attribute The attribute in which to verify the existence of. * @return <code>true</code> if the root element has the given attribute. */ public boolean hasAttribute(String attribute) { return document.getDocumentElement().hasAttribute(attribute); } /** * Sets an attribute on the root element. * * @param key The key of the attribute. * @param value The value of the attribute. */ public void setAttribute(String key, String value) { document.getDocumentElement().setAttribute(key, value); } /** * Sets an attribute on the root element. * * @param key The key of the attribute. * @param value The value of the attribute. */ public void setAttribute(String key, int value) { setAttribute(key, (new Integer(value)).toString()); } /** * Dummy serialization stub. * Must be overridden by subclasses. * * @return Null. */ public Element toXMLElement() { return null; // do nothing } /** * Returns the <code>String</code> representation of the message. * This is what actually gets transmitted to the other peer. * * @return The <code>String</code> representation of the message. */ @Override public String toString() { return document.getDocumentElement().toString(); } /** * Gets the current version of the FreeCol protocol. * * @return The version of the FreeCol protocol. */ public static String getFreeColProtocolVersion() { return FREECOL_PROTOCOL_VERSION; } }