NativeLinuxSocketImpl.java

package eu.javaexperience.nativ.java.socket;

import java.io.FileDescriptor;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketImpl;
import java.net.SocketImplFactory;
import java.util.Map;
import java.util.Map.Entry;

import eu.javaexperience.collection.map.SmallMap;
import eu.javaexperience.exceptions.UnimplementedMethodException;
import eu.javaexperience.io.fd.FDIOStream;
import eu.javaexperience.nativ.java.JavaNativeExtension;
import eu.javaexperience.nativ.java.SockAddr;
import eu.javaexperience.nativ.posix.INTEGER;
import eu.javaexperience.nativ.posix.Posix;
import eu.javaexperience.nativ.posix.PosixErrnoException;
import eu.javaexperience.reflect.CastTo;
import eu.javaexperience.reflect.Mirror;

public class NativeLinuxSocketImpl extends SocketImpl implements FileDescriptorHolder, FDIOStream
{
	public static final SocketImplFactory FACTORY = new SocketImplFactory()
	{
		@Override
		public SocketImpl createSocketImpl()
		{
			return new NativeLinuxSocketImpl();
		}
	};
	
	public static void overrideJavaSocketFacilities() throws IOException
	{
		ServerSocket.setSocketFactory(FACTORY);
		Socket.setSocketImplFactory(FACTORY);
	}
	
	protected int fileDescriptor;
	
	@Override
	public FileDescriptor getFileDescriptor()
	{
		return super.getFileDescriptor();
	}
	
	public NativeLinuxSocketImpl()
	{
		setFd(-1);
	}
	
	public NativeLinuxSocketImpl(int fd)
	{
		setFd(fileDescriptor);
	}
	
	protected void setFd(int fd)
	{
		this.fileDescriptor = fd;
		super.fd = fileDescriptor < 0? null:new INTEGER(fd).asFileDescriptor();
		setSockAddr(null);
	}
	
	protected SockAddr addr;
	
	protected void setSockAddr(SockAddr addr)
	{
		if(null != this.addr)
		{
			this.addr = null;
		}
		this.addr = addr;
	}
	
	Map<Integer, Integer> opts = new SmallMap<>();
	
	@Override
	public void setOption(int optID, Object value) throws SocketException
	{
		Integer v = (Integer) CastTo.Int.cast(value);
		if(null != v)
		{
			if(fileDescriptor >= 0)
			{
				applyOneOption(optID, v);
			}
			opts.put(optID, v);
		}
	}
	
	protected static final boolean printSetOpts = false;
	
	protected void applyOneOption(int k, int v)
	{
		long addr = 0;
		try
		{
			addr = Posix.malloc(4);
		
			Posix.setIntAt(addr, v);
			int ret = Posix.setsockopt(fileDescriptor, Posix.SOL_SOCKET, k, addr, 4);
			if(printSetOpts)System.out.println("NativeLinuxSocketImpl.applyOneOption"+this+" "+k+" => "+v+" ret: "+ret+" "+PosixErrnoException.getIfOcurred());
		}
		finally
		{
			Posix.free(addr);
		}
	}
	
	protected void applyOptions()
	{
		long addr = 0;
		try
		{
			addr = Posix.malloc(4);
		
			for(Entry<Integer, Integer> kv:opts.entrySet())
			{
				Posix.setIntAt(addr, kv.getValue());
				int ret = Posix.setsockopt(fileDescriptor, Posix.SOL_SOCKET, kv.getKey(), addr, 4);
				if(printSetOpts)System.out.println("NativeLinuxSocketImpl.applyOptions"+this+" "+kv.getKey()+" => "+kv.getValue()+" ret: "+ret);
			}
		}
		finally
		{
			Posix.free(addr);
		}
	}

	@Override
	public Object getOption(int optID) throws SocketException
	{
		if(printSetOpts)System.out.println("NativeLinuxSocketImpl.getOption"+this+" "+optID);
		return null;//opts.get(optID);
	}
	
	protected boolean createWithStream = true;
	
	protected void tryCreateLazy(int family)
	{
		if(fileDescriptor < 0)
		{
			int ret = Posix.socket(family, createWithStream?Posix.SOCK_STREAM:Posix.SOCK_DGRAM, 0);
			checkException(ret, false);
			setFd(ret);
			
			if(0 != JavaNativeExtension.initSocket(ret))
			{
				PosixErrnoException.throwRtIfErrorOcurred();
			}
			
			applyOptions();
		}
	}
	
