/*
* Copyright (c) 2003-2005 Sun Microsystems, Inc. All Rights Reserved.
* Copyright (c) 2013 JogAmp Community. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* - Redistribution of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistribution in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* Neither the name of Sun Microsystems, Inc. or the names of
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* This software is provided "AS IS," without a warranty of any kind. ALL
* EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES,
* INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN
* MICROSYSTEMS, INC. ("SUN") AND ITS LICENSORS SHALL NOT BE LIABLE FOR
* ANY DAMAGES SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR
* DISTRIBUTING THIS SOFTWARE OR ITS DERIVATIVES. IN NO EVENT WILL SUN OR
* ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR
* DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE
* DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY OF LIABILITY,
* ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, EVEN IF
* SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
*
* You acknowledge that this software is not designed or intended for use
* in the design, construction, operation or maintenance of any nuclear
* facility.
*/
package com.jogamp.gluegen.runtime;
import com.jogamp.common.os.DynamicLookupHelper;
import com.jogamp.common.util.SecurityUtil;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
/**
* Superclass for all generated ProcAddressTables.
*
* A ProcAddressTable is a cache of pointers to the dynamically-linkable C
* functions this autogenerated Java binding has exposed. Some
* libraries such as OpenGL, OpenAL and others define function pointer
* signatures rather than statically linkable entry points for the
* purposes of being able to query at run-time whether a particular
* extension is available. This table acts as a cache of these
* function pointers. Each function pointer is typically looked up at
* run-time by a platform-dependent mechanism such as dlsym(),
* wgl/glXGetProcAddress(), or alGetProcAddress(). If the field containing the function
* pointer is 0, the function is considered to be unavailable and can
* not be called.
*
* @author Kenneth Russel
* @author Michael Bien
* @author Sven Gothel
*
* @see FunctionAddressResolver
* @see DynamicLookupHelper
*/
public abstract class ProcAddressTable {
private static final String PROCADDRESS_VAR_PREFIX = "_addressof_";
private static final int PROCADDRESS_VAR_PREFIX_LEN = PROCADDRESS_VAR_PREFIX.length();
protected static boolean DEBUG;
protected static String DEBUG_PREFIX;
protected static int debugNum;
private final FunctionAddressResolver resolver;
static {
AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
DEBUG = (System.getProperty("jogamp.debug.ProcAddressHelper") != null);
if (DEBUG) {
DEBUG_PREFIX = System.getProperty("jogamp.debug.ProcAddressHelper.prefix");
}
return null;
}
});
}
public ProcAddressTable() {
this(new One2OneResolver());
}
public ProcAddressTable(final FunctionAddressResolver resolver) {
this.resolver = resolver;
}
/**
* Resets the complete table.
* <p>
* If a {@link SecurityManager} is installed, user needs link permissions
* for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
* </p>
* @throws SecurityException if user is not granted access for all libraries.
*/
public void reset(final DynamicLookupHelper lookup) throws SecurityException, RuntimeException {
if(null==lookup) {
throw new RuntimeException("Passed null DynamicLookupHelper");
}
final Field[] fields = getClass().getDeclaredFields();
final PrintStream dout;
if (DEBUG) {
dout = getDebugOutStream();
dout.println(getClass().getName()+".reset() (w/ "+fields.length+" prospective fields)");
} else {
dout = null;
}
// All at once - performance.
AccessibleObject.setAccessible(fields, true);
lookup.claimAllLinkPermission();
try {
for (int i = 0; i < fields.length; ++i) {
final String fieldName = fields[i].getName();
if ( isAddressField(fieldName) ) {
final String funcName = fieldToFunctionName(fieldName);
setEntry(fields[i], funcName, lookup);
}
}
} finally {
lookup.releaseAllLinkPermission();
}
if (DEBUG) {
dout.flush();
if (DEBUG_PREFIX != null) {
dout.close();
}
}
}
/**
* Initializes the mapping for a single function.
* <p>
* If a {@link SecurityManager} is installed, user needs link permissions
* for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
* </p>
*
* @throws IllegalArgumentException if this function is not in this table.
* @throws SecurityException if user is not granted access for all libraries.
*/
public void initEntry(final String name, final DynamicLookupHelper lookup) throws SecurityException, IllegalArgumentException {
final Field addressField = fieldForFunction(name);
addressField.setAccessible(true);
setEntry(addressField, name, lookup);
}
private final void setEntry(final Field addressField, final String funcName, final DynamicLookupHelper lookup) throws SecurityException {
try {
assert (addressField.getType() == Long.TYPE);
final long newProcAddress = resolver.resolve(funcName, lookup); // issues SecurityUtil.checkLinkPermission(String)
addressField.setLong(this, newProcAddress);
if (DEBUG) {
getDebugOutStream().println(" " + addressField.getName() + " -> 0x" + Long.toHexString(newProcAddress));
}
} catch (final Exception e) {
throw new RuntimeException("Can not get proc address for method \""
+ funcName + "\": Couldn't set value of field \"" + addressField, e);
}
}
private final String fieldToFunctionName(final String addressFieldName) {
return addressFieldName.substring(PROCADDRESS_VAR_PREFIX_LEN);
}
private final Field fieldForFunction(final String name) throws IllegalArgumentException {
try {
return getClass().getDeclaredField(PROCADDRESS_VAR_PREFIX + name);
} catch (final NoSuchFieldException ex) {
throw new IllegalArgumentException(getClass().getName() +" has no entry for the function '"+name+"'.", ex);
}
}
/**
* Warning: Returns an accessible probably protected field!
* <p>
* Caller should have checked link permissions
* for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>
* <i>if</i> exposing the field or address!
* </p>
*/
private final Field fieldForFunctionInSec(final String name) throws IllegalArgumentException {
return AccessController.doPrivileged(new PrivilegedAction<Field>() {
@Override
public Field run() {
try {
final Field addressField = ProcAddressTable.this.getClass().getDeclaredField(PROCADDRESS_VAR_PREFIX + name);
addressField.setAccessible(true); // we need to read the protected value!
return addressField;
} catch (final NoSuchFieldException ex) {
throw new IllegalArgumentException(getClass().getName() +" has no entry for the function '"+name+"'.", ex);
}
}
} );
}
private final boolean isAddressField(final String fieldName) {
return fieldName.startsWith(PROCADDRESS_VAR_PREFIX);
}
private final static PrintStream getDebugOutStream() {
PrintStream out = null;
if (DEBUG) {
if (DEBUG_PREFIX != null) {
try {
out = new PrintStream(new BufferedOutputStream(new FileOutputStream(DEBUG_PREFIX + File.separatorChar
+ "procaddresstable-" + (++debugNum) + ".txt")));
} catch (final IOException e) {
e.printStackTrace();
out = System.err;
}
} else {
out = System.err;
}
}
return out;
}
/**
* Returns this table as map with the function name as key and the address as value.
*/
private final Map<String, Long> toMap() {
final SortedMap<String, Long> map = new TreeMap<String, Long>();
final Field[] fields = getClass().getFields();
try {
for (int i = 0; i < fields.length; ++i) {
final String addressFieldName = fields[i].getName();
if (isAddressField(addressFieldName)) {
map.put(fieldToFunctionName(addressFieldName), (Long)fields[i].get(this));
}
}
} catch (final IllegalArgumentException ex) {
throw new RuntimeException(ex);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
}
return map;
}
/**
* Returns true only if non null function pointer to this function exists.
*/
public final boolean isFunctionAvailable(final String functionName) {
try{
return isFunctionAvailableImpl(functionName);
} catch (final IllegalArgumentException ex) {
return false;
}
}
/**
* This is a convenience method to query the native function existence by name.
* <p>
* It lets you avoid having to
* manually compute the "{@link #PROCADDRESS_VAR_PREFIX} + <functionName>"
* member variable name and look it up via reflection.
* </p>
*
* @throws IllegalArgumentException if this function is not in this table.
*/
protected boolean isFunctionAvailableImpl(final String functionName) throws IllegalArgumentException {
final Field addressField = fieldForFunctionInSec(functionName);
try {
return 0 != addressField.getLong(this);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
/**
* This is a convenience method to query the native function handle by name.
* <p>
* It lets you avoid having to
* manually compute the "{@link #PROCADDRESS_VAR_PREFIX} + <functionName>"
* member variable name and look it up via reflection.
* </p>
* <p>
* If a {@link SecurityManager} is installed, user needs link permissions
* for <b>all</b> libraries, i.e. for <code>new RuntimePermission("loadLibrary.*");</code>!
* </p>
*
* @throws IllegalArgumentException if this function is not in this table.
* @throws SecurityException if user is not granted access for all libraries.
*/
public long getAddressFor(final String functionName) throws SecurityException, IllegalArgumentException {
SecurityUtil.checkAllLinkPermission();
final Field addressField = fieldForFunctionInSec(functionName);
try {
return addressField.getLong(this);
} catch (final IllegalAccessException ex) {
throw new RuntimeException(ex);
}
}
/**
* Returns all functions pointing to null.
*/
public final Set<String> getNullPointerFunctions() {
final Map<String, Long> table = toMap();
final Set<String> nullPointers = new LinkedHashSet<String>();
for (final Iterator<Map.Entry<String, Long>> it = table.entrySet().iterator(); it.hasNext();) {
final Map.Entry<String, Long> entry = it.next();
final long address = entry.getValue().longValue();
if(address == 0) {
nullPointers.add(entry.getKey());
}
}
return nullPointers;
}
@Override
public final String toString() {
return getClass().getName()+""+toMap();
}
private static class One2OneResolver implements FunctionAddressResolver {
@Override
public long resolve(final String name, final DynamicLookupHelper lookup) throws SecurityException {
return lookup.dynamicLookupFunction(name);
}
}
}