package otherSupport.bufferControl;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.Buffer;
import java.nio.ByteBuffer;

import sun.nio.ch.DirectBuffer;

/** 
 * @author oliford
 */
public class DirectBufferControl {

	    
	private static final String syncObj = "BufferDealloc syncObj";
	
	
	public static ByteBuffer allocateDirect(int capacity) {
		synchronized (syncObj) {
			return ByteBuffer.allocateDirect(capacity);
		}
	}
	
	/** 
	 * DirectByteBuffers are garbage collected by using a phantom reference and a
	 * reference queue. Every once a while, the JVM checks the reference queue and
	 * cleans the DirectByteBuffers. However, as this doesn't happen
	 * immediately after discarding all references to a DirectByteBuffer, it's
	 * easy to OutOfMemoryError yourself using DirectByteBuffers. This function
	 * explicitly calls the Cleaner method of a DirectByteBuffer.
	 * 
	 * Somehow, even with the syncronisation, this still misses stuff.
	 *  
	 * @param buff
	 *          The DirectByteBuffer that will be "cleaned". Utilizes reflection.
	 *          
	 */
	public static void freeBuffer(Buffer buff) {
		//All the classes we want are private, so do this by name (groan)
				
		if(!buff.isDirect())
			throw new IllegalArgumentException("Not a direct buffer");

		synchronized (syncObj) {
			
			Exception err;
			Method cleanerMethod;
			try {
				cleanerMethod = buff.getClass().getMethod("cleaner");
				cleanerMethod.setAccessible(true);
				Object cleaner = cleanerMethod.invoke(buff);
				if(cleaner == null){ 
					//buffers that are backed by another, e.g. the result of a slice() etc, will not be released
					System.err.println("Tried to free a buffer without a cleaner");
					return;
				}
				Method cleanMethod = cleaner.getClass().getMethod("clean");
				cleanMethod.setAccessible(true);
				cleanMethod.invoke(cleaner);
				
				
				return;
	
			} catch (SecurityException e) { err = e; }
			catch (NoSuchMethodException e) { err = e; }
			catch (IllegalArgumentException e) { err = e; }
			catch (IllegalAccessException e) { err = e; }
			catch (InvocationTargetException e) { err = e; }
			
			System.err.println("Caught exception trying to force deallocation checks on direct buffers: " + err.getMessage());
		}
	}
	
	public static final long getUsedMemory(){
		
		try {
			Class b = Class.forName("java.nio.Bits");
			Field f = b.getDeclaredField("reservedMemory");
			f.setAccessible(true);
			return (Long)f.get(null);
			
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return -1;
	}
	

	public static final long getMaxMemory(){
		
		try {
			Class b = Class.forName("java.nio.Bits");
			Field f = b.getDeclaredField("maxMemory");
			f.setAccessible(true);
			return (Long)f.get(null);
			
		} catch (ClassNotFoundException e1) {
			e1.printStackTrace();
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return -1;
	}
}