	@Override
	public void create(boolean stream) throws IOException
	{
		createWithStream = stream;
	}
	
	public void connect(SockAddr a)
	{
		tryCreateLazy(a.getFamily());
		setSockAddr(a);
		int ret = Posix.connect(fileDescriptor, addr.getAddrPointer(), addr.getAddrLen());
		checkException(ret, true);
	}
	
	@Override
	public void connect(String host, int port) throws IOException
	{
		connect(SockAddr.fromIpv4(host, port));
	}

	@Override
	public void connect(InetAddress address, int port) throws IOException
	{
		connect(SockAddr.fromIpv4(address.getHostAddress(), port));
	}

	@Override
	public void connect(SocketAddress address, int timeout) throws IOException
	{
		//TODO timeout
		connect(SockAddr.fromSocketaddress((InetSocketAddress) address));
	}
	
	public void bind(SockAddr a) throws IOException
	{
		tryCreateLazy(a.getFamily());
		setSockAddr(a);
		int ret = -1;
		/*int att = 10;
		do
		{*/
			ret = Posix.bind(fileDescriptor, addr.getAddrPointer(), addr.getAddrLen());
			if(0 == ret)
			{
				return;
			}
			
			/*try
			{
				Thread.sleep(200);
			}
			catch(InterruptedException e)
			{
				Mirror.propagateAnyway(e);
			}
		}
		while(att-- > 0);*/
		checkException(ret, true);
	}

	@Override
	public void bind(InetAddress host, int port) throws IOException
	{
		bind(SockAddr.fromIpv4(host, port));
	}

	@Override
	public void listen(int backlog) throws IOException
	{
		int ret = Posix.listen(fileDescriptor, backlog);
		checkException(ret, false);
	}

	@Override
	public void accept(SocketImpl _s) throws IOException
	{
		NativeLinuxSocketImpl s = (NativeLinuxSocketImpl) _s;
		int ret = JavaNativeExtension.accept(fileDescriptor, addr.getAddrPointer(), addr.getAddrLen());
		checkException(ret, false);
		s.setFd(ret);
	}

	protected NativeLinuxInputStream is = new NativeLinuxInputStream(this);
	protected NativeLinuxOutputStream os = new NativeLinuxOutputStream(this);
	
	@Override
	public InputStream getInputStream()
	{
		return is;
	}

	@Override
	public OutputStream getOutputStream()
	{
		return os;
	}

	@Override
	protected int available() throws IOException
	{
		throw new UnimplementedMethodException("NativeLinuxSocketImpl.available");
	}

	@Override
	public void close()
	{
		Posix.close(fileDescriptor);
		setFd(-1);
	}

	@Override
	protected void sendUrgentData(int data) throws IOException
	{
		throw new UnimplementedMethodException("NativeLinuxSocketImpl.sendUrgentData");
	}

	@Override
	public int getFd()
	{
		return fileDescriptor;
	}
	
	protected boolean shIn = false;
	protected boolean shOut = false;
	
	protected void closeIfBothShutdown()
	{
		if(shIn && shOut)
		{
			close();
		}
	}
	
	@Override
	protected void shutdownInput() throws IOException
	{
		try
		{
			Posix.shutdown(fileDescriptor, Posix.SHUT_RD);
			PosixErrnoException.throwRtIfErrorOcurred();
		}
		finally
		{
			shIn = true;
			closeIfBothShutdown();
		}
	}
	
	@Override
	protected void shutdownOutput() throws IOException
	{
		try
		{
			Posix.shutdown(fileDescriptor, Posix.SHUT_WR);
			PosixErrnoException.throwRtIfErrorOcurred();
		}
		finally
		{
			shOut = true;
			closeIfBothShutdown();
		}
	}
	
	public static void checkException(int ret, boolean zeroOnly)
	{
		if(zeroOnly?0 != ret:ret < 0)
		{
			try
			{
				PosixErrnoException.throwIfErrorOcurred();
			}
			catch(Exception e)
			{
				Mirror.propagateAnyway(e);
			}
		}
	}

	@Override
	public boolean isClosed()
	{
		return -1 != fileDescriptor;
	}

	@Override
	public String localAddress()
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public String remoteAddress()
	{
		// TODO Auto-generated method stub
		return null;
	}

	@Override
	public void flush() throws IOException
	{
		os.flush();
	}

	@Override
	public int getFD()
	{
		return fileDescriptor;
	}

	@Override
	protected void finalize() throws Throwable
	{
		close();
	}
}