/** * 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.IOException; import java.io.InputStream; import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.logging.Logger; import net.sf.freecol.common.FreeColException; import org.freecolandroid.debug.DebugBufferedInputStream; import org.freecolandroid.debug.DebugInputstream; import org.freecolandroid.debug.FCLog; import org.freecolandroid.xml.stream.XMLInputFactory; import org.freecolandroid.xml.stream.XMLStreamException; import org.freecolandroid.xml.stream.XMLStreamReader; import org.w3c.dom.Element; import org.xml.sax.SAXException; /** * The thread that checks for incoming messages. */ final class ReceivingThread extends Thread { private static final Logger logger = Logger.getLogger(ReceivingThread.class.getName()); /** Maximum number og retries before closing the connection. */ private static final int MAXIMUM_RETRIES = 5; private final FreeColNetworkInputStream in; private XMLStreamReader xmlIn = null; private boolean shouldRun; private int nextNetworkReplyId = 1; private final Map<Integer, NetworkReplyObject> threadsWaitingForNetworkReply; private final Connection connection; private boolean locked = false; private String threadName; /** * The constructor to use. * * @param connection The <code>Connection</code> this * <code>ReceivingThread</code> belongs to. * @param in The stream to read from. */ ReceivingThread(Connection connection, InputStream in, String threadName) { super(threadName + "ReceivingThread - " + connection.toString()); this.threadName = threadName; this.connection = connection; // this.in = new FreeColNetworkInputStream(in); this.in = new FreeColNetworkInputStream(new DebugInputstream(in, threadName)); shouldRun = true; threadsWaitingForNetworkReply = Collections.synchronizedMap( new HashMap<Integer, NetworkReplyObject>()); } /** * Gets the next <code>networkReplyId</code> that will be used when * identifing a network message. * * @return The next available <code>networkReplyId</code>. */ public synchronized int getNextNetworkReplyId() { return nextNetworkReplyId++; } /** * Creates and registers a new <code>NetworkReplyObject</code> with the * specified ID. * * @param networkReplyId The id of the message the calling thread should * wait for. * @return The <code>NetworkReplyObject</code> containing the network * message. */ public NetworkReplyObject waitForNetworkReply(int networkReplyId) { NetworkReplyObject nro = new NetworkReplyObject(networkReplyId, false); threadsWaitingForNetworkReply.put(networkReplyId, nro); return nro; } /** * Creates and registers a new <code>NetworkReplyObject</code> with the * specified ID. * * @param networkReplyId The id of the message the calling thread should * wait for. * @return The <code>NetworkReplyObject</code> containing the network * message. */ public NetworkReplyObject waitForStreamedNetworkReply(int networkReplyId) { NetworkReplyObject nro = new NetworkReplyObject(networkReplyId, true); threadsWaitingForNetworkReply.put(networkReplyId, nro); return nro; } /** * Receives messages from the network in a loop. This method is invoked when * the thread starts and the thread will stop when this method returns. */ public void run() { int timesFailed = 0; try { while (shouldRun()) { try { listen(); timesFailed = 0; } catch (XMLStreamException e) { FCLog.log(e); timesFailed++; // warnOf(e); if (shouldRun && timesFailed > MAXIMUM_RETRIES) { disconnect(); } } catch (SAXException e) { FCLog.log(e); timesFailed++; // warnOf(e); if (shouldRun && timesFailed > MAXIMUM_RETRIES) { disconnect(); } } catch (IOException e) { FCLog.log(e); if (shouldRun) { disconnect(); } } } } finally { askToStop(); } } public void unlock() { locked = false; } /** * Listens to the inputstream and calls the messagehandler for each message * received. * * @exception IOException If thrown by the {@link FreeColNetworkInputStream}. * @exception SAXException if a problem occured during parsing. * @exception XMLStreamException if a problem occured during parsing. */ private void listen() throws IOException, SAXException, XMLStreamException { while (locked) { try { // TODO: Fix this sleep(1) is not a solution Thread.sleep(1); } catch (InterruptedException e) { // Do nothing here. } } // final int LOOK_AHEAD = 500; final int LOOK_AHEAD = 16000; // BufferedInputStream bis = new BufferedInputStream(in, LOOK_AHEAD); BufferedInputStream bis = new DebugBufferedInputStream(in, LOOK_AHEAD); in.enable(); bis.mark(LOOK_AHEAD); if (!shouldRun()) return; XMLInputFactory xif = XMLInputFactory.newInstance(); xmlIn = xif.createXMLStreamReader(bis); xmlIn.nextTag(); boolean disconnectMessage = (xmlIn.getLocalName().equals("disconnect")) ? true : false; if (xmlIn.getLocalName().equals("reply")) { String networkReplyID = xmlIn.getAttributeValue(null, "networkReplyId"); NetworkReplyObject nro = threadsWaitingForNetworkReply.remove( Integer.valueOf(networkReplyID)); if (nro != null) { if (nro.isStreamed()) { locked = true; nro.setResponse(xmlIn); } else { xmlIn.close(); xmlIn = null; bis.reset(); final DOMMessage msg = new DOMMessage(bis); nro.setResponse(msg); } } else { while (xmlIn.hasNext()) { xmlIn.next(); } xmlIn.close(); xmlIn = null; logger.warning("Could not find networkReplyId=" + networkReplyID); } } else { FCLog.log("NetMsg: " + xmlIn.getLocalName()); xmlIn.close(); xmlIn = null; bis.reset(); connection.handleAndSendReply(bis); } if (disconnectMessage) { askToStop(); } } /** * Checks if this thread has been halted. */ private synchronized boolean shouldRun() { return shouldRun; } /** * Tells this thread that it doesn't need to do any more work. */ synchronized void askToStop() { shouldRun = false; for (NetworkReplyObject o : threadsWaitingForNetworkReply.values()) { o.interrupt(); } } private void disconnect() { if (connection.getMessageHandler() != null) { try { Element disconnectElement = DOMMessage.createNewRootElement("disconnect"); disconnectElement.setAttribute("reason", "reception exception"); connection.getMessageHandler().handle(connection, disconnectElement); } catch (FreeColException e) { e.printStackTrace(); } } } /** * Input stream for buffering the data from the network. * * <br> * <br> * * This is just a buffered input stream that signals end-of-stream when a * given token {@link #END_OF_STREAM} is encountered. In order to continue * receiving data, the method {@link #enable} has to be called. Calls to * <code>close()</code> has no effect, the underlying input stream has to * be closed directly. */ class FreeColNetworkInputStream extends InputStream { private static final int BUFFER_SIZE = 8192; private static final char END_OF_STREAM = '\n'; private final InputStream in; private byte[] buffer = new byte[BUFFER_SIZE]; private int bStart = 0; private int bEnd = 0; private boolean empty = true; private boolean wait = false; /** * Creates a new <code>FreeColNetworkInputStream</code>. * * @param in The input stream in which this object should get the data * from. */ FreeColNetworkInputStream(InputStream in) { this.in = in; } /** * Fills the buffer with data. * * @return <i>true</i> if the buffer has been filled with data, and * <i>false</i> if an error occured. * @exception IOException if thrown by the underlying stream. */ private boolean fill() throws IOException { int r; if (bStart < bEnd || empty && bStart == bEnd) { if (empty) { bStart = 0; bEnd = 0; } r = in.read(buffer, bEnd, BUFFER_SIZE - bEnd); } else if (bStart == bEnd) { throw new IllegalStateException(); } else { r = in.read(buffer, bEnd, bStart - bEnd); } if (r <= 0) { logger.fine("Could not read data from stream."); return false; } empty = false; bEnd += r; if (bEnd == BUFFER_SIZE) { bEnd = 0; } return true; } /** * Prepares the input stream for a new message. <br> * <br> * Makes the subsequent calls to <code>read</code> return the data * instead of <code>-1</code>. */ void enable() { wait = false; } /** * Reads a single byte. * * @see #read(byte[], int, int) */ public int read() throws IOException { if (wait) { return -1; } if (empty) { if (!fill()) { wait = true; FCLog.log(threadName + " - wait = true 5"); return -1; } } if (buffer[bStart] == END_OF_STREAM) { bStart++; if (bStart == BUFFER_SIZE) { bStart = 0; } if (bStart == bEnd) { empty = true; } wait = true; FCLog.log(threadName + " - wait = true 4"); FCLog.log(threadName + " - end of stream token"); return -1; } else { bStart++; if (bStart == bEnd || bEnd == 0 && bStart == BUFFER_SIZE) { empty = true; } if (bStart == BUFFER_SIZE) { bStart = 0; return buffer[BUFFER_SIZE - 1]; } else { return buffer[bStart - 1]; } } } /** * Reads from the buffer and returns the data. * * @param b The place where the data will be put. * @param off The offset to use when writing the data to <code>b</code>. * @param len Number of bytes to read. * @return The actual number of bytes read and <code>-1</code> if the * message has ended, that is; if the token * {@link #END_OF_STREAM} is encountered. * */ public int read(byte[] b, int off, int len) throws IOException { if (wait) { FCLog.log(threadName + " - end of stream 1"); return -1; } if (empty) { if (!fill()) { wait = true; FCLog.log(threadName + " - wait = true 3"); FCLog.log(threadName + " - end of stream 2"); return -1; } } int r = 0; for (; r < len; r++) { if (buffer[bStart] == END_OF_STREAM) { bStart++; if (bStart == BUFFER_SIZE) { bStart = 0; } if (bStart == bEnd) { empty = true; } FCLog.log(threadName + " - wait = true 1"); wait = true; return r; } b[r + off] = buffer[bStart]; bStart++; if (bStart == bEnd || bEnd == 0 && bStart == BUFFER_SIZE) { empty = true; if (!fill()) { wait = true; FCLog.log(threadName + " - wait = true 2"); return r + 1; } } if (bStart == BUFFER_SIZE) { bStart = 0; } } return len; } // private InputStream in; // // public FreeColNetworkInputStream(InputStream in) { // this.in = in; // } // // public void enable() { // // TODO Auto-generated method stub // // } // // @Override // public int available() throws IOException { // return in.available(); // } // // @Override // public void close() throws IOException { // in.close(); // } // // @Override // public void mark(int readlimit) { // in.mark(readlimit); // } // // @Override // public boolean markSupported() { // return in.markSupported(); // } // // @Override // public int read() throws IOException { // return in.read(); // } // // @Override // public int read(byte[] buffer, int offset, int length) // throws IOException { // return in.read(buffer, offset, length); // } // // @Override // public int read(byte[] buffer) throws IOException { // return in.read(buffer); // } // // @Override // public synchronized void reset() throws IOException { // in.reset(); // } // // @Override // public long skip(long byteCount) throws IOException { // return in.skip(byteCount); // } // } }