001/* [{
002Copyright 2007, 2008 Nicolas Carranza <nicarran at gmail.com>
003
004This file is part of jpen.
005
006jpen is free software: you can redistribute it and/or modify
007it under the terms of the GNU Lesser General Public License as published by
008the Free Software Foundation, either version 3 of the License,
009or (at your option) any later version.
010
011jpen is distributed in the hope that it will be useful,
012but WITHOUT ANY WARRANTY; without even the implied warranty of
013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
014GNU Lesser General Public License for more details.
015
016You should have received a copy of the GNU Lesser General Public License
017along with jpen.  If not, see <http://www.gnu.org/licenses/>.
018}] */
019package jpen.provider;
020
021import java.security.AccessController;
022import java.security.PrivilegedAction;
023import java.util.Arrays;
024import java.util.Collection;
025import java.util.HashMap;
026import java.util.logging.Logger;
027import java.util.Map;
028import java.util.prefs.Preferences;
029import jpen.internal.BuildInfo;
030import jpen.PenManager;
031
032public class NativeLibraryLoader{
033        private static final Logger L=Logger.getLogger(NativeLibraryLoader.class.getName());
034        // static{L.setLevel(Level.ALL);        }
035        private static String PREFERENCE_KEY$ARCHITECTURE="NativeLibraryLoader.architecture";
036
037        private final Map<String, Collection<String>> dataModelToArchitectures=new HashMap<String, Collection<String>>();
038        private boolean loaded;
039        public final int nativeVersion;
040        
041        public NativeLibraryLoader(int nativeVersion){
042                this(new String[]{""}, nativeVersion);
043        }
044        
045        public NativeLibraryLoader(String[] architectures, int nativeVersion){
046                this(architectures, architectures, nativeVersion);
047        }
048
049        public NativeLibraryLoader(){
050                this(new String[]{""});
051        }
052        
053        public NativeLibraryLoader(String[] architectures){
054                this(architectures, architectures);
055        }
056        
057        public NativeLibraryLoader(String[] architectures32, String[] architectures64){
058                this(architectures32, architectures64, 0);
059        }
060        
061        public NativeLibraryLoader(String[] architectures32, String[] architectures64, int nativeVersion){
062                dataModelToArchitectures.put("32", Arrays.asList(architectures32));
063                dataModelToArchitectures.put("64", Arrays.asList(architectures64));
064                this.nativeVersion=nativeVersion;
065        }
066
067        public synchronized void load(){
068                if(!loaded){
069                        L.finest("v");
070                        Throwable loadExceptionCause=doLoad();
071                        loaded=true;
072                        if(loadExceptionCause!=null){
073                                L.info("no suitable JNI library found");
074                                throw new LoadException(loadExceptionCause);
075                        }
076                        L.finest("^");
077                }
078        }
079
080        /**
081        @return the last load exception or {@code null} if one matching library was found and loaded.
082        */
083        private Throwable doLoad(){
084                String preferredArchitecture=getPreferredArchitecture();
085                if(preferredArchitecture!=null){
086                        try{
087                                loadLibrary(preferredArchitecture, nativeVersion);
088                                return null;
089                        }catch(Throwable t){
090                                setPreferredArchitecture(null);
091                        }
092                }
093                
094                Throwable loadExceptionCause=null;
095                String dataModel=getJavaVMDataModel();
096                Collection<String> architectures=dataModelToArchitectures.get(dataModel);
097                if(architectures==null)
098                        throw new IllegalStateException("Unsupported data model: "+dataModel);
099                for(String architecture: architectures){
100                        try{
101                                loadLibrary(architecture, nativeVersion);
102                                setPreferredArchitecture(architecture);
103                                loadExceptionCause=null;
104                                break;
105                        }catch(Throwable t){
106                                setPreferredArchitecture(null);
107                                loadExceptionCause=t;
108                        }
109                }
110                return loadExceptionCause;
111        }
112
113        public static class LoadException
114                extends RuntimeException{
115                LoadException(Throwable cause){
116                        super(cause);
117                }
118        }
119
120        private static String getJavaVMDataModel(){
121                String dataModel=AccessController.doPrivileged(new PrivilegedAction<String>() {
122                            //@Override
123                            public String run() {
124                                    return System.getProperty("sun.arch.data.model");
125                            }
126                    });
127                return dataModel==null? "32": dataModel;
128        }
129
130        private String getPreferredArchitecture(){
131                return AccessController.doPrivileged(new PrivilegedAction<String>(){
132                               //@Override
133                               public String run(){
134                                       String override=System.getProperty("jpen.provider.architecture");
135                                       if(override!=null)
136                                               return override;
137                                       Preferences preferences=Preferences.userNodeForPackage(NativeLibraryLoader.class);
138                                       return preferences.get(PREFERENCE_KEY$ARCHITECTURE, null);
139                               }
140                       });
141        }
142
143        private static void setPreferredArchitecture(final String architecture){
144                AccessController.doPrivileged(new PrivilegedAction<Object>(){
145                            //@Override
146                            public String run(){
147                                    Preferences preferences=Preferences.userNodeForPackage(NativeLibraryLoader.class);
148                                    if(architecture==null){
149                                            preferences.remove(PREFERENCE_KEY$ARCHITECTURE);
150                                    }
151                                    else{
152                                            preferences.put(PREFERENCE_KEY$ARCHITECTURE, architecture);
153                                            L.info("preferred architecture set");
154                                    }
155                                    return null;
156                            }
157                    });
158        }
159        
160        public static final void loadLibrary(final String architecture, final int nativeVersion) {
161                AccessController.doPrivileged(new PrivilegedAction<Object>() {
162                            final String jniLibName=getJniLibName(architecture, nativeVersion);
163                            //@Override
164                            public Object run() {
165                                    try{
166                                            L.info("loading JPen "+PenManager.getJPenFullVersion()+" JNI library: "+jniLibName+" ...");
167                                            System.loadLibrary(jniLibName);
168                                            L.info(jniLibName+" loaded");
169                                            return null;
170                                    }catch(RuntimeException ex){
171                                            logOnFail();
172                                            throw ex;
173                                    }catch(Error ex){
174                                            logOnFail();
175                                            throw ex;
176                                    }
177                            }
178                            private void logOnFail(){
179                                    L.info(jniLibName+" couldn't be loaded");
180                            }
181                    });
182        }
183        
184        private static final String getJniLibName(String architecture, int nativeVersion) {
185                StringBuilder jniLibName=new StringBuilder(64);
186                jniLibName.append(BuildInfo.getModuleId());
187                jniLibName.append("-");
188                jniLibName.append(BuildInfo.getVersion());
189                if(nativeVersion!=0){ // backwards compatibility
190                        jniLibName.append("-");
191                        jniLibName.append(nativeVersion);
192                }
193                if(architecture!=null && architecture.trim().length()!=0){
194                        jniLibName.append("-");
195                        jniLibName.append(architecture);
196                }
197                return jniLibName.toString();
198        }
199}