001/*
002 * BridJ - Dynamic and blazing-fast native interop for Java.
003 * http://bridj.googlecode.com/
004 *
005 * Copyright (c) 2010-2013, Olivier Chafik (http://ochafik.com/)
006 * All rights reserved.
007 *
008 * Redistribution and use in source and binary forms, with or without
009 * modification, are permitted provided that the following conditions are met:
010 * 
011 *     * Redistributions of source code must retain the above copyright
012 *       notice, this list of conditions and the following disclaimer.
013 *     * Redistributions in binary form must reproduce the above copyright
014 *       notice, this list of conditions and the following disclaimer in the
015 *       documentation and/or other materials provided with the distribution.
016 *     * Neither the name of Olivier Chafik nor the
017 *       names of its contributors may be used to endorse or promote products
018 *       derived from this software without specific prior written permission.
019 * 
020 * THIS SOFTWARE IS PROVIDED BY OLIVIER CHAFIK AND CONTRIBUTORS ``AS IS'' AND ANY
021 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
022 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
023 * DISCLAIMED. IN NO EVENT SHALL THE REGENTS AND CONTRIBUTORS BE LIABLE FOR ANY
024 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
025 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
026 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
027 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
028 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
029 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030 */
031package org.bridj.util;
032
033import java.io.IOException;
034import java.lang.reflect.Method;
035import java.lang.reflect.Modifier;
036import java.util.HashMap;
037import java.util.Map;
038import java.util.Set;
039import java.util.WeakHashMap;
040import java.util.regex.Pattern;
041import org.bridj.BridJ;
042import org.bridj.Platform;
043import org.bridj.Version;
044
045/**
046 *
047 * @author ochafik
048 */
049public class JNIUtils {
050
051    private static class NativeMethodsCache {
052
053        Map<String, String> signatures = new HashMap<String, String>();
054
055        public NativeMethodsCache(String internalClassName) throws IOException {
056            for (String[] sig : BytecodeAnalyzer.getNativeMethodSignatures(internalClassName, Platform.getClassLoader())) {
057                signatures.put(sig[1], sig[2]);
058            }
059        }
060
061        public String get(String name) {
062            return signatures.get(name);
063        }
064
065        public Set<String> getNames() {
066            return signatures.keySet();
067        }
068    }
069    private static Map<String, NativeMethodsCache> nativeMethodsCache = new WeakHashMap<String, NativeMethodsCache>();
070
071    private static synchronized NativeMethodsCache getNativeMethodsCache(String internalClassName) throws IOException {
072        NativeMethodsCache cache = nativeMethodsCache.get(internalClassName);
073        if (cache == null) {
074            nativeMethodsCache.put(internalClassName, cache = new NativeMethodsCache(internalClassName));
075        }
076        return cache;
077    }
078    private static final String bridjPackage =
079      BridJ.class.getPackage() == null ? "org.bridj" : BridJ.class.getPackage().getName();
080    private static final String bridjNormalPackagePrefix = bridjPackage.endsWith(Version.VERSION_SPECIFIC_SUB_PACKAGE) ? bridjPackage.substring(0, bridjPackage.length() - Version.VERSION_SPECIFIC_SUB_PACKAGE.length()) : bridjPackage + ".";
081    private static final String bridjVersionSpecificPackagePrefix = bridjPackage + ".";
082
083    static int findLastNonEscapeUnderscore(String s) {
084        int len = s.length(), i = len;
085        do {
086            i = s.lastIndexOf("_", i - 1);
087            if (i >= 0 && (i == len - 1 || !Character.isDigit(s.charAt(i + 1)))) {
088                return i;
089            }
090        } while (i > 0);
091        return -1;
092    }
093
094    public static String decodeVersionSpecificMethodNameClassAndSignature(String symbolName, Object[] nameAndSigArray) throws NoSuchMethodException, IOException {
095        return decodeMethodNameClassAndSignature(symbolName, nameAndSigArray, bridjNormalPackagePrefix, bridjVersionSpecificPackagePrefix);
096    }
097
098    static String decodeMethodNameClassAndSignature(String symbolName, Object[] nameAndSigArray, String normalClassPrefix, String replacementClassPrefix) throws NoSuchMethodException, IOException {
099        if (symbolName.startsWith("_")) {
100            symbolName = symbolName.substring(1);
101        }
102        if (symbolName.startsWith("Java_")) {
103            symbolName = symbolName.substring("Java_".length());
104        }
105
106        int i = findLastNonEscapeUnderscore(symbolName);
107        String className = symbolName.substring(0, i).replace('_', '.');
108        if (normalClassPrefix != null) {
109            if (className.startsWith(normalClassPrefix) && !className.startsWith(replacementClassPrefix)) {
110                className = replacementClassPrefix + className.substring(normalClassPrefix.length());
111            }
112        }
113        String methodName = symbolName.substring(i + 1).replaceAll("_1", "_");
114
115        NativeMethodsCache mc = getNativeMethodsCache(className.replace('.', '/'));
116        String sig = mc.get(methodName);
117        if (sig == null) {
118            throw new NoSuchMethodException("Method " + methodName + " not found in class " + className + " : known method names = " + StringUtils.implode(mc.getNames(), ", "));
119        }
120
121        nameAndSigArray[0] = methodName;
122        nameAndSigArray[1] = sig;
123
124        String internalClassName = className.replace('.', '/');
125        return internalClassName;//className;
126
127    }
128
129    public static String getNativeName(Class c) {
130        return c.getName().replace('.', '/');
131    }
132
133    public static String getNativeSignature(Method m) {
134        StringBuffer b = new StringBuffer();
135        b.append('(');
136        for (Class c : m.getParameterTypes()) {
137            b.append(getNativeSignature(c));
138        }
139        b.append(')');
140        b.append(getNativeSignature(m.getReturnType()));
141        return b.toString();
142    }
143
144    public static String getNativeSignature(Class c) {
145        if (c.isPrimitive()) {
146            if (c == int.class) {
147                return "I";
148            }
149            if (c == long.class) {
150                return "J";
151            }
152            if (c == short.class) {
153                return "S";
154            }
155            if (c == byte.class) {
156                return "B";
157            }
158            if (c == boolean.class) {
159                return "Z";
160            }
161            if (c == double.class) {
162                return "D";
163            }
164            if (c == float.class) {
165                return "F";
166            }
167            if (c == char.class) {
168                return "C";
169            }
170            if (c == void.class) {
171                return "V";
172            }
173
174            throw new RuntimeException("unexpected case");
175        }
176        if (c.isArray()) {
177            return "[" + getNativeSignature(c.getComponentType());
178        }
179        return "L" + getNativeName(c) + ";";
180    }
181}