1   /**
2    * Distribution License:
3    * JSword is free software; you can redistribute it and/or modify it under
4    * the terms of the GNU Lesser General Public License, version 2.1 or later
5    * as published by the Free Software Foundation. This program is distributed
6    * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
7    * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
8    * See the GNU Lesser General Public License for more details.
9    *
10   * The License is available on the internet at:
11   *       http://www.gnu.org/copyleft/lgpl.html
12   * or by writing to:
13   *      Free Software Foundation, Inc.
14   *      59 Temple Place - Suite 330
15   *      Boston, MA 02111-1307, USA
16   *
17   * Copyright: 2005-2013
18   *     The copyright to this program is held by it's authors.
19   *
20   */
21  package org.crosswire.common.util;
22  
23  import java.io.BufferedReader;
24  import java.io.File;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.FileOutputStream;
28  import java.io.FilenameFilter;
29  import java.io.IOException;
30  import java.io.InputStream;
31  import java.io.InputStreamReader;
32  import java.io.OutputStream;
33  import java.net.JarURLConnection;
34  import java.net.MalformedURLException;
35  import java.net.URI;
36  import java.net.URISyntaxException;
37  import java.net.URL;
38  import java.net.URLConnection;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.Date;
42  import java.util.List;
43  import java.util.jar.JarEntry;
44  
45  import org.crosswire.jsword.JSMsg;
46  import org.crosswire.jsword.JSOtherMsg;
47  import org.slf4j.LoggerFactory;
48  
49  /**
50   * The NetUtil class looks after general utility stuff around the java.net
51   * package.
52   * 
53   * @see gnu.lgpl.License for license details.<br>
54   *      The copyright to this program is held by it's authors.
55   * @author Joe Walker [joe at eireneh dot com]
56   * @author Mark Goodwin [goodwinster at gmail dot com]
57   * @author DM Smith
58   */
59  public final class NetUtil {
60      /**
61       * Basic constructor - ensure that we can't be instantiated
62       */
63      private NetUtil() {
64      }
65  
66      /**
67       * Constant for the file: protocol or scheme
68       */
69      public static final String PROTOCOL_FILE = "file";
70  
71      /**
72       * Constant for the http: protocol or scheme
73       */
74      public static final String PROTOCOL_HTTP = "http";
75  
76      /**
77       * Constant for the ftp: protocol or scheme
78       */
79      public static final String PROTOCOL_FTP = "ftp";
80  
81      /**
82       * Constant for the jar: protocol or scheme
83       */
84      public static final String PROTOCOL_JAR = "jar";
85  
86      /**
87       * For directory listings
88       */
89      public static final String INDEX_FILE = "index.txt";
90  
91      /**
92       * URL/URI separator
93       */
94      public static final String SEPARATOR = "/";
95  
96      /**
97       * Separating the username from the rest of the URL/URI
98       */
99      public static final String AUTH_SEPERATOR_USERNAME = "@";
100 
101     /**
102      * Separating the password from the username
103      */
104     public static final String AUTH_SEPERATOR_PASSWORD = ":";
105 
106     /**
107      * The temporary suffix, used when a temporary file is needed in the
108      * system's temporary directory.
109      */
110     private static final String TEMP_SUFFIX = "tmp";
111 
112     public static URI copy(URI uri) {
113         try {
114             return new URI(uri.toString());
115         } catch (URISyntaxException e) {
116             assert false : e;
117             return null;
118         }
119     }
120 
121     /**
122      * If the directory does not exist, create it. Note this currently only
123      * works with file: type URIs
124      * 
125      * @param orig
126      *            The directory URI to create
127      */
128     public static void makeDirectory(URI orig) throws MalformedURLException {
129         checkFileURI(orig);
130 
131         File file = new File(orig.getPath());
132 
133         // If it is a file, except
134         if (file.isFile()) {
135             // TRANSLATOR: Error condition: A directory was expected, but a file was found. {0} is a placeholder for the file.
136             throw new MalformedURLException(JSMsg.gettext("The URL {0} is a file.", orig));
137         }
138 
139         // Is it already a directory ?
140         if (!file.isDirectory()) {
141             // Create the directory and make sure it worked.
142             if (!file.mkdirs()) {
143                 // TRANSLATOR: Error condition: A directory could not be created. {0} is a placeholder for the directory
144                 throw new MalformedURLException(JSMsg.gettext("The URL {0} could not be created as a directory.", orig));
145             }
146         }
147     }
148 
149     /**
150      * If the file does not exist, create it. Note this currently only works
151      * with file: type URIs
152      * 
153      * @param orig
154      *            The file URI to create
155      */
156     public static void makeFile(URI orig) throws MalformedURLException, IOException {
157         checkFileURI(orig);
158 
159         File file = new File(orig.getPath());
160 
161         // If it is a file, except
162         if (file.isDirectory()) {
163             // TRANSLATOR: Error condition: A file was expected, but a directory was found. {0} is a placeholder for the directory.
164             throw new MalformedURLException(JSMsg.gettext("The URL {0} is a directory.", orig));
165         }
166 
167         // Is it already a directory ?
168         if (!file.isFile()) {
169             FileOutputStream fout = new FileOutputStream(file);
170             fout.close();
171 
172             // Did that work?
173             if (!file.isFile()) {
174                 // TRANSLATOR: Error condition: A file could not be created. {0} is a placeholder for the file
175                 throw new MalformedURLException(JSMsg.gettext("The URL {0} could not be created as a file.", orig));
176             }
177         }
178     }
179 
180     /**
181      * If there is a file at the other end of this URI return true.
182      * 
183      * @param uri
184      *            The URI to check
185      * @return true if the URI points at a file
186      */
187     public static boolean isFile(URI uri) {
188         if (uri.getScheme().equals(PROTOCOL_FILE)) {
189             return new File(uri.getPath()).isFile();
190         }
191 
192         try {
193             // This will throw if the resource does not exist
194             uri.toURL().openStream().close();
195             return true;
196         } catch (IOException ex) {
197             // the resource does not exist!
198             return false;
199         }
200     }
201 
202     /**
203      * If there is a directory at the other end of this URI return true. Note
204      * non file: type URI will always return false
205      * 
206      * @param orig
207      *            The URI to check
208      * @return true if the URI points at a file: directory
209      */
210     public static boolean isDirectory(URI orig) {
211         if (!orig.getScheme().equals(PROTOCOL_FILE)) {
212             return false;
213         }
214 
215         return new File(orig.getPath()).isDirectory();
216     }
217 
218     /**
219      * If there is a writable directory or file at the other end of this URI
220      * return true. Note non file: type URIs will always return false
221      * 
222      * @param orig
223      *            The URI to check
224      * @return true if the URI points at a writable file or directory
225      */
226     public static boolean canWrite(URI orig) {
227         if (!orig.getScheme().equals(PROTOCOL_FILE)) {
228             return false;
229         }
230 
231         return new File(orig.getPath()).canWrite();
232     }
233 
234     /**
235      * If there is a readable directory or file at the other end of this URI
236      * return true. Note non file: type URIs will always return false
237      * 
238      * @param orig
239      *            The URI to check
240      * @return true if the URI points at a readable file or directory
241      */
242     public static boolean canRead(URI orig) {
243         if (!orig.getScheme().equals(PROTOCOL_FILE)) {
244             return false;
245         }
246 
247         return new File(orig.getPath()).canRead();
248     }
249 
250     /**
251      * Move a URI from one place to another. Currently this only works for file:
252      * URIs, however the interface should not need to change to handle more
253      * complex URIs
254      * 
255      * @param oldUri
256      *            The URI to move
257      * @param newUri
258      *            The destination URI
259      */
260     public static boolean move(URI oldUri, URI newUri) throws IOException {
261         checkFileURI(oldUri);
262         checkFileURI(newUri);
263 
264         File oldFile = new File(oldUri.getPath());
265         File newFile = new File(newUri.getPath());
266         return oldFile.renameTo(newFile);
267     }
268 
269     /**
270      * Delete a URI. Currently this only works for file: URIs, however the
271      * interface should not need to change to handle more complex URIs
272      * 
273      * @param orig
274      *            The URI to delete
275      */
276     public static boolean delete(URI orig) throws IOException {
277         checkFileURI(orig);
278 
279         return new File(orig.getPath()).delete();
280     }
281 
282     /**
283      * Return a File from the URI either by extracting from a file: URI or by
284      * downloading to a temp dir first
285      * 
286      * @param uri
287      *            The original URI to the file.
288      * @return The URI as a file
289      * @throws IOException
290      */
291     public static File getAsFile(URI uri) throws IOException {
292         // if the URI is already a file URI, return it
293         if (uri.getScheme().equals(PROTOCOL_FILE)) {
294             return new File(uri.getPath());
295         }
296         String hashString = (uri.toString().hashCode() + "").replace('-', 'm');
297 
298         // get the location of the tempWorkingDir
299         File workingDir = getURICacheDir();
300         File workingFile = null;
301 
302         if (workingDir != null && workingDir.isDirectory()) {
303             workingFile = new File(workingDir, hashString);
304         } else {
305             // If there's no working dir, we just use temp...
306             workingFile = File.createTempFile(hashString, TEMP_SUFFIX);
307         }
308         workingFile.deleteOnExit();
309 
310         // copy the contents of the URI to the file
311         OutputStream output = null;
312         InputStream input = null;
313         try {
314             output = new FileOutputStream(workingFile);
315             input = uri.toURL().openStream();
316             byte[] data = new byte[512];
317             for (int read = 0; read != -1; read = input.read(data)) {
318                 output.write(data, 0, read);
319             }
320         } finally {
321             try {
322                 if (input != null) {
323                     input.close();
324                 }
325             } finally {
326                 if (output != null) {
327                     output.close();
328                 }
329             }
330         }
331 
332         // return the new file in URI form
333         return workingFile;
334     }
335 
336     /**
337      * Utility to strip a string from the end of a URI.
338      * 
339      * @param orig
340      *            The URI to strip
341      * @param strip
342      *            The text to strip from the end of the URI
343      * @return The stripped URI
344      * @exception MalformedURLException
345      *                If the URI does not end in the given text
346      */
347     public static URI shortenURI(URI orig, String strip) throws MalformedURLException {
348         String file = orig.getPath();
349         char lastChar = file.charAt(file.length() - 1);
350         if (isSeparator(lastChar)) {
351             file = file.substring(0, file.length() - 1);
352         }
353 
354         String test = file.substring(file.length() - strip.length());
355         if (!test.equals(strip)) {
356             throw new MalformedURLException(JSOtherMsg.lookupText("The URL {0} does not end in {1}.", orig, strip));
357         }
358 
359         String newFile = file.substring(0, file.length() - strip.length());
360 
361         try {
362             return new URI(orig.getScheme(), orig.getUserInfo(), orig.getHost(), orig.getPort(), newFile, "",
363                     "");
364         } catch (URISyntaxException e) {
365             throw new MalformedURLException(JSOtherMsg.lookupText("The URL {0} does not end in {1}.", orig, strip));
366         }
367     }
368 
369     /**
370      * Utility to add a string to the end of a URI.
371      * 
372      * @param orig
373      *            The URI to lengthen
374      * @param anExtra
375      *            The text to add to the end of the URI
376      * @return The lengthened URI
377      */
378     public static URI lengthenURI(URI orig, String anExtra) {
379         String extra = anExtra;
380         try {
381             StringBuilder path = new StringBuilder(orig.getPath());
382             char lastChar = path.charAt(path.length() - 1);
383             char firstChar = extra.charAt(0);
384             if (isSeparator(firstChar)) {
385                 if (isSeparator(lastChar)) {
386                     path.append(extra.substring(1));
387                 } else {
388                     path.append(extra);
389                 }
390             } else {
391                 if (!isSeparator(lastChar)) {
392                     path.append(SEPARATOR);
393                 }
394                 path.append(extra);
395             }
396 
397             return new URI(orig.getScheme(), orig.getUserInfo(), orig.getHost(), orig.getPort(), path.toString(), orig.getQuery(), orig.getFragment());
398         } catch (URISyntaxException ex) {
399             return null;
400         }
401     }
402 
403     private static boolean isSeparator(char c) {
404         return c == '/' || c == '\\';
405     }
406 
407     /**
408      * Attempt to obtain an InputStream from a URI. If the URI is a file scheme
409      * then just open it directly. Otherwise, call uri.toURL().openStream().
410      * 
411      * @param uri
412      *            The URI to attempt to read from
413      * @return An InputStream connection
414      */
415     public static InputStream getInputStream(URI uri) throws IOException {
416         // We favor the FileOutputStream
417         if (uri.getScheme().equals(PROTOCOL_FILE)) {
418             return new FileInputStream(uri.getPath());
419         }
420         return uri.toURL().openStream();
421     }
422 
423     /**
424      * Attempt to obtain an OutputStream from a URI. The simple case will open
425      * it if it is local. Otherwise, it will call
426      * uri.toURL().openConnection().getOutputStream(), however in some JVMs (MS
427      * at least this fails where new FileOutputStream(url) works.
428      * 
429      * @param uri
430      *            The URI to attempt to write to
431      * @return An OutputStream connection
432      */
433     public static OutputStream getOutputStream(URI uri) throws IOException {
434         return getOutputStream(uri, false);
435     }
436 
437     /**
438      * Attempt to obtain an OutputStream from a URI. The simple case will open
439      * it if it is local. Otherwise, it will call
440      * uri.toURL().openConnection().getOutputStream(), however in some JVMs (MS
441      * at least this fails where new FileOutputStream(url) works.
442      * 
443      * @param uri
444      *            The URI to attempt to write to
445      * @param append
446      *            Do we write to the end of the file instead of the beginning
447      * @return An OutputStream connection
448      */
449     public static OutputStream getOutputStream(URI uri, boolean append) throws IOException {
450         // We favor the FileOutputStream method here because append
451         // is not well defined for the openConnection method
452         if (uri.getScheme().equals(PROTOCOL_FILE)) {
453             return new FileOutputStream(uri.getPath(), append);
454         }
455         URLConnection cnx = uri.toURL().openConnection();
456         cnx.setDoOutput(true);
457         return cnx.getOutputStream();
458     }
459 
460     /**
461      * List the items available assuming that this URI points to a directory.
462      * <p>
463      * There are 2 methods of calculating the answer - if the URI is a file: URI
464      * then we can just use File.list(), otherwise we ask for a file inside the
465      * directory called index.txt and assume the directories contents to be
466      * listed one per line.
467      * <p>
468      * If the URI is a file: URI then we execute both methods and warn if there
469      * is a difference, but returning the values from the index.txt method.
470      */
471     public static String[] list(URI uri, URIFilter filter) throws MalformedURLException, IOException {
472         // We should probably cache this in some way? This is going
473         // to get very slow calling this often across a network
474         String[] reply = {};
475         try {
476             URI search = NetUtil.lengthenURI(uri, INDEX_FILE);
477             reply = listByIndexFile(search, filter);
478         } catch (FileNotFoundException ex) {
479             // So the index file was not found - this isn't going to work over
480             // JNLP or other systems that can't use file: URIs. But it is silly
481             // to get to picky so if there is a solution using file: then just
482             // print a warning and carry on.
483             log.warn("index file for " + uri.toString() + " was not found.");
484             if (uri.getScheme().equals(PROTOCOL_FILE)) {
485                 return listByFile(uri, filter);
486             }
487         }
488 
489         // if we can - check that the index file is up to date.
490         if (uri.getScheme().equals(PROTOCOL_FILE)) {
491             String[] files = listByFile(uri, filter);
492 
493             // Check that the answers are the same
494             if (files.length != reply.length) {
495                 log.warn("index file for {} has incorrect number of entries.", uri.toString());
496             } else {
497                 List<String> list = Arrays.asList(files);
498                 for (int i = 0; i < files.length; i++) {
499                     if (!list.contains(files[i])) {
500                         log.warn("file: based index found {} but this was not found using index file.", files[i]);
501                     }
502                 }
503             }
504         }
505 
506         return reply;
507     }
508 
509     /**
510      * List all the files specified by the index file passed in.
511      * 
512      * @return String[] Matching results.
513      */
514     public static String[] listByFile(URI uri, URIFilter filter) throws MalformedURLException {
515         File fdir = new File(uri.getPath());
516         if (!fdir.isDirectory()) {
517             // TRANSLATOR: Error condition: A directory was expected, but a file was found. {0} is a placeholder for the file.
518             throw new MalformedURLException(JSMsg.gettext("The URL {0} is not a directory", uri.toString()));
519         }
520 
521         return fdir.list(new URIFilterFilenameFilter(filter));
522     }
523 
524     /**
525      * List all the strings specified by the index file passed in. To be
526      * acceptable it must be a non-0 length string, not commented with #, and
527      * not the index file itself.
528      * 
529      * @return String[] Matching results.
530      * @throws FileNotFoundException
531      */
532     public static String[] listByIndexFile(URI index) throws IOException {
533         return listByIndexFile(index, new DefaultURIFilter());
534     }
535 
536     /**
537      * List all the files specified by the index file passed in.
538      * <p>Each line is pre-processed:</p>
539      * <ul>
540      * <li>Ignore comments (# to end of line)</li>
541      * <li>Trim spaces from line.</li>
542      * <li>Ignore blank lines.</li>
543      * 
544      * To be acceptable it:
545      * <ul>
546      * <li> cannot be the index file itself</li>
547      * <li> and must acceptable by the filter.</li>
548      * </ul>
549      * 
550      * @return String[] Matching results.
551      * @throws FileNotFoundException
552      */
553     public static String[] listByIndexFile(URI index, URIFilter filter) throws IOException {
554         InputStream in = null;
555         BufferedReader din = null;
556         try {
557             in = NetUtil.getInputStream(index);
558             // Quiet Android from complaining about using the default BufferReader buffer size.
559             // The actual buffer size is undocumented. So this is a good idea any way.
560             din = new BufferedReader(new InputStreamReader(in), 8192);
561 
562             // We still need to do the filtering
563             List<String> list = new ArrayList<String>();
564 
565             while (true) {
566                 String line = din.readLine();
567 
568                 if (line == null) {
569                     break;
570                 }
571 
572                 String name = line;
573 
574                 // Strip comments from the line
575                 int len = name.length();
576                 int commentPos;
577                 for (commentPos = 0; commentPos < len && name.charAt(commentPos) != '#'; ++commentPos) {
578                     continue; // test does the work
579                 }
580 
581                 if (commentPos < len) {
582                     name = name.substring(0, commentPos);
583                 }
584 
585                 // we need to trim extraneous whitespace on the line
586                 name = name.trim();
587 
588                 // Is it acceptable?
589                 if (name.length() > 0 && !name.equals(INDEX_FILE) && filter.accept(name)) {
590                     list.add(name);
591                 }
592             }
593 
594             return list.toArray(new String[list.size()]);
595         } finally {
596             IOUtil.close(din);
597             IOUtil.close(in);
598         }
599     }
600 
601     /**
602      * Load up properties given by a URI.
603      * 
604      * @param uri
605      *            the location of the properties
606      * @return the properties given by the URI
607      * @throws IOException
608      */
609     public static PropertyMap loadProperties(URI uri) throws IOException {
610         InputStream is = null;
611         try {
612             is = NetUtil.getInputStream(uri);
613             PropertyMap prop = new PropertyMap();
614             prop.load(is);
615             is.close();
616             return prop;
617         } finally {
618             IOUtil.close(is);
619         }
620     }
621 
622     /**
623      * Store the properties at the location given by the uri using the supplied
624      * title.
625      * 
626      * @param properties
627      *            the properties to store
628      * @param uri
629      *            the location of the store
630      * @param title
631      *            the label held in the properties file
632      * @throws IOException
633      */
634     public static void storeProperties(PropertyMap properties, URI uri, String title) throws IOException {
635         OutputStream out = null;
636 
637         try {
638             out = NetUtil.getOutputStream(uri);
639             PropertyMap temp = new PropertyMap();
640             temp.putAll(properties);
641             temp.store(out, title);
642         } finally {
643             IOUtil.close(out);
644         }
645     }
646 
647     /**
648      * @param uri
649      *            the resource whose size is wanted
650      * @return the size of that resource
651      */
652     public static int getSize(URI uri) {
653         return getSize(uri, null, null);
654     }
655 
656     public static int getSize(URI uri, String proxyHost) {
657         return getSize(uri, proxyHost, null);
658     }
659 
660     public static int getSize(URI uri, String proxyHost, Integer proxyPort) {
661         try {
662             if (uri.getScheme().equals(PROTOCOL_HTTP)) {
663                 WebResource resource = new WebResource(uri, proxyHost, proxyPort);
664                 int size = resource.getSize();
665                 resource.shutdown();
666                 return size;
667             }
668 
669             return uri.toURL().openConnection().getContentLength();
670         } catch (IOException e) {
671             return 0;
672         }
673     }
674 
675     /**
676      * When was the given URI last modified. If no modification time is
677      * available then this method return the current time.
678      */
679     public static long getLastModified(URI uri) {
680         return getLastModified(uri, null, null);
681     }
682 
683     public static long getLastModified(URI uri, String proxyHost) {
684         return getLastModified(uri, proxyHost, null);
685     }
686 
687     public static long getLastModified(URI uri, String proxyHost, Integer proxyPort) {
688         try {
689             if (uri.getScheme().equals(PROTOCOL_HTTP)) {
690                 WebResource resource = new WebResource(uri, proxyHost, proxyPort);
691                 long time = resource.getLastModified();
692                 resource.shutdown();
693                 return time;
694             }
695 
696             URLConnection urlConnection = uri.toURL().openConnection();
697             long time = urlConnection.getLastModified();
698 
699             // If it were a jar then time contains the last modified date of the jar.
700             if (urlConnection instanceof JarURLConnection) {
701                 // form is jar:file:.../xxx.jar!.../filename.ext
702                 JarURLConnection jarConnection = (JarURLConnection) urlConnection;
703                 JarEntry jarEntry = jarConnection.getJarEntry();
704                 time = jarEntry.getTime();
705             }
706 
707             return time;
708         } catch (IOException ex) {
709             log.warn("Failed to get modified time", ex);
710             return new Date().getTime();
711         }
712     }
713 
714     /**
715      * Returns whether the left is newer than the right by comparing their last
716      * modified dates.
717      * 
718      * @param left
719      * @param right
720      * @return true if the left is newer
721      */
722     public static boolean isNewer(URI left, URI right) {
723         return isNewer(left, right, null, null);
724     }
725 
726     public static boolean isNewer(URI left, URI right, String proxyHost) {
727         return isNewer(left, right, proxyHost, null);
728     }
729 
730     public static boolean isNewer(URI left, URI right, String proxyHost, Integer proxyPort) {
731         return NetUtil.getLastModified(left, proxyHost, proxyPort) > NetUtil.getLastModified(right, proxyHost, proxyPort);
732     }
733 
734     /**
735      * Quick implementation of FilenameFilter that uses a URIFilter
736      */
737     public static class URIFilterFilenameFilter implements FilenameFilter {
738         /**
739          * Simple ctor
740          */
741         public URIFilterFilenameFilter(URIFilter filter) {
742             this.filter = filter;
743         }
744 
745         /*
746          * (non-Javadoc)
747          * 
748          * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
749          */
750         public boolean accept(File arg0, String name) {
751             return filter.accept(name);
752         }
753 
754         private URIFilter filter;
755     }
756 
757     /**
758      * Throw if the given URI does not use the 'file:' protocol
759      * 
760      * @param uri
761      *            The URI to check
762      * @throws MalformedURLException
763      *             If the protocol is not file:
764      */
765     private static void checkFileURI(URI uri) throws MalformedURLException {
766         if (!uri.getScheme().equals(PROTOCOL_FILE)) {
767             // TRANSLATOR: Error condition: The URL protocol "file:" was expected, but something else was found. {0} is a placeholder for the URL.
768             throw new MalformedURLException(JSMsg.gettext("The URL {0} is not a file.", uri));
769         }
770     }
771 
772     /**
773      * Returns the cache directory.
774      * 
775      * @return File
776      */
777     public static File getURICacheDir() {
778         return cachedir;
779     }
780 
781     /**
782      * Sets the cache directory.
783      * 
784      * @param cachedir
785      *            The cache directory to set
786      */
787     public static void setURICacheDir(File cachedir) {
788         NetUtil.cachedir = cachedir;
789     }
790 
791     /**
792      * Get a URI version of the given file.
793      * 
794      * @param file
795      *            The File to turn into a URI
796      * @return a URI for the given file
797      */
798     public static URI getURI(File file) {
799         return file.toURI();
800     }
801 
802     /**
803      * A URI version of <code>File.createTempFile()</code>
804      * 
805      * @return A new temporary URI
806      * @throws IOException
807      *             If something goes wrong creating the temp URI
808      */
809     public static URI getTemporaryURI(String prefix, String suffix) throws IOException {
810         File tempFile = File.createTempFile(prefix, suffix);
811         return getURI(tempFile);
812     }
813 
814     /**
815      * Convert an URL to an URI.
816      * 
817      * @param url
818      *            to convert
819      * @return the URI representation of the URL
820      */
821     public static URI toURI(URL url) {
822         try {
823             return new URI(url.toExternalForm());
824         } catch (URISyntaxException e) {
825             return null;
826         }
827     }
828 
829     /**
830      * Convert an URI to an URL.
831      * 
832      * @param uri
833      *            to convert
834      * @return the URL representation of the URI
835      */
836     public static URL toURL(URI uri) {
837         try {
838             return uri.toURL();
839         } catch (MalformedURLException e) {
840             return null;
841         }
842     }
843 
844     /**
845      * Check that the directories in the version directory really represent
846      * versions.
847      */
848     public static class IsDirectoryURIFilter implements URIFilter {
849         /**
850          * Simple ctor
851          */
852         public IsDirectoryURIFilter(URI parent) {
853             this.parent = parent;
854         }
855 
856         /*
857          * (non-Javadoc)
858          * 
859          * @see org.crosswire.common.util.URLFilter#accept(java.lang.String)
860          */
861         public boolean accept(String name) {
862             return NetUtil.isDirectory(NetUtil.lengthenURI(parent, name));
863         }
864 
865         private URI parent;
866     }
867 
868     /**
869      * Where are temporary files cached.
870      */
871     private static File cachedir;
872 
873     /**
874      * The log stream
875      */
876     private static final org.slf4j.Logger log = LoggerFactory.getLogger(NetUtil.class);
877 }
878