public final class Native {

    private static final String VERSION = "3.2.7";

    private static String nativeLibraryPath = null;
    private static boolean unpacked;
    private static Map typeMappers = new WeakHashMap();
    private static Map alignments = new WeakHashMap();
    private static Map options = new WeakHashMap();
    private static Map libraries = new WeakHashMap();
    private static final UncaughtExceptionHandler DEFAULT_HANDLER = 
        new UncaughtExceptionHandler() {
            public void uncaughtException(Callback c, Throwable e) {
                System.err.println("JNA: Callback " + c + " threw the following exception:");
                e.printStackTrace();
            }
        };
    private static UncaughtExceptionHandler callbackExceptionHandler = DEFAULT_HANDLER;
    
    /** The size of a native pointer (<code>void*</code>) on the current 
     * platform, in bytes. 
     */
    public static final int POINTER_SIZE;
    /** Size of a native <code>long</code> type, in bytes. */
    public static final int LONG_SIZE;
    /** Size of a native <code>wchar_t</code> type, in bytes. */
    public static final int WCHAR_SIZE;
    /** Size of a native <code>size_t</code> type, in bytes. */
    public static final int SIZE_T_SIZE;

    private static final int TYPE_VOIDP = 0;
    private static final int TYPE_LONG = 1;
    private static final int TYPE_WCHAR_T = 2;
    private static final int TYPE_SIZE_T = 3;

    static {
        loadNativeLibrary();
        POINTER_SIZE = sizeof(TYPE_VOIDP);
        LONG_SIZE = sizeof(TYPE_LONG);
        WCHAR_SIZE = sizeof(TYPE_WCHAR_T);
        SIZE_T_SIZE = sizeof(TYPE_SIZE_T);
        // Perform initialization of other JNA classes until *after* 
        // initializing the above final fields
        initIDs();
        if (Boolean.getBoolean("jna.protected")) {
            setProtected(true);
        }
    }
    
    /** Ensure our unpacked native library gets cleaned up if this class gets
        garbage-collected.
    */
    private static final Object finalizer = new Object() {
        protected void finalize() {
            deleteNativeLibrary();
        }
    };

    /** Remove any unpacked native library.  Forcing the class loader to
        unload it first is required on Windows, since the temporary native
        library can't be deleted until the native library is unloaded.  Any
        deferred execution we might install at this point would prevent the
        Native class and its class loader from being GC'd, so we instead force
        the native library unload just a little bit prematurely.
     */
    private static boolean deleteNativeLibrary() {
        String path = nativeLibraryPath;
        if (path == null || !unpacked) return true;
        File flib = new File(path);
        if (flib.delete()) {
            nativeLibraryPath = null;
            unpacked = false;
            return true;
        }
        // Reach into the bowels of ClassLoader to force the native
        // library to unload
        // NOTE: this may cause a failure when freeing com.sun.jna.Memory
        // after the library has been unloaded, see
        // https://jna.dev.java.net/issues/show_bug.cgi?id=157
        try {
            ClassLoader cl = Native.class.getClassLoader();
            Field f = ClassLoader.class.getDeclaredField("nativeLibraries");
            f.setAccessible(true);
            List libs = (List)f.get(cl);
            for (Iterator i = libs.iterator();i.hasNext();) {
                Object lib = i.next();
                f = lib.getClass().getDeclaredField("name");
                f.setAccessible(true);
                String name = (String)f.get(lib);
                if (name.equals(path) || name.indexOf(path) != -1
                    || name.equals(flib.getCanonicalPath())) {
                    Method m = lib.getClass().getDeclaredMethod("finalize", new Class[0]);
                    m.setAccessible(true);
                    m.invoke(lib, new Object[0]);
                    nativeLibraryPath = null;
                    if (unpacked) {
                        if (flib.exists()) {
                            if (flib.delete()) {
                                unpacked = false;
                                return true;
                            }
                            return false;
                        }
                    }
                    return true;
                }
            }
        }
        catch(Exception e) {
        }
        return false;
    }

    private Native() { }
    
    private static native void initIDs();

    /** Set whether native memory accesses are protected from invalid
     * accesses.  This should only be set true when testing or debugging,
     * and should not be considered reliable or robust for applications
     * where JNA native calls are occurring on multiple threads.
     * Protected mode will be automatically set if the
     * system property <code>jna.protected</code> has a value of "true"
     * when the JNA library is first loaded.<p>
     * If not supported by the underlying platform, this setting will
     * have no effect.<p>
     * NOTE: On platforms which support signals (non-Windows), JNA uses
     * signals to trap errors.  This may interfere with the JVM's own use of
     * signals.  When protected mode is enabled, you should make use of the
     * jsig library, if available (see <a href="http://java.sun.com/j2se/1.4.2/docs/guide/vm/signal-chaining.html">Signal Chaining</a>).
     * In short, set the environment variable <code>LD_PRELOAD</code> to the
     * path to <code>libjsig.so</code> in your JRE lib directory
     * (usually ${java.home}/lib/${os.arch}/libjsig.so) before launching your
     * Java application.
     */
    public static synchronized native void setProtected(boolean enable);
    
    /** Returns whether protection is enabled.  Check the result of this method
     * after calling {@link #setProtected setProtected(true)} to determine
     * if this platform supports protecting memory accesses.
     */
    public static synchronized native boolean isProtected();

    /** Set whether the system last error result is captured after every
     * native invocation.  Defaults to <code>true</code> (<code>false</code>
     * for direct-mapped calls).<p>
     * @deprecated The preferred method of obtaining the last error result is
     * to declare your mapped method to throw {@link LastErrorException}
     * instead. 
     */
    public static synchronized native void setPreserveLastError(boolean enable);
    
    /** Indicates whether the system last error result is preserved
     * after every invocation.<p>
     * @deprecated The preferred method of obtaining the last error result is
     * to declare your mapped method to throw {@link LastErrorException}
     * instead. 
     */
    public static synchronized native boolean getPreserveLastError();
    
    /** Utility method to get the native window ID for a Java {@link Window}
     * as a <code>long</code> value.
     * This method is primarily for X11-based systems, which use an opaque
     * <code>XID</code> (usually <code>long int</code>) to identify windows.
     * @throws HeadlessException if the current VM is running headless 
     */
    public static long getWindowID(Window w) throws HeadlessException {
        return getComponentID(w);
    }

    /** Utility method to get the native window ID for a heavyweight Java 
     * {@link Component} as a <code>long</code> value.
     * This method is primarily for X11-based systems, which use an opaque
     * <code>XID</code> (usually <code>long int</code>) to identify windows. 
     * @throws HeadlessException if the current VM is running headless 
     */
    public static long getComponentID(Component c) throws HeadlessException {
        if (GraphicsEnvironment.isHeadless()) {
            throw new HeadlessException("No native windows when headless");
        }
        if (c.isLightweight()) {
            throw new IllegalArgumentException("Component must be heavyweight");
        }
        if (!c.isDisplayable()) 
            throw new IllegalStateException("Component must be displayable");
        // On X11 VMs prior to 1.5, the window must be visible
        if (Platform.isX11()
            && System.getProperty("java.version").startsWith("1.4")) {
            if (!c.isVisible()) {
                throw new IllegalStateException("Component must be visible");
            }
        }
        // By this point, we're certain that Toolkit.loadLibraries() has
        // been called, thus avoiding AWT/JAWT link errors
        // (see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6539705).
        return getWindowHandle0(c);
    }
    
    /** Utility method to get the native window pointer for a Java 
     * {@link Window} as a {@link Pointer} value.  This method is primarily for 
     * w32, which uses the <code>HANDLE</code> type (actually 
     * <code>void *</code>) to identify windows. 
     * @throws HeadlessException if the current VM is running headless 
     */
    public static Pointer getWindowPointer(Window w) throws HeadlessException {
        return getComponentPointer(w);
    }
    
    /** Utility method to get the native window pointer for a heavyweight Java 
     * {@link Component} as a {@link Pointer} value.  This method is primarily 
     * for w32, which uses the <code>HWND</code> type (actually 
     * <code>void *</code>) to identify windows. 
     * @throws HeadlessException if the current VM is running headless 
     */
    public static Pointer getComponentPointer(Component c) throws HeadlessException {
        return new Pointer(getComponentID(c));
    }
    
    private static native long getWindowHandle0(Component c);

    /** Convert a direct {@link Buffer} into a {@link Pointer}. 
     * @throws IllegalArgumentException if the buffer is not direct.
     */
    public static native Pointer getDirectBufferPointer(Buffer b);
    
    /** Obtain a Java String from the given native byte array.  If there is
     * no NUL terminator, the String will comprise the entire array.  If the
     * system property <code>jna.encoding</code> is set, its value will 
     * override the platform default encoding (if supported).
     */
    public static String toString(byte[] buf) {
        return toString(buf, System.getProperty("jna.encoding"));
    }

    /** Obtain a Java String from the given native byte array, using the given
     * encoding.  If there is no NUL terminator, the String will comprise the
     * entire array.  If the <code>encoding</code> parameter is null, 
     * the platform default encoding will be used.
     */
    public static String toString(byte[] buf, String encoding) {
        String s = null;
        if (encoding != null) {
            try {
                s = new String(buf, encoding);
            }
            catch(UnsupportedEncodingException e) { }
        }
        if (s == null) {
            s = new String(buf);
        }
        int term = s.indexOf(0);
        if (term != -1)
            s = s.substring(0, term);
        return s;
    }
    
    /** Obtain a Java String from the given native wchar_t array.  If there is
     * no NUL terminator, the String will comprise the entire array.
     */
    public static String toString(char[] buf) {
        String s = new String(buf); 
        int term = s.indexOf(0);
        if (term != -1)
            s = s.substring(0, term);
        return s;
    }
    
    /** Map a library interface to the current process, providing
     * the explicit interface class.
     * @param interfaceClass
     */
    public static Object loadLibrary(Class interfaceClass) {
        return loadLibrary(null, interfaceClass);
    }

    /** Map a library interface to the current process, providing
     * the explicit interface class.
     * @param interfaceClass
     * @param options Map of library options
     */
    public static Object loadLibrary(Class interfaceClass, Map options) {
        return loadLibrary(null, interfaceClass, options);
    }

    /** Map a library interface to the given shared library, providing
     * the explicit interface class.
     * If <code>name</code> is null, attempts to map onto the current process.
     * @param name
     * @param interfaceClass
     */
    public static Object loadLibrary(String name, Class interfaceClass) {
        return loadLibrary(name, interfaceClass, Collections.EMPTY_MAP);
    }

    /** Load a library interface from the given shared library, providing
     * the explicit interface class and a map of options for the library.
     * If no library options are detected the map is interpreted as a map
     * of Java method names to native function names.<p>
     * If <code>name</code> is null, attempts to map onto the current process.
     * @param name
     * @param interfaceClass
     * @param options Map of library options
     */
    public static Object loadLibrary(String name, 
                                     Class interfaceClass,
                                     Map options) {
        Library.Handler handler = 
            new Library.Handler(name, interfaceClass, options);
        ClassLoader loader = interfaceClass.getClassLoader();
        Library proxy = (Library)
            Proxy.newProxyInstance(loader, new Class[] {interfaceClass},
                                   handler);
        cacheOptions(interfaceClass, options, proxy);
        return proxy;
    }

