001/*
002 * Class NativeUtils is published under the The MIT License:
003 *
004 * Copyright (c) 2012 Adam Heinrich <adam@adamh.cz>
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package net.gdface.utils;
025
026import java.io.File;
027import java.io.FileNotFoundException;
028import java.io.FileOutputStream;
029import java.io.IOException;
030import java.io.InputStream;
031import java.io.OutputStream;
032import java.net.URL;
033
034/**
035 * 从jar包中加载指定路径下的动态库<br>
036 * 
037 * @see <a href="http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar">http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar</a>
038 * @see <a href="https://github.com/adamheinrich/native-utils">https://github.com/adamheinrich/native-utils</a>
039 *
040 */
041public class NativeUtils {
042    // buffer size used for reading and writing
043    private static final int BUFFER_SIZE = 8192;
044    public static final String NATIVE_FOLDER_PATH_PREFIX = "nativeutils";
045
046    /**
047     * Temporary directory which will contain the DLLs.
048     */
049    private static File temporaryDir;
050
051    /**
052     * Private constructor - this class will never be instanced
053     */
054    private NativeUtils() {
055    }
056
057    /**
058     * Loads library from current JAR archive
059     * @see #copyToTempFromJar(String, Class) 
060     */
061    public static synchronized void loadLibraryFromJar(String path, Class<?> loadClass) throws IOException {
062        File temp = copyToTempFromJar(path,loadClass);
063        System.load(temp.getAbsolutePath());
064    }
065    /**
066     * copy file from current JAR archive to system temporary directory
067     * 
068     * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after
069     * exiting.
070     * Method uses String as filename because the pathname is "abstract", not system-dependent.
071     * 
072     * @param path The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext
073     * @param loadClass class that provide {@link ClassLoader} to load library file by input stream,if null, current class instead.
074     * @throws IOException If temporary file creation or read/write operation fails
075     * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters
076     * (restriction of {@link File#createTempFile(java.lang.String, java.lang.String)}).
077     * @throws FileNotFoundException If the file could not be found inside the JAR.
078     */
079    public static synchronized File copyToTempFromJar(String path, Class<?> loadClass) throws IOException {
080         
081        if (null == path || !path.startsWith("/")) {
082            throw new IllegalArgumentException("The path has to be absolute (start with '/').");
083        }
084 
085        // Prepare temporary file
086        if (temporaryDir == null) {
087            temporaryDir = createTempDirectory(NATIVE_FOLDER_PATH_PREFIX);
088            temporaryDir.deleteOnExit();
089        }
090
091        File temp = new File(temporaryDir, path);
092        Class<?> clazz = loadClass == null ? NativeUtils.class  : loadClass;
093        InputStream is = clazz.getResourceAsStream(path);
094        try{
095            copy(is, temp);
096            temp.deleteOnExit();
097            return temp;
098        } catch (IOException e) {
099            temp.delete();
100            throw e;
101        } catch (NullPointerException e) {
102            temp.delete();
103            throw new FileNotFoundException("File " + path + " was not found inside JAR.");
104        } finally {
105                        is.close();
106                }       
107    }
108    /**
109         * @see {@link #copyToTempFromJar(String, Class)}
110     */
111    public static File copyToTempFromJar(String path) throws IOException {
112        return copyToTempFromJar(path, null);
113    }
114    /**
115     * 从jar包中加载指定的动态库
116     * @param path
117     * @throws IOException
118     * @see {@link #loadLibraryFromJar(String, Class)}
119     */
120    public static void loadLibraryFromJar(String path) throws IOException {
121        loadLibraryFromJar(path,null);
122    }
123    private static File createTempDirectory(String prefix) throws IOException {
124        String tempDir = System.getProperty("java.io.tmpdir");
125        File generatedDir = new File(tempDir, prefix + System.nanoTime());
126        if (!generatedDir.mkdir())
127            throw new IOException("Failed to create temp directory " + generatedDir.getName());
128        
129        return generatedDir;
130    }
131    /**
132     * 从jar包中位置'/lib/os_prefix'加载指定名字的动态库<br>
133     * os_prefix由{@link Platform#getNativeLibraryResourcePrefix()}计算
134     * @param name 库名
135     * @throws IOException
136     * @see {@link #loadLibraryFromJar(String, Class)}
137     */
138    public static void loadFromJar(String name) throws IOException {
139        String prefix = Platform.getNativeLibraryResourcePrefix();
140        loadLibraryFromJar("/lib/" + prefix +"/" + System.mapLibraryName(name));
141    }
142    /**
143     * 将动态库解析为内部资源路径
144     * @param name
145     * @return
146     * @throws IOException
147     */
148    private static String resolveName(String name) throws IOException {
149        String prefix = Platform.getNativeLibraryResourcePrefix();
150        return "/lib/" + prefix +"/" + System.mapLibraryName(name);
151    }
152        public static File getTemporaryDir() {
153                return temporaryDir;
154        }
155    private static long copy(InputStream in, File target) throws IOException  {
156        target.delete();
157                File parent = target.getParentFile();
158        if (parent != null){
159                parent.mkdirs();
160        }
161        OutputStream out = new FileOutputStream(target);
162        // do the copy
163        try {
164                return copy(in, out);
165        }finally{
166                out.close();
167        }
168    }
169    /**
170     * Reads all bytes from an input stream and writes them to an output stream.
171     */
172    private static long copy(InputStream source, OutputStream dest)
173        throws IOException
174    {
175        long nread = 0L;
176        byte[] buf = new byte[BUFFER_SIZE];
177        int n;
178        while ((n = source.read(buf)) > 0) {
179            dest.write(buf, 0, n);
180            nread += n;
181        }
182        return nread;
183    }
184        /**
185         * 从资源中加载动态库
186         * @param name 动态库名,参见 {@link System#loadLibrary(String)}
187         * @param loaderClass 
188         * @throws IOException
189         */
190        public static void loadLibraryFromResource(String name, Class<?> loaderClass) throws IOException{
191                if(Platform.isAndroid()){
192                        System.loadLibrary(name);
193                        return;
194                }
195                if(null == loaderClass){
196                        loaderClass = NativeUtils.class;
197                }
198                URL url = loaderClass.getResource(resolveName(name));
199                Assert.notNull(url, "url","not found library");
200                if(url.getProtocol().equals("file")){
201                        System.load(url.getPath());
202                        return ;
203                }
204                if(url.getProtocol().equals("jar")){
205                        loadFromJar(name);
206                        return;
207                }
208                throw new UnsupportedOperationException();
209        }
210}