View Javadoc

1   /*
2    * Copyright 2006-2010 Sam Adams <sea36 at users.sourceforge.net>
3    *
4    * This file is part of JNI-InChI.
5    *
6    * JNI-InChI is free software: you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as published
8    * by the Free Software Foundation, either version 3 of the License, or
9    * (at your option) any later version.
10   *
11   * JNI-InChI is distributed in the hope that it will be useful,
12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14   * GNU Lesser General Public License for more details.
15   *
16   * You should have received a copy of the GNU Lesser General Public License
17   * along with JNI-InChI.  If not, see <http://www.gnu.org/licenses/>.
18   */
19  package net.sf.jniinchi;
20  
21  import net.sf.jnati.NativeCodeException;
22  import net.sf.jnati.deploy.NativeLibraryLoader;
23  import org.apache.log4j.Logger;
24  
25  import java.util.List;
26  import java.util.StringTokenizer;
27  import java.util.concurrent.TimeUnit;
28  import java.util.concurrent.TimeoutException;
29  import java.util.concurrent.locks.Lock;
30  import java.util.concurrent.locks.ReentrantLock;
31  
32  /**
33   * <p>JNI Wrapper for International Chemical Identifier (InChI) C++ library.
34   *
35   * <p>This class is not intended to be used directly, but should be accessed
36   * through subclasses that read data formats and load them into the InChI
37   * data structures.
38   *
39   * <p>Subclasses should load data through the addAtom, addBond and addParity
40   * methods. Once the molecule is fully loaded then the generateInchi method
41   * should be called. Ideally this should all take place within the subclass's
42   * constructor. The public get methods will all return null until this has
43   * happened.
44   *
45   * <p>See <tt>inchi_api.h</tt>.
46   *
47   * @author Sam Adams
48   */
49  public class JniInchiWrapper {
50  
51      private static final Logger LOG = Logger.getLogger(JniInchiWrapper.class);
52  
53      private static final String ID = "jniinchi";
54      private static final String VERSION = "1.03_1";
55  
56      /**
57       * Maximum time to wait for a lock (in seconds).
58       */
59      private static final int MAX_LOCK_TIMEOUT = 15;
60  
61      /**
62       * Flag indicating windows or linux.
63       */
64      private static final boolean IS_WINDOWS = System.getProperty("os.name", "").toLowerCase().startsWith("windows");
65  
66      /**
67       * Switch character for passing options. / in windows, - on other systems.
68       */
69      static final String flagChar = IS_WINDOWS ? "/" : "-";
70  
71      /**
72       * Records whether native library has been loaded by system.
73       */
74      private static boolean libraryLoaded = false;
75  
76      private static JniInchiWrapper inchiWrapper;
77  
78      private static final Lock lock = new ReentrantLock(true); 
79  
80      /**
81       * Loads native library.
82       * @throws JniInchiException Library failed to load
83       */
84      public static synchronized void loadLibrary() throws LoadNativeLibraryException {
85          if (!libraryLoaded) {
86              try {
87                  NativeLibraryLoader.loadLibrary(ID, VERSION);
88  
89                  // Check expected version of native code loaded
90                  // Throws NativeCodeException if unable to make call / wrong version
91                  checkNativeCodeVersion();
92  
93                  // Everything is set up!
94                  libraryLoaded = true;
95              } catch (NativeCodeException ex) {
96                  System.err.println();
97                  System.err.println("Error loading JNI InChI native code.");
98                  System.err.println("You may need to compile the native code for your platform.");
99                  System.err.println("See http://jni-inchi.sourceforge.net for instructions.");
100                 System.err.println();
101                 throw new LoadNativeLibraryException(ex);
102             }
103         }
104     }
105 
106     /**
107      * Checks the expected native code version has been loaded.
108      * @throws NativeCodeException
109      */
110     private static void checkNativeCodeVersion() throws NativeCodeException {
111 
112         LOG.trace("Checking native code version");
113 
114         // Get native code version string
115         String nativeVersion;
116         try {
117             nativeVersion = JniInchiWrapper.LibInchiGetVersion();
118         } catch (UnsatisfiedLinkError e) {
119             LOG.error("Unable to get native code version", e);
120             throw new NativeCodeException("Unable get native code version", e);
121         }
122 
123         // Compare to expected version
124         if (!VERSION.equals(nativeVersion)) {
125             LOG.error("Native code version mismatch; expected " + VERSION + ", found " + nativeVersion);
126             throw new NativeCodeException("JNI InChI native code version mismatch: expected "
127                     + VERSION + ", found " + nativeVersion);
128         }
129 
130         LOG.trace("Expected native code version found: " + nativeVersion);
131     }
132 
133 
134     private static synchronized JniInchiWrapper getWrapper() throws LoadNativeLibraryException {
135         if (inchiWrapper == null) {
136             loadLibrary();
137             init();
138             inchiWrapper = new JniInchiWrapper();
139         }
140         return inchiWrapper;
141     }
142 
143     /**
144      * Constructor
145      */
146     private JniInchiWrapper() throws LoadNativeLibraryException {
147 
148     }
149 
150 
151     /**
152      * Checks and canonicalises options.
153      *
154      * @param ops  List of INCHI_OPTION
155      */
156     protected static String checkOptions(List<INCHI_OPTION> ops) throws JniInchiException {
157         if (ops == null) {
158             throw new IllegalArgumentException("Null options");
159         }
160         StringBuffer sbOptions = new StringBuffer();
161 
162         for (int i = 0; i < ops.size(); i++) {
163             Object op = ops.get(i);
164             if (op instanceof INCHI_OPTION) {
165                 sbOptions.append(flagChar + ((INCHI_OPTION) op).name() + " ");
166             } else {
167                 throw new JniInchiException("Unrecognised InChI option");
168             }
169         }
170 
171         return sbOptions.toString();
172     }
173 
174     /**
175      * Checks and canonicalises options.
176      *
177      * @param ops          Space delimited string of options to pass to InChI library.
178      *                     Each option may optionally be preceded by a command line
179      *                     switch (/ or -).
180      */
181     protected static String checkOptions(final String ops) throws JniInchiException {
182         if (ops == null) {
183             throw new IllegalArgumentException("Null options");
184         }
185         StringBuilder sbOptions = new StringBuilder();
186 
187         StringTokenizer tok = new StringTokenizer(ops);
188         while (tok.hasMoreTokens()) {
189             String op = tok.nextToken();
190 
191             if (op.startsWith("-") || op.startsWith("/")) {
192                 op = op.substring(1);
193             }
194 
195             INCHI_OPTION option = INCHI_OPTION.valueOfIgnoreCase(op);
196             if (option != null) {
197                 sbOptions.append(flagChar + option.name());
198                 if (tok.hasMoreTokens()) {
199                      sbOptions.append(" ");
200                 }
201             } else {
202                 throw new JniInchiException("Unrecognised InChI option");
203             }
204         }
205 
206         return sbOptions.toString();
207     }
208 
209 
210     /**
211      * <p>Generates the InChI for a chemical structure.</p>
212      *
213      * <p>If no InChI creation/stereo modification options are specified then a standard
214      * InChI is produced, otherwise the generated InChI will be a non-standard one.</p>
215      *
216      * <p><b>Valid options:</b></p>
217      * <pre>
218      *  Structure perception (compatible with stdInChI):
219      *    /NEWPSOFF   /DoNotAddH   /SNon
220      *  Stereo interpretation (lead to generation of non-standard InChI)
221      *    /SRel /SRac /SUCF /ChiralFlagON /ChiralFlagOFF
222      *  InChI creation options (lead to generation of non-standard InChI)
223      *    /SUU /SLUUD   /FixedH  /RecMet  /KET /15T
224      * </pre>
225      *
226      * <p><b>Other options:</b></p>
227      * <pre>
228      *  /AuxNone    Omit auxiliary information (default: Include)
229      *  /Wnumber    Set time-out per structure in seconds; W0 means unlimited
230      *              In InChI library the default value is unlimited
231      *  /OutputSDF  Output SDfile instead of InChI
232      *  /WarnOnEmptyStructure
233      *              Warn and produce empty InChI for empty structure
234      *  /SaveOpt    Save custom InChI creation options (non-standard InChI)
235      * </pre>
236      *
237      * @param input
238      * @return
239      * @throws JniInchiException
240      */
241     @SuppressWarnings("unchecked")
242     public static JniInchiOutput getInchi(JniInchiInput input) throws JniInchiException {
243         if (input == null) {
244             throw new IllegalArgumentException("Null input");
245         }
246         JniInchiWrapper wrapper = getWrapper();
247         wrapper.getLock();
248         try {
249             return wrapper.GetINCHI(input);
250         } finally {
251             lock.unlock();
252         }
253     }
254 
255 
256     /**
257      * <p>Calculates the Standard InChI string for a chemical structure.</p>
258      * <p>The only valid structure perception options are NEWPSOFF/DoNotAddH/SNon. In any other structural
259      * perception options are specified then the calculation will fail.</p>
260      * @param input
261      * @return
262      * @throws JniInchiException
263      */
264     @SuppressWarnings("unchecked")
265     public static JniInchiOutput getStdInchi(JniInchiInput input) throws JniInchiException {
266         if (input == null) {
267             throw new IllegalArgumentException("Null input");
268         }
269         JniInchiWrapper wrapper = getWrapper();
270         wrapper.getLock();
271         try {
272             return wrapper.GetStdINCHI(input);
273         } finally {
274             lock.unlock();
275         }
276     }
277 
278 
279     /**
280      * <p>Converts an InChI into an InChI for validation purposes (the same as the -InChI2InChI option).</p>
281      * <p>This method may also be used to filter out specific layers. For instance, /Snon would remove the
282      * stereochemical layer; Omitting /FixedH and/or /RecMet would remove Fixed-H or Reconnected layers.
283      * In order to keep all InChI layers use options string "/FixedH /RecMet"; option /InChI2InChI is not needed.</p>         
284      * @param input
285      * @return
286      * @throws JniInchiException
287      */
288     public static JniInchiOutput getInchiFromInchi(JniInchiInputInchi input) throws JniInchiException {
289         if (input == null) {
290             throw new IllegalArgumentException("Null input");
291         }
292         JniInchiWrapper wrapper = getWrapper();
293         wrapper.getLock();
294         try {
295             return wrapper.GetINCHIfromINCHI(input.getInchi(), input.getOptions());
296         } finally {
297             lock.unlock();
298         }
299     }
300 
301     /**
302      * Generated 0D structure from an InChI string.
303      * @param input
304      * @return
305      * @throws JniInchiException
306      */
307     public static JniInchiOutputStructure getStructureFromInchi(JniInchiInputInchi input) throws JniInchiException {
308         if (input == null) {
309             throw new IllegalArgumentException("Null input");
310         }
311         JniInchiWrapper wrapper = getWrapper();
312         wrapper.getLock();
313         try {
314             return wrapper.GetStructFromINCHI(input.getInchi(), input.getOptions());
315         } finally {
316             lock.unlock();
317         }
318     }
319 
320 
321     /**
322      * Calculates the InChIKey for an InChI string.
323      * @param inchi     source InChI string
324      * @return  InChIKey output
325      * @throws  JniInchiException
326      */
327     public static JniInchiOutputKey getInchiKey(final String inchi) throws JniInchiException {
328         if (inchi == null) {
329             throw new IllegalArgumentException("Null InChI");
330         }
331         JniInchiWrapper wrapper = getWrapper();
332         wrapper.getLock();
333         try {
334             return wrapper.GetINCHIKeyFromINCHI(inchi);
335         } finally {
336             lock.unlock();
337         }
338     }
339 
340 
341     /**
342      * Checks whether a string represents valid InChIKey.
343      * @param key
344      * @return
345      * @throws JniInchiException
346      */
347     public static INCHI_KEY_STATUS checkInchiKey(final String key) throws JniInchiException {
348         if (key == null) {
349             throw new IllegalArgumentException("Null InChI key");
350         }
351         JniInchiWrapper wrapper = getWrapper();
352         wrapper.getLock();
353         try {
354             int ret = wrapper.CheckINCHIKey(key);
355             INCHI_KEY_STATUS retStatus = INCHI_KEY_STATUS.getValue(ret);
356             if (retStatus == null) {
357                 throw new JniInchiException("Unknown return status: " + ret);
358             }
359             return retStatus;
360         } finally {
361             lock.unlock();
362         }
363     }
364 
365 
366     /**
367      * <p>Checks if the string represents valid InChI/standard InChI.</p>
368      *
369      * @param inchi  source InChI
370      * @param strict if <code>false</code>, just briefly check for proper layout (prefix, version, etc.) The result
371  *               may not be strict.
372  *               If <code>true</code>, try to perform InChI2InChI conversion and returns success if a resulting
373  *               InChI string exactly match source. The result may be 'false alarm' due to imperfectness of
374      */
375     public static INCHI_STATUS checkInchi(final String inchi, final boolean strict) throws JniInchiException {
376         if (inchi == null) {
377             throw new IllegalArgumentException("Null InChI");
378         }
379         JniInchiWrapper wrapper = getWrapper();
380         wrapper.getLock();
381         try {
382             int ret = wrapper.CheckINCHI(inchi, strict);
383             INCHI_STATUS retStatus = INCHI_STATUS.getValue(ret);
384             if (retStatus == null) {
385                 throw new JniInchiException("Unknown return status: " + ret);
386             }
387             return retStatus;
388         } finally {
389             lock.unlock();
390         }
391     }
392 
393     public static JniInchiInputData getInputFromAuxInfo(String auxInfo) throws JniInchiException {
394         if (auxInfo == null) {
395             throw new IllegalArgumentException("Null AuxInfo");
396         }
397         JniInchiWrapper wrapper = getWrapper();
398         wrapper.getLock();
399         try {
400             return wrapper.GetINCHIInputFromAuxInfo(auxInfo, false, false);
401         } finally {
402             lock.unlock();
403         }
404     }
405 
406 
407     private static synchronized void getLock() throws JniInchiException {
408         try {
409             if (!lock.tryLock(MAX_LOCK_TIMEOUT, TimeUnit.SECONDS)) {
410                 throw new TimeoutException("Unable to get lock");
411             }
412         } catch (TimeoutException ex) {
413             throw new JniInchiException(ex);
414         } catch (InterruptedException ex) {
415             throw new JniInchiException(ex);
416         }
417     }
418 
419 
420 
421     protected native static String LibInchiGetVersion();
422 
423     private native static void init();
424 
425 
426     private native JniInchiOutput GetINCHI(JniInchiInput input);
427 
428     private native JniInchiOutput GetStdINCHI(JniInchiInput input);
429 
430     private native JniInchiOutput GetINCHIfromINCHI(String inchi, String options);
431 
432     private native JniInchiOutputStructure GetStructFromINCHI(String inchi, String options);
433 
434     private native JniInchiOutputKey GetINCHIKeyFromINCHI(String inchi);
435 
436     private native JniInchiOutputKey GetStdINCHIKeyFromStdINCHI(String inchi);
437 
438     private native int CheckINCHIKey(String key);
439 
440     private native int CheckINCHI(String inchi, boolean strict);
441 
442     private native JniInchiInputData GetINCHIInputFromAuxInfo(String auxInfo, boolean bDoNotAddH, boolean bDiffUnkUndfStereo);
443 
444 }