/* * Copyright (c) 2007 Wayne Meissner * * This file is part of gstreamer-java. * * This code is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3 only, as * published by the Free Software Foundation. * * This code 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 * version 3 for more details. * * You should have received a copy of the GNU Lesser General Public License * version 3 along with this work. If not, see <http://www.gnu.org/licenses/>. */ package org.gstreamer.lowlevel; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.gstreamer.ClockTime; import com.sun.jna.Library; import com.sun.jna.Native; import com.sun.jna.NativeLibrary; import com.sun.jna.Platform; /** * */ public final class GNative { // gstreamer on win32 names the dll files one of foo.dll, libfoo.dll and libfoo-0.dll private static String[] windowsNameFormats = { "%s", "lib%s", "lib%s-0" }; private static AtomicReference<String> globalLibName; private GNative() {} public static synchronized <T extends Library> T loadLibrary(String name, Class<T> interfaceClass, Map<String, ?> options) { if (!Platform.isWindows()) return loadNativeLibrary(name, interfaceClass, options); for (String format : windowsNameFormats) try { return interfaceClass.cast(loadNativeLibrary(String.format(format, name), interfaceClass, options)); } catch (UnsatisfiedLinkError ex) { continue; } throw new UnsatisfiedLinkError("Could not load library: " + name); } private static <T extends Library> T loadNativeLibrary(String name, Class<T> interfaceClass, Map<String, ?> options) { T library = interfaceClass.cast(Native.loadLibrary(globalLibName == null ? name : globalLibName.get(), interfaceClass, options)); boolean needCustom = false; search: for (Method m : interfaceClass.getMethods()) for (Class<?> cls : m.getParameterTypes()) if (cls.isArray() && getConverter(cls.getComponentType()) != null) { needCustom = true; break search; } if (!needCustom) return library; // System.out.println("Using custom library proxy for " + interfaceClass.getName()); return interfaceClass.cast( Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{ interfaceClass }, new Handler<T>(library, options))); } public static synchronized NativeLibrary getNativeLibrary(String name) { if (!Platform.isWindows()) return NativeLibrary.getInstance(name); for (String format : windowsNameFormats) try { return NativeLibrary.getInstance(String.format(format, name)); } catch (UnsatisfiedLinkError ex) { continue; } throw new UnsatisfiedLinkError("Could not load library: " + name); } private static interface Converter { Class<?> nativeType(); Object toNative(Object value); Object fromNative(Object value, Class<?> javaType); } private static final Converter enumConverter = new Converter() { public Class<?> nativeType() { return int.class; } public Object toNative(Object value) { return value != null ? EnumMapper.getInstance().intValue((Enum<?>) value) : 0; } @SuppressWarnings({"unchecked","rawtypes"}) public Object fromNative(Object value, Class javaType) { return EnumMapper.getInstance().valueOf((Integer) value, javaType); } }; private static final Converter booleanConverter = new Converter() { public Class<?> nativeType() { return int.class; } public Object toNative(Object value) { return value != null ? Boolean.TRUE.equals(value) ? 1 : 0 : 0; } @SuppressWarnings("rawtypes") public Object fromNative(Object value, Class javaType) { return value != null ? ((Integer) value).intValue() != 0 : 0; } }; private static final Converter clocktimeConverter = new Converter() { public Class<?> nativeType() { return long.class; } public Object toNative(Object value) { return value != null ? ((ClockTime) value).toNanos() : 0L; } public Object fromNative(Object value, Class<?> javaType) { return ClockTime.valueOf(value != null ? (Long) value : 0L, TimeUnit.NANOSECONDS); } }; private static Converter getConverter(Class<?> javaType) { if (Enum.class.isAssignableFrom(javaType)) return enumConverter; else if (ClockTime.class == javaType) return clocktimeConverter; else if (boolean.class == javaType || Boolean.class == javaType) return booleanConverter; return null; } private static class Handler<T> implements InvocationHandler { private final InvocationHandler proxy; @SuppressWarnings("unused") // Keep a reference to stop underlying Library being GC'd private final T library; public Handler(T library, Map<String, ?> options) { this.library = library; this.proxy = Proxy.getInvocationHandler(library); } public Object invoke(Object self, Method method, Object[] args) throws Throwable { int lastArg = args != null ? args.length : 0; if (method.isVarArgs()) --lastArg; Runnable[] postInvoke = null; int postCount = 0; for (int i = 0; i < lastArg; ++i) { if (args[i] == null) continue; final Class<?> cls = args[i].getClass(); if (!cls.isArray() || cls.getComponentType().isPrimitive() || cls.getComponentType() == String.class) continue; final Converter converter = getConverter(cls.getComponentType()); if (converter != null) { final Object[] src = (Object[]) args[i]; final Object dst = java.lang.reflect.Array.newInstance(converter.nativeType(), src.length); final ArrayIO io = getArrayIO(converter.nativeType()); for (int a = 0; a < src.length; ++a) io.set(dst, a, converter.toNative(src[a])); if (postInvoke == null) postInvoke = new Runnable[lastArg]; postInvoke[postCount++] = new Runnable() { public void run() { for (int a = 0; a < src.length; ++a) src[a] = converter.fromNative(io.get(dst, a), cls.getComponentType()); } }; args[i] = dst; } } Object retval = proxy.invoke(self, method, args); // // Reload any native arrays into java arrays // for (int i = 0; i < postCount; ++i) postInvoke[i].run(); return retval; } @SuppressWarnings("unused") Class<?> getNativeClass(Class<?> cls) { if (cls == Integer.class) return int.class; else if (cls == Long.class) return long.class; return cls; } private static interface ArrayIO { public void set(Object array, int index, Object data); public Object get(Object array, int index); } private static final ArrayIO intArrayIO = new ArrayIO() { public void set(Object array, int index, Object data) { java.lang.reflect.Array.setInt(array, index, data != null ? (Integer) data : 0); } public Object get(Object array, int index) { return java.lang.reflect.Array.getInt(array, index); } }; private static final ArrayIO longArrayIO = new ArrayIO() { public void set(Object array, int index, Object data) { java.lang.reflect.Array.setLong(array, index, data != null ? (Long) data : 0); } public Object get(Object array, int index) { return java.lang.reflect.Array.getLong(array, index); } }; private static ArrayIO getArrayIO(final Class<?> cls) { if (cls == int.class || cls == Integer.class) return intArrayIO; else if (cls == long.class || cls == Long.class) return longArrayIO; throw new IllegalArgumentException("No such conversion"); } } /** * * Map all DLL/SO names to the a single library. This can be useful in situations when all the * gstreamer code is compiled into a single object. If globalLibName is set to 'null', then you are * responsible for pre-loading all necessary libraries yourself and * GNative will attach Library interfaces to the current running process instead of loading a library. * * This method has to be called before any other gstreamer lowlevel API Libraries are loaded. * * @param name name of single library or null if all libraries are preloaded */ public static void setGlobalLibName(String name) { globalLibName = new AtomicReference<String>(name); } }