/* * This file is part of the Haven & Hearth game client. * Copyright (C) 2009 Fredrik Tolf <fredrik@dolda2000.com>, and * Björn Johannessen <johannessen.bjorn@gmail.com> * * Redistribution and/or modification of this file is subject to the * terms of the GNU Lesser General Public License, version 3, as * published by the Free Software Foundation. * * This program 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. * * Other parts of this source tree adhere to other copying * rights. Please see the file `COPYING' in the root directory of the * source tree for details. * * A copy the GNU Lesser General Public License is distributed along * with the source tree of which this file is a part in the file * `doc/LPGL-3'. If it is missing for any reason, please see the Free * Software Foundation's website at <http://www.fsf.org/>, or write * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, * Boston, MA 02111-1307 USA */ package haven; import java.io.*; import java.net.*; import java.nio.channels.ClosedByInterruptException; /* * This is horrible. This code exists to mitigate two bugs in Sun's * J2SE. * * First of all, the standard java.net.Socket implementation is not * interruptible per the standard Thread.interrupt mechanism. It is * very hard to see that as anything but a bug. * * Second of all, the java.nio.channels.SocketChannel has a bug on * Windows that prevents it from connecting to IPv6 addresses. (The * reason for that bug is most likely that Sun has two completely * different implementations for the java.net sockets and the NIO * sockets, that don't seem to share a single line of code, and they * probably missed one of the four functions for creating a socket * when making the code IPv6-capable, but that's an aside.) * * So where does this leave Haven, which uses thread interruption * heavily, if I want to be able to connect to IPv6 addresses? Well, * in hack-land, of course; where else? */ public class HackSocket extends Socket { private InputStream in = null; private OutputStream out = null; private ThreadLocal<InterruptAction> ia = new ThreadLocal<InterruptAction>(); private class InterruptAction implements Runnable { private boolean interrupted; public void run() { interrupted = true; try { HackSocket.this.close(); } catch(IOException e) { /* * Emm, well... Yeah. * * If the close fails, there isn't really a * lot to do about it, I guess. It's probably * unreasonable to throw exceptions around on * the thread calling interrupt(), though, so * the best action is probably to discard this * exception. */ } } } private void hook() { Thread ct = Thread.currentThread(); if(!(ct instanceof HackThread)) throw(new RuntimeException("Tried to use an HackSocket on a non-hacked thread.")); final HackThread ut = (HackThread)ct; InterruptAction ia = new InterruptAction(); ut.addil(ia); this.ia.set(ia); } private void release() throws ClosedByInterruptException { HackThread ut = (HackThread)Thread.currentThread(); InterruptAction ia = this.ia.get(); if(ia == null) throw(new Error("Tried to release a hacked thread without an interrupt handler.")); ut.remil(ia); if(ia.interrupted) { ut.interrupt(); throw(new ClosedByInterruptException()); } } public void connect(SocketAddress address, int timeout) throws IOException { hook(); try { super.connect(address, timeout); } finally { release(); } } public void connect(SocketAddress address) throws IOException { connect(address, 0); } private class HackInputStream extends InputStream { private InputStream bk; private HackInputStream(InputStream bk) { this.bk = bk; } public void close() throws IOException {bk.close();} public int read() throws IOException { hook(); try { return(bk.read()); } finally { release(); } } public int read(byte[] buf) throws IOException { hook(); try { return(bk.read(buf)); } finally { release(); } } public int read(byte[] buf, int off, int len) throws IOException { hook(); try { return(bk.read(buf, off, len)); } finally { release(); } } } private class HackOutputStream extends OutputStream { private OutputStream bk; private HackOutputStream(OutputStream bk) { this.bk = bk; } public void close() throws IOException {bk.close();} public void flush() throws IOException { hook(); try { bk.flush(); } finally { release(); } } public void write(int b) throws IOException { hook(); try { bk.write(b); } finally { release(); } } public void write(byte[] buf) throws IOException { hook(); try { bk.write(buf); } finally { release(); } } public void write(byte[] buf, int off, int len) throws IOException { hook(); try { bk.write(buf, off, len); } finally { release(); } } } public InputStream getInputStream() throws IOException { synchronized(this) { if(in == null) in = new HackInputStream(super.getInputStream()); return(in); } } public OutputStream getOutputStream() throws IOException { synchronized(this) { if(out == null) out = new HackOutputStream(super.getOutputStream()); return(out); } } }