    /** Attempts to force initialization of an instance of the library interface
     * by loading a public static field of the requisite type.
     * Returns whether an instance variable was instantiated. 
     * Expects that lock on libraries is already held
     */
    private static void loadLibraryInstance(Class cls) {
        if (cls != null && !libraries.containsKey(cls)) {
            try {
                Field[] fields = cls.getFields();
                for (int i=0;i < fields.length;i++) {
                    Field field = fields[i];
                    if (field.getType() == cls 
                        && Modifier.isStatic(field.getModifiers())) {
                        // Ensure the field gets initialized by reading it
                        libraries.put(cls, new WeakReference(field.get(null)));
                        break;
                    }
                }
            }
            catch (Exception e) {
                throw new IllegalArgumentException("Could not access instance of " 
                                                   + cls + " (" + e + ")");
            }
        }
    }
    
    /** Find the library interface corresponding to the given class.  Checks
     * all ancestor classes and interfaces for a declaring class which
     * implements {@link Library}.
     */
    static Class findEnclosingLibraryClass(Class cls) {
        if (cls == null) { 
            return null;
        }
        synchronized(libraries) {
            if (options.containsKey(cls)) {
                return cls;
            }
        }
        if (Library.class.isAssignableFrom(cls)) {
            return cls;
        }
        if (Callback.class.isAssignableFrom(cls)) {
            cls = CallbackReference.findCallbackClass(cls);
        }
        Class declaring = cls.getDeclaringClass();
        Class fromDeclaring = findEnclosingLibraryClass(declaring);
        if (fromDeclaring != null) {
            return fromDeclaring;
        }
        return findEnclosingLibraryClass(cls.getSuperclass());
    }
    

    /** Return the preferred native library configuration options for the given 
     * class.
     * @see Library 
     */
    public static Map getLibraryOptions(Class type) {
        synchronized(libraries) {
            Class interfaceClass = findEnclosingLibraryClass(type);
            if (interfaceClass != null) 
                loadLibraryInstance(interfaceClass);
            else
                interfaceClass = type;
            if (!options.containsKey(interfaceClass)) {
                try {
                    Field field = interfaceClass.getField("OPTIONS");
                    field.setAccessible(true);
                    options.put(interfaceClass, field.get(null));
                }
                catch (NoSuchFieldException e) {
                }
                catch (Exception e) {
                    throw new IllegalArgumentException("OPTIONS must be a public field of type java.util.Map (" 
                                                       + e + "): " + interfaceClass);
                }
            }
            return (Map)options.get(interfaceClass);
        }
    }

    /** Return the preferred {@link TypeMapper} for the given native interface.
     * See {@link com.sun.jna.Library#OPTION_TYPE_MAPPER}. 
     */
    public static TypeMapper getTypeMapper(Class cls) {
        synchronized(libraries) {
            Class interfaceClass = findEnclosingLibraryClass(cls);
            if (interfaceClass != null)
                loadLibraryInstance(interfaceClass);
            else
                interfaceClass = cls;

            if (!typeMappers.containsKey(interfaceClass)) {
                try {
                    Field field = interfaceClass.getField("TYPE_MAPPER");
                    field.setAccessible(true);
                    typeMappers.put(interfaceClass, field.get(null));
                }
                catch (NoSuchFieldException e) {
                    Map options = getLibraryOptions(cls);
                    if (options != null
                        && options.containsKey(Library.OPTION_TYPE_MAPPER)) {
                        typeMappers.put(interfaceClass, options.get(Library.OPTION_TYPE_MAPPER));
                    }
                }
                catch (Exception e) {
                    throw new IllegalArgumentException("TYPE_MAPPER must be a public field of type "
                                                       + TypeMapper.class.getName() + " (" 
                                                       + e + "): " + interfaceClass);
                }
            }
            return (TypeMapper)typeMappers.get(interfaceClass);
        }
    }

    /** Return the preferred structure alignment for the given native interface. 
     * See {@link com.sun.jna.Library#OPTION_STRUCTURE_ALIGNMENT}.
     */
    public static int getStructureAlignment(Class cls) {
        synchronized(libraries) {
            Class interfaceClass = findEnclosingLibraryClass(cls);
            if (interfaceClass != null) 
                loadLibraryInstance(interfaceClass);
            else
                interfaceClass = cls;
            if (!alignments.containsKey(interfaceClass)) {
                try {
                    Field field = interfaceClass.getField("STRUCTURE_ALIGNMENT");
                    field.setAccessible(true);
                    alignments.put(interfaceClass, field.get(null));
                }
                catch(NoSuchFieldException e) {
                    Map options = getLibraryOptions(interfaceClass);
                    if (options != null
                        && options.containsKey(Library.OPTION_STRUCTURE_ALIGNMENT)) {
                        alignments.put(interfaceClass, options.get(Library.OPTION_STRUCTURE_ALIGNMENT));
                    }
                }
                catch(Exception e) {
                    throw new IllegalArgumentException("STRUCTURE_ALIGNMENT must be a public field of type int ("
                                                       + e + "): " + interfaceClass);
                }
            }
            Integer value = (Integer)alignments.get(interfaceClass);
            return value != null ? value.intValue() : Structure.ALIGN_DEFAULT;
        }
    }
    
    /** Return a byte array corresponding to the given String.  If the
     * system property <code>jna.encoding</code> is set, its value will override
     * the default platform encoding (if supported).
     */
    static byte[] getBytes(String s) {
        try {
            return getBytes(s, System.getProperty("jna.encoding"));
        }
        catch (UnsupportedEncodingException e) {
            return s.getBytes();
        }
    }

    /** Return a byte array corresponding to the given String, using the given
        encoding.
    */
    static byte[] getBytes(String s, String encoding) throws UnsupportedEncodingException {
        if (encoding != null) {
            return s.getBytes(encoding);
        }
        return s.getBytes();
    }

    /** Obtain a NUL-terminated byte buffer equivalent to the given String,
        using <code>jna.encoding</code> or the default platform encoding if
        that property is not set.
    */
    public static byte[] toByteArray(String s) {
        byte[] bytes = getBytes(s);
        byte[] buf = new byte[bytes.length+1];
        System.arraycopy(bytes, 0, buf, 0, bytes.length);
        return buf;
    }

    /** Obtain a NUL-terminated byte buffer equivalent to the given String,
        using the given encoding.
     */
    public static byte[] toByteArray(String s, String encoding) throws UnsupportedEncodingException {
        byte[] bytes = getBytes(s, encoding);
        byte[] buf = new byte[bytes.length+1];
        System.arraycopy(bytes, 0, buf, 0, bytes.length);
        return buf;
    }

    /** Obtain a NUL-terminated wide character buffer equivalent to the given
        String.
    */ 
    public static char[] toCharArray(String s) {
        char[] chars = s.toCharArray();
        char[] buf = new char[chars.length+1];
        System.arraycopy(chars, 0, buf, 0, chars.length);
        return buf;
    }

    static String getNativeLibraryResourcePath(int osType, String arch, String name) {
        String osPrefix;
        arch = arch.toLowerCase();
        switch(osType) {
        case Platform.WINDOWS:
            if ("i386".equals(arch))
                arch = "x86";
            osPrefix = "win32-" + arch;
            break;
        case Platform.MAC:
            osPrefix = "darwin";
            break;
        case Platform.LINUX:
            if ("x86".equals(arch)) {
                arch = "i386";
            }
            else if ("x86_64".equals(arch)) {
                arch = "amd64";
            }
            osPrefix = "linux-" + arch;
            break;
        case Platform.SOLARIS:
            osPrefix = "sunos-" + arch;
            break;
        default:
            osPrefix = name.toLowerCase();
            if ("x86".equals(arch)) {
                arch = "i386";
            }
            if ("x86_64".equals(arch)) {
                arch = "amd64";
            }
            if ("powerpc".equals(arch)) {
                arch = "ppc";
            }
            int space = osPrefix.indexOf(" ");
            if (space != -1) {
                osPrefix = osPrefix.substring(0, space);
            }
            osPrefix += "-" + arch;
            break;
        }
        return "/com/sun/jna/" + osPrefix;
    }

    /**
     * Loads the JNA stub library.  It will first attempt to load this library
     * from the directories specified in jna.boot.library.path.  If that fails,
     * it will fallback to loading from the system library paths. Finally it will
     * attempt to extract the stub library from from the JNA jar file, and load it.
     * <p>
     * The jna.boot.library.path property is mainly to support jna.jar being
     * included in -Xbootclasspath, where java.library.path and LD_LIBRARY_PATH
     * are ignored.  It might also be useful in other situations.
     * </p>
     */
    private static void loadNativeLibrary() {
        String libName = "jnidispatch";
        String bootPath = System.getProperty("jna.boot.library.path");
        if (bootPath != null) {
            String[] dirs = bootPath.split(File.pathSeparator);
            for (int i = 0; i < dirs.length; ++i) {
                String path = new File(new File(dirs[i]), System.mapLibraryName(libName)).getAbsolutePath();
                try {
                    System.load(path);
                    nativeLibraryPath = path;
                    return;
                } catch (UnsatisfiedLinkError ex) {
                }
                if (Platform.isMac()) {
                    String orig, ext;
                    if (path.endsWith("dylib")) {
                        orig = "dylib";
                        ext = "jnilib";
                    } else {
                        orig = "jnilib";
                        ext = "dylib";
                    }
                    try {
                        path = path.substring(0, path.lastIndexOf(orig)) + ext;
                        System.load(path);
                        nativeLibraryPath = path;
                        return;
                    } catch (UnsatisfiedLinkError ex) {
                    }
                }
            }
        }
        try {
            System.loadLibrary(libName);
            nativeLibraryPath = libName;
        }
        catch(UnsatisfiedLinkError e) {
            loadNativeLibraryFromJar();
        }
    }

    /**
     * Attempts to load the native library resource from the filesystem,
     * extracting the JNA stub library from jna.jar if not already available.
     */
    private static void loadNativeLibraryFromJar() {
        String libname = System.mapLibraryName("jnidispatch");
        String arch = System.getProperty("os.arch");
        String name = System.getProperty("os.name");
        String resourceName = getNativeLibraryResourcePath(Platform.getOSType(), arch, name) + "/" + libname;
        URL url = Native.class.getResource(resourceName);
                
        // Add an ugly hack for OpenJDK (soylatte) - JNI libs use the usual
        // .dylib extension 
        if (url == null && Platform.isMac()
            && resourceName.endsWith(".dylib")) {
            resourceName = resourceName.substring(0, resourceName.lastIndexOf(".dylib")) + ".jnilib";
            url = Native.class.getResource(resourceName);
        }
        if (url == null) {
            throw new UnsatisfiedLinkError("jnidispatch (" + resourceName 
                                           + ") not found in resource path");
        }
    
        File lib = null;
        if (url.getProtocol().toLowerCase().equals("file")) {
            try {
                lib = new File(new URI(url.toString()));
            }
            catch(URISyntaxException e) {
                lib = new File(url.getPath());
            }
            if (!lib.exists()) {
                throw new Error("File URL " + url + " could not be properly decoded");
            }
        }
        else {
            InputStream is = Native.class.getResourceAsStream(resourceName);
            if (is == null) {
                throw new Error("Can't obtain jnidispatch InputStream");
            }
            
            FileOutputStream fos = null;
            try {
                // Suffix is required on windows, or library fails to load
                // Let Java pick the suffix, except on windows, to avoid
                // problems with Web Start.
                lib = File.createTempFile("jna", Platform.isWindows()?".dll":null);
                lib.deleteOnExit();
                ClassLoader cl = Native.class.getClassLoader();
                if (Platform.deleteNativeLibraryAfterVMExit()
                    && (cl == null
                        || cl.equals(ClassLoader.getSystemClassLoader()))) {
                    Runtime.getRuntime().addShutdownHook(new DeleteNativeLibrary(lib));
                }
                fos = new FileOutputStream(lib);
                int count;
                byte[] buf = new byte[1024];
                while ((count = is.read(buf, 0, buf.length)) > 0) {
                    fos.write(buf, 0, count);
                }
            }
            catch(IOException e) {
                throw new Error("Failed to create temporary file for jnidispatch library: " + e);
            }
            finally {
                try { is.close(); } catch(IOException e) { }
                if (fos != null) {
                    try { fos.close(); } catch(IOException e) { }
                }
            }
            unpacked = true;
        }
        System.load(lib.getAbsolutePath());
        nativeLibraryPath = lib.getAbsolutePath();
    }

    /**
     * Initialize field and method IDs for native methods of this class. 
     * Returns the size of a native pointer.
     **/
    private static native int sizeof(int type);

    private static native String getNativeVersion();
    private static native String getAPIChecksum();

    private static final ThreadLocal lastError = new ThreadLocal() {
        protected synchronized Object initialValue() {
            return new Integer(0);
        }
    };
    
    /** Retrieve the last error set by the OS.  This corresponds to
     * <code>GetLastError()</code> on Windows, and <code>errno</code> on
     * most other platforms.  The value is preserved per-thread, but whether 
     * the original value is per-thread depends on the underlying OS.  The 
     * result is undefined if {@link #getPreserveLastError} is 
     * <code>false</code>.<p>
     * The preferred method of obtaining the last error result is
     * to declare your mapped method to throw {@link LastErrorException}
     * instead. 
     */
    public static int getLastError() {
        return ((Integer)lastError.get()).intValue();
    }
    
    /** Set the OS last error code.  Whether the setting is per-thread
     * or global depends on the underlying OS.
     */
    public static native void setLastError(int code);

    /** Update the last error value (called from native code). */
    static void updateLastError(int e) {
        lastError.set(new Integer(e));
    }

    /**
     * Returns a synchronized (thread-safe) library backed by the specified
     * library.  This wrapping will prevent simultaneous invocations of any
     * functions mapped to a given {@link NativeLibrary}.  Note that the 
     * native library may still be sensitive to being called from different
     * threads.
     * <p>
     * @param  library the library to be "wrapped" in a synchronized library.
     * @return a synchronized view of the specified library.
     */
    public static Library synchronizedLibrary(final Library library) {
        Class cls = library.getClass();
        if (!Proxy.isProxyClass(cls)) {
            throw new IllegalArgumentException("Library must be a proxy class");
        }
        InvocationHandler ih = Proxy.getInvocationHandler(library);
        if (!(ih instanceof Library.Handler)) {
            throw new IllegalArgumentException("Unrecognized proxy handler: " + ih);
        }
        final Library.Handler handler = (Library.Handler)ih; 
        InvocationHandler newHandler = new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                synchronized(handler.getNativeLibrary()) {
                    return handler.invoke(library, method, args);
                }
            }
        };
        return (Library)Proxy.newProxyInstance(cls.getClassLoader(),
                                               cls.getInterfaces(),
                                               newHandler);
    }
    
    /** If running web start, determine the location of a given native 
     * library.  This value may be used to properly set 
     * <code>jna.library.path</code> so that JNA can load libraries identified
     * by the &lt;nativelib&gt; tag in the JNLP configuration file.  Returns 
     * <code>null</code> if the Web Start native library cache location can not 
     * be determined.  Note that the path returned may be different for any
     * given library name.
     * <p>
     * Use <code>System.getProperty("javawebstart.version")</code> to detect
     * whether your code is running under Web Start.
     * @throws UnsatisfiedLinkError if the library can't be found by the
     * Web Start class loader, which usually means it wasn't included as 
     * a <code>&lt;nativelib&gt;</code> resource in the JNLP file.
     * @return null if unable to query the web start loader.
     */
    public static String getWebStartLibraryPath(final String libName) {
        if (System.getProperty("javawebstart.version") == null)
            return null;
        try {

            final ClassLoader cl = Native.class.getClassLoader();
            Method m = (Method)AccessController.doPrivileged(new PrivilegedAction() {
                public Object run() {
                    try {
                        Method m = ClassLoader.class.getDeclaredMethod("findLibrary", new Class[] { String.class });
                        m.setAccessible(true);
                        return m;
                    }
                    catch(Exception e) {
                        return null;
                    }
                }
            });
            String libpath = (String)m.invoke(cl, new Object[] { libName });
            if (libpath != null) {
                return new File(libpath).getParent();
            }
            return null;
        }
        catch (Exception e) {
            return null;
        }
    }

    /** For internal use only. */
    public static class DeleteNativeLibrary extends Thread {
        private final File file;
        public DeleteNativeLibrary(File file) {
            this.file = file;
        }
        public void run() {
            // If we can't force an unload/delete, spawn a new process
            // to do so
            if (!deleteNativeLibrary()) {
                try {
                    Runtime.getRuntime().exec(new String[] {
                        System.getProperty("java.home") + "/bin/java",
                        "-cp", System.getProperty("java.class.path"),
                        getClass().getName(),
                        file.getAbsolutePath(),
                    });
                }
                catch(IOException e) { e.printStackTrace(); }
            }
        }
        public static void main(String[] args) {
            if (args.length == 1) {
                File file = new File(args[0]);
                if (file.exists()) { 
                    long start = System.currentTimeMillis();
                    while (!file.delete() && file.exists()) {
                        try { Thread.sleep(10); }
                        catch(InterruptedException e) { }
                        if (System.currentTimeMillis() - start > 5000) {
                            System.err.println("Could not remove temp file: "
                                               + file.getAbsolutePath());
                            break;
                        }
                    }
                }
            }
            System.exit(0);
        }
    }

    /** Returns the native size of the given class, in bytes. 
     * For use with arrays.
     */
    public static int getNativeSize(Class type, Object value) {
        if (type.isArray()) {
            int len = Array.getLength(value);
            if (len > 0) {
                Object o = Array.get(value, 0);
                return len * getNativeSize(type.getComponentType(), o);
            }
            // Don't process zero-length arrays
            throw new IllegalArgumentException("Arrays of length zero not allowed: " + type);
        }
        if (Structure.class.isAssignableFrom(type)
            && !Structure.ByReference.class.isAssignableFrom(type)) {
            if (value == null)
                value = Structure.newInstance(type);
            return ((Structure)value).size();
        }
        try {
            return getNativeSize(type);
        }
        catch(IllegalArgumentException e) {
            throw new IllegalArgumentException("The type \"" + type.getName()
                                               + "\" is not supported: "
                                               + e.getMessage());
        }
    }

    /** Returns the native size for a given Java class.  Structures are
     * assumed to be <code>struct</code> pointers unless they implement
     * {@link Structure.ByValue}.
     */
    public static int getNativeSize(Class cls) {
        if (NativeMapped.class.isAssignableFrom(cls)) {
            cls = NativeMappedConverter.getInstance(cls).nativeType();
        }
        // boolean defaults to 32 bit integer if not otherwise mapped
        if (cls == boolean.class || cls == Boolean.class) return 4;
        if (cls == byte.class || cls == Byte.class) return 1;
        if (cls == short.class || cls == Short.class) return 2; 
        if (cls == char.class || cls == Character.class) return WCHAR_SIZE;
        if (cls == int.class || cls == Integer.class) return 4;
        if (cls == long.class || cls == Long.class) return 8;
        if (cls == float.class || cls == Float.class) return 4;
        if (cls == double.class || cls == Double.class) return 8;
        if (Structure.class.isAssignableFrom(cls)) {
            if (Structure.ByValue.class.isAssignableFrom(cls)) {
                return Structure.newInstance(cls).size();
            }
            return POINTER_SIZE;
        }
        if (Pointer.class.isAssignableFrom(cls)
            || Buffer.class.isAssignableFrom(cls)
            || Callback.class.isAssignableFrom(cls)
            || String.class == cls
            || WString.class == cls) {
            return POINTER_SIZE;
        }
        throw new IllegalArgumentException("Native size for type \"" + cls.getName() 
        								   + "\" is unknown");
    }

    /** Indicate whether the given class is supported as a native argument
     * type.
     */  
    public static boolean isSupportedNativeType(Class cls) {
        if (Structure.class.isAssignableFrom(cls)) {
            return true;
        }
        try {
            return getNativeSize(cls) != 0;
        }
        catch(IllegalArgumentException e) {
            return false;
        }
    }

    /** Set the default handler invoked when a callback throws an uncaught
     * exception.  If the given handler is <code>null</code>, the default
     * handler will be reinstated.
     */
    public static void setCallbackExceptionHandler(UncaughtExceptionHandler eh) {
        callbackExceptionHandler = eh == null ? DEFAULT_HANDLER : eh;
    }

    /** Returns the current handler for callback uncaught exceptions. */
    public static UncaughtExceptionHandler getCallbackExceptionHandler() {
        return callbackExceptionHandler;
    }
    
    /** When called from a class static initializer, maps all native methods
     * found within that class to native libraries via the JNA raw calling
     * interface.
     * @param libName library name to which functions should be bound
     */
    public static void register(String libName) {
        register(getNativeClass(getCallingClass()),
                 NativeLibrary.getInstance(libName));
    }

    /** When called from a class static initializer, maps all native methods
     * found within that class to native libraries via the JNA raw calling
     * interface.
     * @param lib native library to which functions should be bound
     */
    public static void register(NativeLibrary lib) {
        register(getNativeClass(getCallingClass()), lib);
    }

    static Class getNativeClass(Class cls) {
        Method[] methods = cls.getDeclaredMethods();
        for (int i=0;i < methods.length;i++) {
            if ((methods[i].getModifiers() & Modifier.NATIVE) != 0) {
                return cls;
            }
        }
        int idx = cls.getName().lastIndexOf("$");
        if (idx != -1) {
            String name = cls.getName().substring(0, idx);
            try {
                return getNativeClass(Class.forName(name, true, cls.getClassLoader()));
            }
            catch(ClassNotFoundException e) {
            }
        }
        throw new IllegalArgumentException("Can't determine class with native methods from the current context (" + cls + ")");
    }

    static Class getCallingClass() {
        Class[] context = new SecurityManager() {
            public Class[] getClassContext() {
                return super.getClassContext();
            }
        }.getClassContext();
        if (context.length < 4) {
            throw new IllegalStateException("This method must be called from the static initializer of a class");
        }
        return context[3];
    }

    private static Map registeredClasses = new HashMap();
    private static Map registeredLibraries = new HashMap();
    private static Object unloader = new Object() {
        protected void finalize() {
            synchronized(registeredClasses) {
                for (Iterator i=registeredClasses.entrySet().iterator();i.hasNext();) {
                    Map.Entry e = (Map.Entry)i.next();
                    unregister((Class)e.getKey(), (long[])e.getValue());
                    i.remove();
                }
            }
        }
    };

    /** Remove all native mappings for the calling class.
        Should only be called if the class is no longer referenced and about
        to be garbage collected.
     */
    public static void unregister() {
        unregister(getNativeClass(getCallingClass()));
    }

    /** Remove all native mappings for the given class.
        Should only be called if the class is no longer referenced and about
        to be garbage collected.
     */
    public static void unregister(Class cls) {
        synchronized(registeredClasses) {
            if (registeredClasses.containsKey(cls)) {
                unregister(cls, (long[])registeredClasses.get(cls));
                registeredClasses.remove(cls);
                registeredLibraries.remove(cls);
            }
        }
    }

    /** Unregister the native methods for the given class. */
    private static native void unregister(Class cls, long[] handles);

    private static String getSignature(Class cls) {
        if (cls.isArray()) {
            return "[" + getSignature(cls.getComponentType());
        }
        if (cls.isPrimitive()) {
            if (cls == void.class) return "V";
            if (cls == boolean.class) return "Z";
            if (cls == byte.class) return "B";
            if (cls == short.class) return "S";
            if (cls == char.class) return "C";
            if (cls == int.class) return "I";
            if (cls == long.class) return "J";
            if (cls == float.class) return "F";
            if (cls == double.class) return "D";
        }
        return "L" + replace(".", "/", cls.getName()) + ";";
    }

    // No String.replace available in 1.4 
    static String replace(String s1, String s2, String str) {
        StringBuffer buf = new StringBuffer();
        while (true) {
            int idx = str.indexOf(s1);
            if (idx == -1) {
                buf.append(str);
                break;
            }
            else {
                buf.append(str.substring(0, idx));
                buf.append(s2);
                str = str.substring(idx + s1.length());
            }
        }
        return buf.toString();
    }

    private static final int CVT_UNSUPPORTED = -1;
    private static final int CVT_DEFAULT = 0;
    private static final int CVT_POINTER = 1;
    private static final int CVT_STRING = 2;
    private static final int CVT_STRUCTURE = 3;
    private static final int CVT_STRUCTURE_BYVAL = 4;
    private static final int CVT_BUFFER = 5;
    private static final int CVT_ARRAY_BYTE = 6;
    private static final int CVT_ARRAY_SHORT = 7;
    private static final int CVT_ARRAY_CHAR = 8;
    private static final int CVT_ARRAY_INT = 9;
    private static final int CVT_ARRAY_LONG = 10;
    private static final int CVT_ARRAY_FLOAT = 11;
    private static final int CVT_ARRAY_DOUBLE = 12;
    private static final int CVT_ARRAY_BOOLEAN = 13;
    private static final int CVT_BOOLEAN = 14;
    private static final int CVT_CALLBACK = 15;
    private static final int CVT_FLOAT = 16;
    private static final int CVT_NATIVE_MAPPED = 17;
    private static final int CVT_WSTRING = 18;
    private static final int CVT_INTEGER_TYPE = 19;
    private static final int CVT_POINTER_TYPE = 20;
    private static final int CVT_TYPE_MAPPER = 21;

    private static int getConversion(Class type, TypeMapper mapper) {
        if (type == Boolean.class) type = boolean.class;
        else if (type == Byte.class) type = byte.class;
        else if (type == Short.class) type = short.class;
        else if (type == Character.class) type = char.class;
        else if (type == Integer.class) type = int.class;
        else if (type == Long.class) type = long.class;
        else if (type == Float.class) type = float.class;
        else if (type == Double.class) type = double.class;
        else if (type == Void.class) type = void.class;
            
        if (mapper != null
            && (mapper.getFromNativeConverter(type) != null
                || mapper.getToNativeConverter(type) != null)) {
            return CVT_TYPE_MAPPER;
        }

        if (Pointer.class.isAssignableFrom(type)) {
            return CVT_POINTER;
        }
        if (String.class == type) {
            return CVT_STRING;
        }
        if (WString.class.isAssignableFrom(type)) {
            return CVT_WSTRING;
        }
        if (Buffer.class.isAssignableFrom(type)) {
            return CVT_BUFFER;
        }
        if (Structure.class.isAssignableFrom(type)) {
            if (Structure.ByValue.class.isAssignableFrom(type)) {
                return CVT_STRUCTURE_BYVAL;
            }
            return CVT_STRUCTURE;
        }
        if (type.isArray()) {
            switch(type.getName().charAt(1)) {
            case 'Z': return CVT_ARRAY_BOOLEAN;
            case 'B': return CVT_ARRAY_BYTE;
            case 'S': return CVT_ARRAY_SHORT;
            case 'C': return CVT_ARRAY_CHAR;
            case 'I': return CVT_ARRAY_INT;
            case 'J': return CVT_ARRAY_LONG;
            case 'F': return CVT_ARRAY_FLOAT;
            case 'D': return CVT_ARRAY_DOUBLE;
            default: break;
            }
        }
        if (type.isPrimitive()) {
            return type == boolean.class ? CVT_BOOLEAN : CVT_DEFAULT;
        }
        if (Callback.class.isAssignableFrom(type)) {
            return CVT_CALLBACK;
        }
        if (IntegerType.class.isAssignableFrom(type)) {
            return CVT_INTEGER_TYPE;
        }
        if (PointerType.class.isAssignableFrom(type)) {
            return CVT_POINTER_TYPE;
        }
        if (NativeMapped.class.isAssignableFrom(type)) {
            return CVT_NATIVE_MAPPED;
        }
        return CVT_UNSUPPORTED;
    }

    /** When called from a class static initializer, maps all native methods
     * found within that class to native libraries via the JNA raw calling
     * interface.
     * @param lib library to which functions should be bound
     */
    // TODO: derive options from annotations (per-class or per-method)
    // options: read parameter type mapping (long/native long),
    // method name, library name, call conv
    public static void register(Class cls, NativeLibrary lib) {
        Method[] methods = cls.getDeclaredMethods();
        List mlist = new ArrayList();
        TypeMapper mapper = (TypeMapper)
            lib.getOptions().get(Library.OPTION_TYPE_MAPPER);

        for (int i=0;i < methods.length;i++) {
            if ((methods[i].getModifiers() & Modifier.NATIVE) != 0) {
                mlist.add(methods[i]);
            }
        }
        long[] handles = new long[mlist.size()];
        for (int i=0;i < handles.length;i++) {
            Method method = (Method)mlist.get(i);
            String sig = "(";
            Class rclass = method.getReturnType();
            long rtype, closure_rtype;
            Class[] ptypes = method.getParameterTypes();
            long[] atypes = new long[ptypes.length];
            long[] closure_atypes = new long[ptypes.length];
            int[] cvt = new int[ptypes.length];
            ToNativeConverter[] toNative = new ToNativeConverter[ptypes.length];
            FromNativeConverter fromNative = null;
            int rcvt = getConversion(rclass, mapper);
            boolean throwLastError = false;
            switch (rcvt) {
            case CVT_UNSUPPORTED:
                throw new IllegalArgumentException(rclass + " is not a supported return type (in method " + method.getName() + " in " + cls + ")");
            case CVT_TYPE_MAPPER:
                fromNative = mapper.getFromNativeConverter(rclass);
                closure_rtype = FFIType.get(rclass).peer;
                rtype = FFIType.get(fromNative.nativeType()).peer;
                break;
            case CVT_NATIVE_MAPPED:
            case CVT_INTEGER_TYPE:
            case CVT_POINTER_TYPE:
                closure_rtype = FFIType.get(Pointer.class).peer;
                rtype = FFIType.get(NativeMappedConverter.getInstance(rclass).nativeType()).peer;
                break;
            case CVT_STRUCTURE:
                closure_rtype = rtype = FFIType.get(Pointer.class).peer;
                break;
            case CVT_STRUCTURE_BYVAL:
                closure_rtype = FFIType.get(Pointer.class).peer;
                rtype = FFIType.get(rclass).peer;
                break;
            default:
                closure_rtype = rtype = FFIType.get(rclass).peer;
                break;
            }
            for (int t=0;t < ptypes.length;t++) {
                Class type = ptypes[t];
                sig += getSignature(type);
                cvt[t] = getConversion(type, mapper);
                if (cvt[t] == CVT_UNSUPPORTED) {
                    throw new IllegalArgumentException(type + " is not a supported argument type (in method " + method.getName() + " in " + cls + ")");
                }
                if (cvt[t] == CVT_NATIVE_MAPPED
                    || cvt[t] == CVT_INTEGER_TYPE) {
                    type = NativeMappedConverter.getInstance(type).nativeType();
                }
                else if (cvt[t] == CVT_TYPE_MAPPER) {
                    toNative[t] = mapper.getToNativeConverter(type);
                }
                // Determine the type that will be passed to the native
                // function, as well as the type to be passed
                // from Java initially
                switch(cvt[t]) {
                case CVT_STRUCTURE_BYVAL:
                case CVT_INTEGER_TYPE:
                case CVT_POINTER_TYPE:
                case CVT_NATIVE_MAPPED:
                    atypes[t] = FFIType.get(type).peer;
                    closure_atypes[t] = FFIType.get(Pointer.class).peer;
                    break;
                case CVT_TYPE_MAPPER:
                    if (type.isPrimitive())
                        closure_atypes[t] = FFIType.get(type).peer;
                    else
                        closure_atypes[t] = FFIType.get(Pointer.class).peer;
                    atypes[t] = FFIType.get(toNative[t].nativeType()).peer;
                    break;
                case CVT_DEFAULT:
                    closure_atypes[t] = atypes[t] = FFIType.get(type).peer;
                    break;
                default:
                    closure_atypes[t] = atypes[t] = FFIType.get(Pointer.class).peer;
                    break;
                }
            }
            sig += ")";
            sig += getSignature(rclass);
            
            Class[] etypes = method.getExceptionTypes();
            for (int e=0;e < etypes.length;e++) {
                if (LastErrorException.class.isAssignableFrom(etypes[e])) {
                    throwLastError = true;
                    break;
                }
            }

            String name = method.getName();
            FunctionMapper fmapper = (FunctionMapper)lib.getOptions().get(Library.OPTION_FUNCTION_MAPPER);
            if (fmapper != null) {
                name = fmapper.getFunctionName(lib, method);
            }
            Function f = lib.getFunction(name, method);
            try {
                handles[i] = registerMethod(cls, method.getName(),
                                            sig, cvt,
                                            closure_atypes, atypes, rcvt,
                                            closure_rtype, rtype, 
                                            rclass,
                                            f.peer, f.getCallingConvention(),
                                            throwLastError,
                                            toNative, fromNative);
            }
            catch(NoSuchMethodError e) {
                throw new UnsatisfiedLinkError("No method " + method.getName() + " with signature " + sig + " in " + cls);
            }
        }
        synchronized(registeredClasses) {
            registeredClasses.put(cls, handles);
            registeredLibraries.put(cls, lib);
        }
        cacheOptions(cls, lib.getOptions(), null);
    }

    /** Take note of options used for a given library mapping, to facilitate
        looking them up later.
    */
    private static void cacheOptions(Class cls, Map libOptions, Object proxy) {
        synchronized(libraries) {
            if (!libOptions.isEmpty()) 
                options.put(cls, libOptions);
            if (libOptions.containsKey(Library.OPTION_TYPE_MAPPER))
                typeMappers.put(cls, libOptions.get(Library.OPTION_TYPE_MAPPER));
            if (libOptions.containsKey(Library.OPTION_STRUCTURE_ALIGNMENT))
                alignments.put(cls, libOptions.get(Library.OPTION_STRUCTURE_ALIGNMENT));
            if (proxy != null) {
                libraries.put(cls, new WeakReference(proxy));
            }

            // If it's a direct mapping, AND implements a Library interface,
            // cache the library interface as well, so that any nested
            // classes get the appropriate associated options
            if (!cls.isInterface()
                && Library.class.isAssignableFrom(cls)) {
                Class ifaces[] = cls.getInterfaces();
                for (int i=0;i < ifaces.length;i++) {
                    if (Library.class.isAssignableFrom(ifaces[i])) {
                        cacheOptions(ifaces[i], libOptions, proxy);
                        break;
                    }
                }
            }
        }
    }

    private static native long registerMethod(Class cls,
                                              String name,
                                              String signature,
                                              int[] conversions,
                                              long[] closure_arg_types,
                                              long[] arg_types,
                                              int rconversion,
                                              long closure_rtype,
                                              long rtype,
                                              Class rclass,
                                              long fptr,
                                              int callingConvention,
                                              boolean throwLastError,
                                              ToNativeConverter[] toNative,
                                              FromNativeConverter fromNative);
    

    // Called from native code
    private static NativeMapped fromNative(Class cls, Object value) {
        // NOTE: technically should be either CallbackParameterContext or
        // FunctionResultContext 
        return (NativeMapped)NativeMappedConverter.getInstance(cls).fromNative(value, new FromNativeContext(cls));
    }
    // Called from native code
    private static Class nativeType(Class cls) {
        return NativeMappedConverter.getInstance(cls).nativeType();
    }
    // Called from native code
    private static Object toNative(ToNativeConverter cvt, Object o) {
        // NOTE: technically should be either CallbackResultContext or
        // FunctionParameterContext
        return cvt.toNative(o, new ToNativeContext());
    }
    // Called from native code
    private static Object fromNative(FromNativeConverter cvt, Object o, Class cls) {
        // NOTE: technically should be FunctionResultContext 
        return cvt.fromNative(o, new FromNativeContext(cls));
    }

    public static native long ffi_prep_cif(int abi, int nargs, long ffi_return_type, long ffi_types);
    public static native void ffi_call(long cif, long fptr, long resp, long args);
    public static native long ffi_prep_closure(long cif, ffi_callback cb);
    public static native void ffi_free_closure(long closure);
    
    /** Returns the size (calculated by libffi) of the given type. */
    static native int initialize_ffi_type(long type_info);

    public interface ffi_callback {
        void invoke(long cif, long resp, long argp);
    }

    /** Prints JNA library details to the console. */
    public static void main(String[] args) {
        final String DEFAULT_TITLE = "Java Native Access (JNA)";
        final String DEFAULT_VERSION = VERSION;
        final String DEFAULT_BUILD = VERSION + " (package information missing)";
        Package pkg = Native.class.getPackage();
        String title = pkg.getSpecificationTitle();
        if (title == null) title = DEFAULT_TITLE;
        String version = pkg.getSpecificationVersion();
        if (version == null) version = DEFAULT_VERSION;
        title += " API Version " + version;
        System.out.println(title);
        version = pkg.getImplementationVersion();
        if (version == null) version = DEFAULT_BUILD;
        System.out.println("Version: " + version);
        System.out.println(" Native: " + getNativeVersion() + " ("
                           + getAPIChecksum() + ")");
        System.exit(0);
    }
} public class NativeLong extends IntegerType {
    /** Size of a native long, in bytes. */
    public static final int SIZE = Native.LONG_SIZE;

    /** Create a zero-valued NativeLong. */
    public NativeLong() {
        this(0);
    }
    
    /** Create a NativeLong with the given value. */
    public NativeLong(long value) {
        super(SIZE, value);
    }
} public class Pointer {

    /** Size of a native pointer, in bytes. */
    public static final int SIZE;
    
    static {
        // Force load of native library
        if ((SIZE = Native.POINTER_SIZE) == 0) {
            throw new Error("Native library not initialized");
        }
    }
    
    /** Convenience constant, same as <code>null</code>. */
    public static final Pointer NULL = null;
    
    /** Convenience constant, equivalent to <code>(void*)CONSTANT</code>. */
    public static final Pointer createConstant(long peer) {
        return new Opaque(peer);
    }
    
    /** Convenience constant, equivalent to <code>(void*)CONSTANT</code>.
        This version will avoid setting any of the high bits on 64-bit
        systems.
     */
    public static final Pointer createConstant(int peer) {
        return new Opaque((long)peer & 0xFFFFFFFF);
    }
    
    /** Pointer value of the real native pointer. Use long to be 64-bit safe. 
     */
    protected long peer;

    /** Derived class must assign peer pointer value. */
    Pointer() { }
    
    /** Create from native pointer.  Don't use this unless you know what
     * you're doing.
     */
    public Pointer(long peer) {
        this.peer = peer;
    }

    public Pointer share(long offset) {
        return share(offset, 0);
    }
    
    /** Provide a view of this pointer with a different peer base. */
    public Pointer share(long offset, long sz) {
        if (offset == 0) return this;
        return new Pointer(peer + offset);
    }

    /** Zero memory for the given number of bytes. */
    public void clear(long size) {
        setMemory(0, size, (byte)0);
    }

    /**
     * Compares this <code>Pointer</code> to the specified object.
     *
     * @param	o 
     *			A <code>Pointer</code> instance
     * @return	True if the other object is a <code>Pointer</code>, 
     *          and the C pointers being pointed to by these objects are also
     *			equal. Returns false otherwise.
     */
    public boolean equals(Object o) {
        if (o == this) return true;
        if (o == null) return false;
        return o instanceof Pointer && ((Pointer)o).peer == peer;
    }

    /**
     * Returns a hashcode for the native pointer represented by this
     * <code>Pointer</code> object
     *
     * @return	A hash code value for the represented native pointer
     */
    public int hashCode() {
        return (int)((peer >>> 32) + (peer & 0xFFFFFFFF));
    }


    //////////////////////////////////////////////////////////////////////////
    // Raw read methods
    //////////////////////////////////////////////////////////////////////////

    /** Returns the offset of the given value in memory from the given offset,
     * or -1 if the value is not found.
     */
    public long indexOf(long offset, byte value) {
        return _indexOf(peer + offset, value);
    }
    
    private static native long _indexOf(long addr, byte value);

    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>byte</code> array into which data is copied
     * @param index  array index from which to start copying
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, byte[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, byte[] buf, int index, int length);


    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>short</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, short[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, short[] buf, int index, int length);


    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>char</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, char[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }
    
    private static native void _read(long addr, char[] buf, int index, int length);


    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>int</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, int[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, int[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>long</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, long[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, long[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>float</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, float[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, float[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    <code>double</code> array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, double[] buf, int index, int length) {
        _read(peer + offset, buf, index, length);
    }

    private static native void _read(long addr, double[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>from</em> memory pointed to by
     * native pointer, into the specified array.
     *
     * @param offset byte offset from pointer from which data is copied
     * @param buf    {@link Pointer} array into which data is copied
     * @param index  array index to which data is copied
     * @param length number of elements from native pointer that must be copied
     */
    public void read(long offset, Pointer[] buf, int index, int length) {
        for (int i=0;i < length;i++) {
            Pointer p = getPointer(offset + i*Pointer.SIZE);
            Pointer oldp = buf[i+index];
            // Avoid replacing the original pointer if it hasn't changed
            if (oldp == null || p == null || p.peer != oldp.peer) {
                buf[i+index] = p;
            }
        }
    }


    //////////////////////////////////////////////////////////////////////////
    // Raw write methods
    //////////////////////////////////////////////////////////////////////////

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>byte</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, byte[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, byte[] buf, int index, int length);


    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>short</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, short[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, short[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>char</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, char[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, char[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>int</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, int[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, int[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by 
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>long</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, long[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, long[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>float</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, float[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, float[] buf, int index, int length);

    /**
     * Indirect the native pointer, copying <em>into</em> memory pointed to by
     * native pointer, from the specified array.
     *
     * @param offset byte offset from pointer into which data is copied
     * @param buf    <code>double</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
     */
    public void write(long offset, double[] buf, int index, int length) {
        _write(peer + offset, buf, index, length);
    }

    private static native void _write(long addr, double[] buf, int index, int length);

    /** Write the given array of Pointer to native memory.
     * @param bOff   byte offset from pointer into which data is copied
     * @param buf    <code>Pointer</code> array from which to copy
     * @param index  array index from which to start copying
     * @param length number of elements from <code>buf</code> that must be
     *               copied
    */
    public void write(long bOff, Pointer[] buf, int index, int length) {
        for (int i=0;i < length;i++) {
            setPointer(bOff + i * Pointer.SIZE, buf[index + i]);
        }
    }

    //////////////////////////////////////////////////////////////////////////
    // Java type read methods
    //////////////////////////////////////////////////////////////////////////

    Object getValue(long offset, Class type, Object currentValue) {

        Object result = null;
        if (Structure.class.isAssignableFrom(type)) {
            Structure s = (Structure)currentValue;
            if (Structure.ByReference.class.isAssignableFrom(type)) {
                s = Structure.updateStructureByReference(type, s, getPointer(offset));
            }
            else {
                s.useMemory(this, (int)offset);
                s.read();
            }
            result = s;
        }
        else if (type == boolean.class || type == Boolean.class) {
            result = Function.valueOf(getInt(offset) != 0);
        }
        else if (type == byte.class || type == Byte.class) {
            result = new Byte(getByte(offset));
        }
        else if (type == short.class || type == Short.class) {
            result = new Short(getShort(offset));
        }
        else if (type == char.class || type == Character.class) {
            result = new Character(getChar(offset));
        }
        else if (type == int.class || type == Integer.class) {
            result = new Integer(getInt(offset));
        }
        else if (type == long.class || type == Long.class) {
            result = new Long(getLong(offset));
        }
        else if (type == float.class || type == Float.class) {
            result=new Float(getFloat(offset));
        }
        else if (type == double.class || type == Double.class) {
            result = new Double(getDouble(offset));
        }
        else if (Pointer.class.isAssignableFrom(type)) {
            Pointer p = getPointer(offset);
            if (p != null) {
                Pointer oldp = currentValue instanceof Pointer
                    ? (Pointer)currentValue : null;
                if (oldp == null || p.peer != oldp.peer)
                    result = p;
                else
                    result = oldp;
            }
        }
        else if (type == String.class) {
            Pointer p = getPointer(offset);
            result = p != null ? p.getString(0) : null;
        }
        else if (type == WString.class) {
            Pointer p = getPointer(offset);
            result = p != null ? new WString(p.getString(0, true)) : null;
        }
        else if (Callback.class.isAssignableFrom(type)) {
            // Overwrite the Java memory if the native pointer is a different
            // function pointer.
            Pointer fp = getPointer(offset);
            if (fp == null) {
                result = null;
            }
            else {
                Callback cb = (Callback)currentValue;
                Pointer oldfp = CallbackReference.getFunctionPointer(cb);
                if (!fp.equals(oldfp)) {
                    cb = CallbackReference.getCallback(type, fp);
                }
                result = cb;
            }
        }
        else if (Buffer.class.isAssignableFrom(type)) {
            Pointer bp = getPointer(offset);
            if (bp == null) {
                result = null;
            }
            else {
                Pointer oldbp = currentValue == null ? null
                    : Native.getDirectBufferPointer((Buffer)currentValue);
                if (oldbp == null || !oldbp.equals(bp)) {
                    throw new IllegalStateException("Can't autogenerate a direct buffer on memory read");
                }
                result = currentValue;
            }
        }
        else if (NativeMapped.class.isAssignableFrom(type)) {
            NativeMapped nm = (NativeMapped)currentValue;
            if (nm != null) {
                Object value = getValue(offset, nm.nativeType(), null);
                result = nm.fromNative(value, new FromNativeContext(type));
            }
            else {
                NativeMappedConverter tc = NativeMappedConverter.getInstance(type);
                Object value = getValue(offset, tc.nativeType(), null);
                result = tc.fromNative(value, new FromNativeContext(type));
            }
        }
        else if (type.isArray()) {
            result = currentValue;
            if (result == null) {
                throw new IllegalStateException("Need an initialized array");
            }
            getArrayValue(offset, result, type.getComponentType());
        }
        else {
            throw new IllegalArgumentException("Reading \""
                                               + type + "\" from memory is not supported");
        }
        return result;
    }

    private void getArrayValue(long offset, Object o, Class cls) {
        int length = 0;
        length = Array.getLength(o);
        Object result = o;
        
        if (cls == byte.class) {
            read(offset, (byte[])result, 0, length);
        }
        else if (cls == short.class) {
            read(offset, (short[])result, 0, length);
        }
        else if (cls == char.class) {
            read(offset, (char[])result, 0, length);
        }
        else if (cls == int.class) {
            read(offset, (int[])result, 0, length);
        }
        else if (cls == long.class) {
            read(offset, (long[])result, 0, length);
        }
        else if (cls == float.class) {
            read(offset, (float[])result, 0, length);
        }
        else if (cls == double.class) {
            read(offset, (double[])result, 0, length);
        }
        else if (Pointer.class.isAssignableFrom(cls)) {
            read(offset, (Pointer[])result, 0, length);
        }
        else if (Structure.class.isAssignableFrom(cls)) {
            Structure[] sarray = (Structure[])result;
            if (Structure.ByReference.class.isAssignableFrom(cls)) {
                Pointer[] parray = getPointerArray(offset, sarray.length);
                for (int i=0;i < sarray.length;i++) {
                    sarray[i] = Structure.updateStructureByReference(cls, sarray[i], parray[i]);
                }
            }
            else {
                for (int i=0;i < sarray.length;i++) {
                    if (sarray[i] == null) {
                        sarray[i] = Structure.newInstance(cls);
                    }
                    sarray[i].useMemory(this, (int)(offset + i * sarray[i].size()));
                    sarray[i].read();
                }
            }
        }
        else if (NativeMapped.class.isAssignableFrom(cls)) {
            NativeMapped[] array = (NativeMapped[])result;
            NativeMappedConverter tc = NativeMappedConverter.getInstance(cls);
            int size = Native.getNativeSize(result.getClass(), result) / array.length;
            for (int i=0;i < array.length;i++) {
                Object value = getValue(offset + size*i, tc.nativeType(), array[i]);
                array[i] = (NativeMapped)tc.fromNative(value, new FromNativeContext(cls));
            }
        }
        else {
            throw new IllegalArgumentException("Reading array of "
                                               + cls
                                               + " from memory not supported");
        }
    }

    /**
     * Indirect the native pointer as a pointer to <code>byte</code>.  This is
     * equivalent to the expression
     * <code>*((jbyte *)((char *)Pointer + offset))</code>.
     *
     * @param offset offset from pointer to perform the indirection
     * @return the <code>byte</code> value being pointed to
     */
    public byte getByte(long offset) {
        return _getByte(peer + offset);
    }

    private static native byte _getByte(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>wchar_t</code>.  This
     * is equivalent to the expression
     * <code>*((wchar_t*)((char *)Pointer + offset))</code>.
     *
     * @param offset offset from pointer to perform the indirection
     * @return the <code>wchar_t</code> value being pointed to
     */
    public char getChar(long offset) {
        return _getChar(peer + offset);
    }

    private static native char _getChar(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>short</code>.  This is
     * equivalent to the expression
     * <code>*((jshort *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>short</code> value being pointed to
     */
    public short getShort(long offset) {
        return _getShort(peer + offset);
    }

    private static native short _getShort(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>int</code>.  This is
     * equivalent to the expression
     * <code>*((jint *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>int</code> value being pointed to
     */
    public int getInt(long offset) {
        return _getInt(peer + offset);
    }

    private static native int _getInt(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>long</code>.  This is
     * equivalent to the expression
     * <code>*((jlong *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>long</code> value being pointed to
     */
    public long getLong(long offset) {
        return _getLong(peer + offset);
    }

    private static native long _getLong(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>long</code>.  This is
     * equivalent to the expression
     * <code>*((long *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>long</code> value being pointed to
     */
    public NativeLong getNativeLong(long offset) {
        return new NativeLong(NativeLong.SIZE == 8 ? getLong(offset) : getInt(offset));
    }

    /**
     * Indirect the native pointer as a pointer to <code>float</code>.  This is
     * equivalent to the expression
     * <code>*((jfloat *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>float</code> value being pointed to
     */
    public float getFloat(long offset) {
        return _getFloat(peer + offset);
    }

    private native float _getFloat(long addr);

    /**
     * Indirect the native pointer as a pointer to <code>double</code>.  This
     * is equivalent to the expression
     * <code>*((jdouble *)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return the <code>double</code> value being pointed to
     */
    public double getDouble(long offset) {
        return _getDouble(peer + offset);
    }

    private static native double _getDouble(long addr);

    /**
     * Indirect the native pointer as a pointer to pointer.  This is equivalent
     * to the expression
     * <code>*((void **)((char *)Pointer + offset))</code>.
     *
     * @param offset byte offset from pointer to perform the indirection
     * @return a {@link Pointer} equivalent of the pointer value
     * being pointed to, or <code>null</code> if the pointer value is
     * <code>NULL</code>;
     */
    public Pointer getPointer(long offset) {
        return _getPointer(peer + offset);
    }

    private static native Pointer _getPointer(long addr);

    /**
     * Get a ByteBuffer mapped to the memory pointed to by the pointer,
     * ensuring the buffer uses native byte order.
     *
     * @param offset byte offset from pointer to start the buffer
     * @param length Length of ByteBuffer
     * @return a direct ByteBuffer that accesses the memory being pointed to,
     */
    public ByteBuffer getByteBuffer(long offset, long length) {
        return _getDirectByteBuffer(peer + offset, length).order(ByteOrder.nativeOrder());
    }

    /**
     * Get a direct ByteBuffer mapped to the memory pointed to by the pointer.
     *
     * @param addr byte offset from pointer to start the buffer
     * @param length Length of ByteBuffer
     * @return a direct ByteBuffer that accesses the memory being pointed to, 
     */
    private native ByteBuffer _getDirectByteBuffer(long addr, long length);

    /**
     * Copy native memory to a Java String.  If <code>wide</code> is true,
     * access the memory as an array of <code>wchar_t</code>, otherwise 
     * as an array of <code>char</code>, using the default platform encoding.
     *
     * @param offset byte offset from pointer to obtain the native string
v     * @param wide whether to convert from a wide or standard C string
     * @return the <code>String</code> value being pointed to 
     */
    public String getString(long offset, boolean wide) {
        return _getString(peer + offset, wide);
    }
    
    private static native String _getString(long addr, boolean wide);

    /**
     * Copy native memory to a Java String.  If the system property 
     * <code>jna.encoding</code> is set, uses it as the native charset
     * when decoding the value, otherwise falls back to the default platform
     * encoding.
     *
     * @param offset byte offset from pointer to obtain the native string
     * @return the <code>String</code> value being pointed to 
     */
    public String getString(long offset) {
        String encoding = System.getProperty("jna.encoding");
        if (encoding != null) {
            long len = indexOf(offset, (byte)0);
            if (len != -1) {
                if (len > Integer.MAX_VALUE) {
                    throw new OutOfMemoryError("String exceeds maximum length: " + len);
                }
                byte[] data = getByteArray(offset, (int)len);
                try {
                    return new String(data, encoding);
                }
                catch(UnsupportedEncodingException e) { 
                }
            }
        }
        return getString(offset, false);
    }

    public byte[] getByteArray(long offset, int arraySize) {
        byte[] buf = new byte[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }
    
    public char[] getCharArray(long offset, int arraySize) {
        char[] buf = new char[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    public short[] getShortArray(long offset, int arraySize) {
        short[] buf = new short[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    public int[] getIntArray(long offset, int arraySize) {
        int[] buf = new int[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    public long[] getLongArray(long offset, int arraySize) {
        long[] buf = new long[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    public float[] getFloatArray(long offset, int arraySize) {
        float[] buf = new float[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    public double[] getDoubleArray(long offset, int arraySize) {
        double[] buf = new double[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    /** Returns an array of {@link Pointer}.  The array length is
     * determined by a NULL-valued terminating element.
     */
    public Pointer[] getPointerArray(long base) {
        List array = new ArrayList();
        int offset = 0;
        Pointer p = getPointer(base);
        while (p != null) {
            array.add(p);
            offset += Pointer.SIZE;
            p = getPointer(base + offset);
        }
        return (Pointer[])array.toArray(new Pointer[array.size()]);
    }

    /** Returns an array of {@link Pointer} of the requested size. */
    public Pointer[] getPointerArray(long offset, int arraySize) {
        Pointer[] buf = new Pointer[arraySize];
        read(offset, buf, 0, arraySize);
        return buf;
    }

    /** Returns an array of <code>String</code> based on a native array
     * of <code>char *</code>.  The array length is determined by a
     * NULL-valued terminating element. 
     */
    public String[] getStringArray(long base) {
        return getStringArray(base, -1, false);
    }

    /** Returns an array of <code>String</code> based on a native array
     * of <code>char *</code>, using the given array length. 
     */
    public String[] getStringArray(long base, int length) {
        return getStringArray(base, length, false);
    }

    /** Returns an array of <code>String</code> based on a native array
     * of <code>char*</code> or <code>wchar_t*</code> based on the
     * <code>wide</code> parameter.  The array length is determined by a
     * NULL-valued terminating element. 
     */
    public String[] getStringArray(long base, boolean wide) {
        return getStringArray(base, -1, wide);
    }

    /** Returns an array of <code>String</code> based on a native array
     * of <code>char*</code> or <code>wchar_t*</code> based on the
     * <code>wide</code> parameter, using the given array length.
     */
    public String[] getStringArray(long base, int length, boolean wide) {
    
        List strings = new ArrayList();
        Pointer p;
        int offset = 0;
        if (length != -1) {
            p = getPointer(base + offset);
            int count = 0;
            while (count++ < length) {
                String s = p == null ? null : p.getString(0, wide);
                strings.add(s);
                if (count < length) {
                    offset += SIZE;
                    p = getPointer(base + offset);
                }
            }
        }
        else {
            while ((p = getPointer(base + offset)) != null) {
                String s = p == null ? null : p.getString(0, wide);
                strings.add(s);
                offset += SIZE;
            }
        }
        return (String[])strings.toArray(new String[strings.size()]);
    }

    //////////////////////////////////////////////////////////////////////////
    // Java type write methods
    //////////////////////////////////////////////////////////////////////////

    void setValue(long offset, Object value, Class type) {

        // Set the value at the offset according to its type
        if (type == boolean.class || type == Boolean.class) {
            setInt(offset, Boolean.TRUE.equals(value) ? -1 : 0);
        }
        else if (type == byte.class || type == Byte.class) {
            setByte(offset, value == null ? 0 : ((Byte)value).byteValue());
        }
        else if (type == short.class || type == Short.class) {
            setShort(offset, value == null ? 0 : ((Short)value).shortValue());
        }
        else if (type == char.class || type == Character.class) {
            setChar(offset, value == null ? 0 : ((Character)value).charValue());
        }
        else if (type == int.class || type == Integer.class) {
            setInt(offset, value == null ? 0 : ((Integer)value).intValue());
        }
        else if (type == long.class || type == Long.class) {
            setLong(offset, value == null ? 0 : ((Long)value).longValue());
        }
        else if (type == float.class || type == Float.class) {
            setFloat(offset, value == null ? 0f : ((Float)value).floatValue());
        }
        else if (type == double.class || type == Double.class) {
            setDouble(offset, value == null ? 0.0 : ((Double)value).doubleValue());
        }
        else if (type == Pointer.class) {
            setPointer(offset, (Pointer)value);
        }
        else if (type == String.class) {
            setPointer(offset, (Pointer)value);
        }
        else if (type == WString.class) {
            setPointer(offset, (Pointer)value);
        }
        else if (Structure.class.isAssignableFrom(type)) {
            Structure s = (Structure)value;
            if (Structure.ByReference.class.isAssignableFrom(type)) {
                setPointer(offset, s == null ? null : s.getPointer());
                if (s != null) {
                    s.autoWrite();
                }
            }
            else {
                s.useMemory(this, (int)offset);
                s.write();
            }
        }
        else if (Callback.class.isAssignableFrom(type)) {
            setPointer(offset, CallbackReference.getFunctionPointer((Callback)value));
        }
        else if (Buffer.class.isAssignableFrom(type)) {
            Pointer p = value == null ? null
                : Native.getDirectBufferPointer((Buffer)value);
            setPointer(offset, p);
        }
        else if (NativeMapped.class.isAssignableFrom(type)) {
            NativeMappedConverter tc = NativeMappedConverter.getInstance(type);
            Class nativeType = tc.nativeType();
            setValue(offset, tc.toNative(value, new ToNativeContext()), nativeType);
        }
        else if (type.isArray()) {
            setArrayValue(offset, value, type.getComponentType());
        }
        else {
            throw new IllegalArgumentException("Writing " + type + " to memory is not supported");
        }
    }

    private void setArrayValue(long offset, Object value, Class cls) {
        if (cls == byte.class) {
            byte[] buf = (byte[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == short.class) {
            short[] buf = (short[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == char.class) {
            char[] buf = (char[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == int.class) {
            int[] buf = (int[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == long.class) {
            long[] buf = (long[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == float.class) {
            float[] buf = (float[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (cls == double.class) {
            double[] buf = (double[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (Pointer.class.isAssignableFrom(cls)) {
            Pointer[] buf = (Pointer[])value;
            write(offset, buf, 0, buf.length);
        }
        else if (Structure.class.isAssignableFrom(cls)) {
            Structure[] sbuf = (Structure[])value;
            if (Structure.ByReference.class.isAssignableFrom(cls)) {
                Pointer[] buf = new Pointer[sbuf.length];
                for (int i=0;i < sbuf.length;i++) {
                    buf[i] = sbuf[i] == null ? null : sbuf[i].getPointer();
                    sbuf[i].write();
                }
                write(offset, buf, 0, buf.length);
            }
            else {
                for (int i=0;i < sbuf.length;i++) {
                    if (sbuf[i] == null) {
                        sbuf[i] = Structure.newInstance(cls);
                    }
                    sbuf[i].useMemory(this, (int)(offset + i * sbuf[i].size()));
                    sbuf[i].write();
                }
            }
        }
        else if (NativeMapped.class.isAssignableFrom(cls)) {
            NativeMapped[] buf = (NativeMapped[])value;
            NativeMappedConverter tc = NativeMappedConverter.getInstance(cls);
            Class nativeType = tc.nativeType();
            int size = Native.getNativeSize(value.getClass(), value) / buf.length;
            for (int i=0;i < buf.length;i++) {
                Object element = tc.toNative(buf[i], new ToNativeContext());
                setValue(offset + i*size, element, nativeType);
            }
        }
        else {
            throw new IllegalArgumentException("Writing array of "
                                               + cls + " to memory not supported");
        }
    }

    /** Write <code>value</code> to the requested bank of memory. 
     * @param offset byte offset from pointer to start
     * @param length number of bytes to write
     * @param value value to be written
     */
    public void setMemory(long offset, long length, byte value) {
        _setMemory(peer + offset, length, value);
    }
    
    static native void _setMemory(long addr, long length, byte value);
    
    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jbyte *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *		     must be set
     * @param value <code>byte</code> value to set
     */
    public void setByte(long offset, byte value) {
        _setByte(peer + offset, value);
    }

    private static native void _setByte(long addr, byte value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jshort *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *		     must be set
     * @param value <code>short</code> value to set
     */
    public void setShort(long offset, short value) {
        _setShort(peer + offset, value);
    }
    
    private static native void _setShort(long addr, short value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((wchar_t *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *               must be set
     * @param value <code>char</code> value to set
     */
    public void setChar(long offset, char value) {
        _setChar(peer + offset, value);
    }
    
    private static native void _setChar(long addr, char value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jint *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *		     must be set
     * @param value <code>int</code> value to set
     */
    public void setInt(long offset, int value) {
        _setInt(peer + offset, value);
    }

    private static native void _setInt(long addr, int value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jlong *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *               must be set
     * @param value <code>long</code> value to set
     */
    public void setLong(long offset, long value) {
        _setLong(peer + offset, value);
    }
    
    private static native void _setLong(long addr, long value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((long *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *               must be set
     * @param value <code>long</code> value to set
     */
    public void setNativeLong(long offset, NativeLong value) {
        if (NativeLong.SIZE == 8) {
            setLong(offset, value.longValue());
        } else {
            setInt(offset, value.intValue());
        }
    }

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jfloat *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *               must be set
     * @param value <code>float</code> value to set
     */
    public void setFloat(long offset, float value) {
        _setFloat(peer + offset, value);
    }
    
    private static native void _setFloat(long addr, float value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression
     * <code>*((jdouble *)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code>
     *               must be set
     * @param value <code>double</code> value to set
     */
    public void setDouble(long offset, double value) {
        _setDouble(peer + offset, value);
    }

    private static native void _setDouble(long addr, double value);

    /**
     * Set <code>value</code> at location being pointed to. This is equivalent
     * to the expression 
     * <code>*((void **)((char *)Pointer + offset)) = value</code>.
     *
     * @param offset byte offset from pointer at which <code>value</code> 
     *               must be set
     * @param value <code>Pointer</code> holding the actual pointer value to 
     * set, which may be <code>null</code> to indicate a <code>NULL</code>
     * pointer.
     */
    public void setPointer(long offset, Pointer value) {
        _setPointer(peer + offset, value != null ? value.peer : 0);
    }

    private static native void _setPointer(long addr, long value);
    
    /**
     * Copy string <code>value</code> to the location being pointed to.  Copy
     * each element in <code>value</code>, converted to native encoding, at an
     * <code>offset</code>from the location pointed to by this pointer.
     *
     * @param offset byte offset from pointer at which characters in
     * 		     <code>value</code> must be set
     * @param value  <code>java.lang.String</code> value to set
     * @param wide whether to write the native string as an array of 
     * <code>wchar_t</code>.  If false, writes as a NUL-terminated array of 
     * <code>char</code> using the default platform encoding. 
     */
    public void setString(long offset, String value, boolean wide) {
        _setString(peer + offset, value, wide);
    }
    
    private static native void _setString(long addr, String value, boolean wide);

    /**
     * Copy string <code>value</code> to the location being pointed to.  Copy
     * each element in <code>value</code>, converted to native encoding, at an
     * <code>offset</code>from the location pointed to by this pointer.
     * Uses the value of the system property <code>jna.encoding</code>, if set, 
     * to determine the appropriate native charset in which to encode the value.  
     * If the property is not set, uses the default platform encoding.
     *
     * @param offset byte offset from pointer at which characters in
     *               <code>value</code> must be set
     * @param value  <code>java.lang.String</code> value to set
     */
    public void setString(long offset, String value) {
        byte[] data = Native.getBytes(value);
        write(offset, data, 0, data.length);
        setByte(offset + data.length, (byte)0);
    }
    
    public String toString() {
        return "native@0x" + Long.toHexString(peer);
    }
    
    /** Read the native peer value.  Use with caution. */
    public static long nativeValue(Pointer p) {
        return p.peer;
    }

    /** Set the native peer value.  Use with caution. */
    public static void nativeValue(Pointer p, long value) {
        p.peer = value;
    }

    /** Pointer which disallows all read/write access. */
    private static class Opaque extends Pointer {
        private Opaque(long peer) { super(peer); }
        private final String MSG = "This pointer is opaque: " + this;
        public long indexOf(long offset, byte value) {
            throw new UnsupportedOperationException(MSG);
        }
        public void read(long bOff, byte[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, char[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, short[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, int[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, long[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, float[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void read(long bOff, double[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, byte[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, char[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, short[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, int[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, long[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, float[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public void write(long bOff, double[] buf, int index, int length) { 
            throw new UnsupportedOperationException(MSG); 
        }
        public byte getByte(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public char getChar(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public short getShort(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public int getInt(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public long getLong(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public float getFloat(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public double getDouble(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public Pointer getPointer(long bOff) {
            throw new UnsupportedOperationException(MSG); 
        }
        public String getString(long bOff, boolean wide) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setByte(long bOff, byte value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setChar(long bOff, char value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setShort(long bOff, short value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setInt(long bOff, int value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setLong(long bOff, long value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setFloat(long bOff, float value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setDouble(long bOff, double value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setPointer(long offset, Pointer value) {
            throw new UnsupportedOperationException(MSG); 
        }
        public void setString(long offset, String value, boolean wide) {
            throw new UnsupportedOperationException(MSG); 
        }
        public String toString() {
            return "opaque@0x" + Long.toHexString(peer);
        }
    }
} public static class CredHandle extends SecHandle {		
}
 public static class CtxtHandle extends SecHandle {		
}
 public static class PSecPkgInfo extends Structure {

	public static class ByReference extends PSecPkgInfo implements Structure.ByReference {

	}
		
	/**
	 * The first entry in an array of SecPkgInfo structures.
	 */
	public SecPkgInfo.ByReference pPkgInfo;
		
	public PSecPkgInfo() {
			
	}
		
	/**
	 * An array of SecPkgInfo structures.
	 */
	public SecPkgInfo.ByReference[] toArray(int size) {
		return (SecPkgInfo.ByReference[]) pPkgInfo.toArray(size);
	}
}
 public static class SecBufferDesc extends Structure {
				
	/**
	 * Version number.
	 */
    public NativeLong ulVersion;
    /**
     * Number of buffers.
     */
    public NativeLong cBuffers;
    /**
     * Pointer to array of buffers.
     */
    public SecBuffer.ByReference[] pBuffers;

    /**
     * Create a new SecBufferDesc with one SECBUFFER_EMPTY buffer.
     */
    public SecBufferDesc() {
    	ulVersion = new NativeLong(SECBUFFER_VERSION);
    	cBuffers = new NativeLong(1);
    	SecBuffer.ByReference secBuffer = new SecBuffer.ByReference();
    	pBuffers = (SecBuffer.ByReference[]) secBuffer.toArray(1);
    	allocateMemory();
    }
	    
    /**
     * Create a new SecBufferDesc with initial data.
     * @param type
     *  Token type.
     * @param token
     *  Initial token data.
     */
    public SecBufferDesc(int type, byte[] token) {
    	ulVersion = new NativeLong(SECBUFFER_VERSION);
    	cBuffers = new NativeLong(1);
    	SecBuffer.ByReference secBuffer = new SecBuffer.ByReference(type, token);
    	pBuffers = (SecBuffer.ByReference[]) secBuffer.toArray(1);
    	allocateMemory();	    	
    }
	    
    /**
     * Create a new SecBufferDesc with one SecBuffer of a given type and size.
     * @param type
     * @param tokenSize
     */
    public SecBufferDesc(int type, int tokenSize) {
    	ulVersion = new NativeLong(SECBUFFER_VERSION);
    	cBuffers = new NativeLong(1);
    	SecBuffer.ByReference secBuffer = new SecBuffer.ByReference(type, tokenSize);
    	pBuffers = (SecBuffer.ByReference[]) secBuffer.toArray(1);
    	allocateMemory();
    }	    	
	    
    public byte[] getBytes() {
    	if (pBuffers == null || cBuffers == null) {
    		throw new RuntimeException("pBuffers | cBuffers");
    	}
    	if (cBuffers.intValue() == 1) {
    		return pBuffers[0].getBytes();
    	}	    	
    	throw new RuntimeException("cBuffers > 1");
    }
}
 public static class TimeStamp extends SECURITY_INTEGER {
		
}
 public static class HANDLEByReference extends ByReference {
        
	public HANDLEByReference() {
        this(null);
    }
        
    public HANDLEByReference(HANDLE h) {
        super(Pointer.SIZE);
        setValue(h);
    }
        
    public void setValue(HANDLE h) {
        getPointer().setPointer(0, h != null ? h.getPointer() : null);
    }
        
    public HANDLE getValue() {
        Pointer p = getPointer().getPointer(0);
        if (p == null)
            return null;
        if (WinBase.INVALID_HANDLE_VALUE.getPointer().equals(p)) 
            return WinBase.INVALID_HANDLE_VALUE;
        HANDLE h = new HANDLE();
        h.setPointer(p);
        return h;
    }
}
 public static class LUID extends Structure {
	int LowPart;
	int HighPart;
}
 public class IntByReference extends ByReference {

    public IntByReference() {
        this(0);
    }
    
    public IntByReference(int value) {
        super(4);
        setValue(value);
    }
    
    public void setValue(int value) {
        getPointer().setInt(0, value);
    }
    
    public int getValue() {
        return getPointer().getInt(0);
    }
} public class NativeLongByReference extends ByReference {
    public NativeLongByReference() {
        this(new NativeLong(0));
    }
    
    public NativeLongByReference(NativeLong value) {
        super(NativeLong.SIZE);
        setValue(value);
    }
    
    public void setValue(NativeLong value) {
        getPointer().setNativeLong(0, value);
    }
    
    public NativeLong getValue() {
        return getPointer().getNativeLong(0);
    }
} public interface StdCallLibrary extends Library, StdCall {
    /** Constant identifying the w32 stdcall calling convention. */
    int STDCALL_CONVENTION = Function.ALT_CONVENTION;
    /** Provides auto-lookup of stdcall-decorated names. */
    FunctionMapper FUNCTION_MAPPER = new StdCallFunctionMapper();
    /** Interface defining a callback using the w32 stdcall calling convention. 
     */
    interface StdCallCallback extends Callback, StdCall { }
} public interface W32APIOptions extends StdCallLibrary {
    /** Standard options to use the unicode version of a w32 API. */
    Map UNICODE_OPTIONS = new HashMap() {
        {
            put(OPTION_TYPE_MAPPER, W32APITypeMapper.UNICODE);
            put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.UNICODE);
        }
    };
    /** Standard options to use the ASCII/MBCS version of a w32 API. */
    Map ASCII_OPTIONS = new HashMap() {
        {
            put(OPTION_TYPE_MAPPER, W32APITypeMapper.ASCII);
            put(OPTION_FUNCTION_MAPPER, W32APIFunctionMapper.ASCII);
        }
    };
    Map DEFAULT_OPTIONS = Boolean.getBoolean("w32.ascii") ? ASCII_OPTIONS : UNICODE_OPTIONS;
}

import com.sun.jna.Native;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.Sspi.CredHandle;
import com.sun.jna.platform.win32.Sspi.CtxtHandle;
import com.sun.jna.platform.win32.Sspi.PSecPkgInfo;
import com.sun.jna.platform.win32.Sspi.SecBufferDesc;
import com.sun.jna.platform.win32.Sspi.TimeStamp;
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
import com.sun.jna.platform.win32.WinNT.LUID;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.NativeLongByReference;
import com.sun.jna.win32.StdCallLibrary;
import com.sun.jna.win32.W32APIOptions;

/* Copyright (c) 2010 Daniel Doubrovkine, All Rights Reserved
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.  
 */
package com.sun.jna.platform.win32;


/**
 * Secur32.dll Interface.
 * @author dblock[at]dblock.org
 */
public interface Secur32 extends StdCallLibrary {
	Secur32 INSTANCE = (Secur32) Native.loadLibrary(
			"Secur32", Secur32.class, W32APIOptions.UNICODE_OPTIONS);
	
	/**
	 * Specifies a format for a directory service object name.
	 * http://msdn.microsoft.com/en-us/library/ms724268(VS.85).aspx
	 */
	public abstract class EXTENDED_NAME_FORMAT {
		public static final int NameUnknown = 0;
		public static final int NameFullyQualifiedDN = 1;
		public static final int NameSamCompatible = 2;
		public static final int NameDisplay = 3;
		public static final int NameUniqueId = 6;
		public static final int NameCanonical = 7;
		public static final int NameUserPrincipal = 8;
		public static final int NameCanonicalEx = 9;
		public static final int NameServicePrincipal = 10;
		public static final int NameDnsDomain = 12;
	};
	
	/**
	 * Retrieves the name of the user or other security principal associated with 
	 * the calling thread. You can specify the format of the returned name.
	 * @param nameFormat The format of the name. 
	 * @param lpNameBuffer A pointer to a buffer that receives the name in the specified format. 
	 * @param len On input, the size of the buffer, on output the number of characters copied into the buffer, not including the terminating null character.
	 * @return True if the function succeeds. False otherwise.
	 */
	public boolean GetUserNameEx(int nameFormat, char[] lpNameBuffer, IntByReference len);

	/**
	 * The AcquireCredentialsHandle function acquires a handle to preexisting credentials 
	 * of a security principal. This handle is required by the AcceptSecurityContext 
	 * and InitializeSecurityContext functions. These can be either preexisting credentials, 
	 * which are established through a system logon that is not described here, or the 
	 * caller can provide alternative credentials.
	 * @param pszPrincipal
	 *  A pointer to a null-terminated string that specifies the name of the principal whose 
	 *  credentials the handle will reference.
	 * @param pszPackage
	 *   A pointer to a null-terminated string that specifies the name of the security package 
	 *   with which these credentials will be used.
	 * @param fCredentialUse
	 *  A flag that indicates how these credentials will be used.
	 * @param pvLogonID
	 *  A pointer to a locally unique identifier (LUID) that identifies the user. 
	 * @param pAuthData
	 *  A pointer to package-specific data. This parameter can be NULL, which indicates 
	 *  that the default credentials for that package must be used. To use supplied 
	 *  credentials, pass a SEC_WINNT_AUTH_IDENTITY structure that includes those credentials 
	 *  in this parameter.
	 * @param pGetKeyFn
	 *  This parameter is not used and should be set to NULL. 
	 * @param pvGetKeyArgument
	 *  This parameter is not used and should be set to NULL.
	 * @param phCredential
	 *  A pointer to a CredHandle structure to receive the credential handle. 
	 * @param ptsExpiry
	 *  A pointer to a TimeStamp structure that receives the time at which the returned 
	 *  credentials expire. The value returned in this TimeStamp structure depends on 
	 *  the security package. The security package must return this value in local time.
	 * @return
	 *  If the function succeeds, the function returns one of the SEC_I_ success codes.
	 *  If the function fails, the function returns one of the SEC_E_ error codes.
	 */
	public int AcquireCredentialsHandle(String pszPrincipal, String pszPackage,
			NativeLong fCredentialUse, LUID pvLogonID,
			Pointer pAuthData, Pointer pGetKeyFn, // TODO: SEC_GET_KEY_FN
			Pointer pvGetKeyArgument, CredHandle phCredential, 
			TimeStamp ptsExpiry);
	
	/**
	 * The InitializeSecurityContext function initiates the client side, outbound security 
	 * context from a credential handle. The function is used to build a security context 
	 * between the client application and a remote peer. InitializeSecurityContext returns 
	 * a token that the client must pass to the remote peer, which the peer in turn submits 
	 * to the local security implementation through the AcceptSecurityContext call. The 
	 * token generated should be considered opaque by all callers.
	 * 
	 * Typically, the InitializeSecurityContext function is called in a loop until a 
	 * sufficient security context is established.
	 * 
	 * @param phCredential
	 *  A handle to the credentials returned by AcquireCredentialsHandle. This handle is 
	 *  used to build the security context. The InitializeSecurityContext function requires 
	 *  at least OUTBOUND credentials. 
	 * @param phContext
	 *  A pointer to a CtxtHandle structure. On the first call to InitializeSecurityContext,
	 *  this pointer is NULL. On the second call, this parameter is a pointer to the handle 
	 *  to the partially formed context returned in the phNewContext parameter by the first 
	 *  call.
	 * @param pszTargetName
	 *  A pointer to a null-terminated string that indicates the target of the context. 
	 *  The string contents are security-package specific.
	 * @param fContextReq
	 *  Bit flags that indicate requests for the context. Not all packages can support all 
	 *  requirements. Flags used for this parameter are prefixed with ISC_REQ_, for example,
	 *  ISC_REQ_DELEGATE. 
	 * @param Reserved1
	 *  This parameter is reserved and must be set to zero.
	 * @param TargetDataRep
	 *  The data representation, such as byte ordering, on the target. This parameter can be 
	 *  either SECURITY_NATIVE_DREP or SECURITY_NETWORK_DREP.
	 * @param pInput
	 *  A pointer to a SecBufferDesc structure that contains pointers to the buffers supplied 
	 *  as input to the package. The pointer must be NULL on the first call to the function. 
	 *  On subsequent calls to the function, it is a pointer to a buffer allocated with enough 
	 *  memory to hold the token returned by the remote peer.
	 * @param Reserved2
	 *  This parameter is reserved and must be set to zero. 
	 * @param phNewContext
	 *  A pointer to a CtxtHandle structure. On the first call to InitializeSecurityContext, 
	 *  this pointer receives the new context handle. On the second call, phNewContext can be 
	 *  the same as the handle specified in the phContext parameter.
	 * @param pOutput
	 *  A pointer to a SecBufferDesc structure that contains pointers to the SecBuffer structure 
	 *  that receives the output data. If a buffer was typed as SEC_READWRITE in the input, it 
	 *  will be there on output. The system will allocate a buffer for the security token if 
	 *  requested (through ISC_REQ_ALLOCATE_MEMORY) and fill in the address in the buffer 
	 *  descriptor for the security token.
	 * @param pfContextAttr
	 *  A pointer to a variable to receive a set of bit flags that indicate the attributes of 
	 *  the established context. Flags used for this parameter are prefixed with ISC_RET, 
	 *  such as ISC_RET_DELEGATE.
	 * @param ptsExpiry
	 *  A pointer to a TimeStamp structure that receives the expiration time of the context.
	 *  It is recommended that the security package always return this value in local time. 
	 *  This parameter is optional and NULL should be passed for short-lived clients.
	 * @return
	 *  If the function succeeds, the function returns one of the SEC_I_ success codes.
	 *  If the function fails, the function returns one of the SEC_E_ error codes.
	 */

	public int InitializeSecurityContext(CredHandle phCredential, CtxtHandle phContext,
