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 as published by
5    * the Free Software Foundation. This program is distributed in the hope
6    * that it will be useful, but WITHOUT ANY WARRANTY; without even the
7    * 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
18   *     The copyright to this program is held by it's authors.
19   *
20   * ID: $Id: NetUtil.java 2230 2012-02-08 00:00:10Z dmsmith $
21   */
22  package org.crosswire.common.util;
23  
24  import java.io.BufferedReader;
25  import java.io.File;
26  import java.io.FileInputStream;
27  import java.io.FileNotFoundException;
28  import java.io.FileOutputStream;
29  import java.io.FilenameFilter;
30  import java.io.IOException;
31  import java.io.InputStream;
32  import java.io.InputStreamReader;
33  import java.io.OutputStream;
34  import java.net.JarURLConnection;
35  import java.net.MalformedURLException;
36  import java.net.URI;
37  import java.net.URISyntaxException;
38  import java.net.URL;
39  import java.net.URLConnection;
40  import java.util.ArrayList;
41  import java.util.Arrays;
42  import java.util.Date;
43  import java.util.List;
44  import java.util.jar.JarEntry;
45  
46  import org.crosswire.jsword.JSMsg;
47  import org.crosswire.jsword.JSOtherMsg;
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 [dmsmith555 at yahoo dot com]
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 //            assert false : ex;
400             return null;
401         }
402     }
403 
404     private static boolean isSeparator(char c) {
405         return c == '/' || c == '\\';
406     }
407 
408     /**
409      * Attempt to obtain an InputStream from a URI. If the URI is a file scheme
410      * then just open it directly. Otherwise, call uri.toURL().openStream().
411      * 
412      * @param uri
413      *            The URI to attempt to read from
414      * @return An InputStream connection
415      */
416     public static InputStream getInputStream(URI uri) throws IOException {
417         // We favor the FileOutputStream
418         if (uri.getScheme().equals(PROTOCOL_FILE)) {
419             return new FileInputStream(uri.getPath());
420         }
421         return uri.toURL().openStream();
422     }
423 
424     /**
425      * Attempt to obtain an OutputStream from a URI. The simple case will open
426      * it if it is local. Otherwise, it will call
427      * uri.toURL().openConnection().getOutputStream(), however in some JVMs (MS
428      * at least this fails where new FileOutputStream(url) works.
429      * 
430      * @param uri
431      *            The URI to attempt to write to
432      * @return An OutputStream connection
433      */
434     public static OutputStream getOutputStream(URI uri) throws IOException {
435         return getOutputStream(uri, false);
436     }
437 
438     /**
439      * Attempt to obtain an OutputStream from a URI. The simple case will open
440      * it if it is local. Otherwise, it will call
441      * uri.toURL().openConnection().getOutputStream(), however in some JVMs (MS
442      * at least this fails where new FileOutputStream(url) works.
443      * 
444      * @param uri
445      *            The URI to attempt to write to
446      * @param append
447      *            Do we write to the end of the file instead of the beginning
448      * @return An OutputStream connection
449      */
450     public static OutputStream getOutputStream(URI uri, boolean append) throws IOException {
451         // We favor the FileOutputStream method here because append
452         // is not well defined for the openConnection method
453         if (uri.getScheme().equals(PROTOCOL_FILE)) {
454             return new FileOutputStream(uri.getPath(), append);
455         }
456         URLConnection cnx = uri.toURL().openConnection();
457         cnx.setDoOutput(true);
458         return cnx.getOutputStream();
459     }
460 
461     /**
462      * List the items available assuming that this URI points to a directory.
463      * <p>
464      * There are 2 methods of calculating the answer - if the URI is a file: URI
465      * then we can just use File.list(), otherwise we ask for a file inside the
466      * directory called index.txt and assume the directories contents to be
467      * listed one per line.
468      * <p>
469      * If the URI is a file: URI then we execute both methods and warn if there
470      * is a difference, but returning the values from the index.txt method.
471      */
472     public static String[] list(URI uri, URIFilter filter) throws MalformedURLException, IOException {
473         // We should probably cache this in some way? This is going
474         // to get very slow calling this often across a network
475         String[] reply = {};
476         try {
477             URI search = NetUtil.lengthenURI(uri, INDEX_FILE);
478             reply = listByIndexFile(search, filter);
479         } catch (FileNotFoundException ex) {
480             // So the index file was not found - this isn't going to work over
481             // JNLP or other systems that can't use file: URIs. But it is silly
482             // to get to picky so if there is a solution using file: then just
483             // print a warning and carry on.
484             log.warn("index file for " + uri.toString() + " was not found.");
485             if (uri.getScheme().equals(PROTOCOL_FILE)) {
486                 return listByFile(uri, filter);
487             }
488         }
489 
490         // if we can - check that the index file is up to date.
491         if (uri.getScheme().equals(PROTOCOL_FILE)) {
492             String[] files = listByFile(uri, filter);
493 
494             // Check that the answers are the same
495             if (files.length != reply.length) {
496                 log.warn("index file for " + uri.toString() + " has incorrect number of entries.");
497             } else {
498                 List<String> list = Arrays.asList(files);
499                 for (int i = 0; i < files.length; i++) {
500                     if (!list.contains(files[i])) {
501                         log.warn("file: based index found " + files[i] + " but this was not found using index file.");
502                     }
503                 }
504             }
505         }
506 
507         return reply;
508     }
509 
510     /**
511      * List all the files specified by the index file passed in.
512      * 
513      * @return String[] Matching results.
514      */
515     public static String[] listByFile(URI uri, URIFilter filter) throws MalformedURLException {
516         File fdir = new File(uri.getPath());
517         if (!fdir.isDirectory()) {
518             // TRANSLATOR: Error condition: A directory was expected, but a file was found. {0} is a placeholder for the file.
519             throw new MalformedURLException(JSMsg.gettext("The URL {0} is not a directory", uri.toString()));
520         }
521 
522         return fdir.list(new URIFilterFilenameFilter(filter));
523     }
524 
525     /**
526      * List all the strings specified by the index file passed in. To be
527      * acceptable it must be a non-0 length string, not commented with #, and
528      * not the index file itself.
529      * 
530      * @return String[] Matching results.
531      * @throws FileNotFoundException
532      */
533     public static String[] listByIndexFile(URI index) throws IOException {
534         return listByIndexFile(index, new DefaultURIFilter());
535     }
536 
537     /**
538      * List all the files specified by the index file passed in. To be
539      * acceptable it:
540      * <ul>
541      * <li> must be a non-0 length string,</li>
542      * <li> not commented with #,</li>
543      * <li> not the index file itself</li>
544      * <li> and acceptable by the filter.</li>
545      * </ul>
546      * 
547      * @return String[] Matching results.
548      * @throws FileNotFoundException
549      */
550     public static String[] listByIndexFile(URI index, URIFilter filter) throws IOException {
551         InputStream in = null;
552         BufferedReader din = null;
553         try {
554             in = NetUtil.getInputStream(index);
555             // Quiet Android from complaining about using the default BufferReader buffer size.
556             // The actual buffer size is undocumented. So this is a good idea any way.
557             din = new BufferedReader(new InputStreamReader(in), 8192);
558 
559             // We still need to do the filtering
560             List<String> list = new ArrayList<String>();
561 
562             while (true) {
563                 String line = din.readLine();
564 
565                 if (line == null) {
566                     break;
567                 }
568 
569                 // we need to trim extraneous whitespace on the line
570                 String name = line.trim();
571 
572                 // Is it acceptable?
573                 if (name.length() > 0 && name.charAt(0) != '#' && !name.equals(INDEX_FILE) && filter.accept(name)) {
574                     list.add(name);
575                 }
576             }
577 
578             return list.toArray(new String[list.size()]);
579         } finally {
580             IOUtil.close(din);
581             IOUtil.close(in);
582         }
583     }
584 
585     /**
586      * Load up properties given by a URI.
587      * 
588      * @param uri
589      *            the location of the properties
590      * @return the properties given by the URI
591      * @throws IOException
592      */
593     public static PropertyMap loadProperties(URI uri) throws IOException {
594         InputStream is = null;
595         try {
596             is = NetUtil.getInputStream(uri);
597             PropertyMap prop = new PropertyMap();
598             prop.load(is);
599             is.close();
600             return prop;
601         } finally {
602             IOUtil.close(is);
603         }
604     }
605 
606     /**
607      * Store the properties at the location given by the uri using the supplied
608      * title.
609      * 
610      * @param properties
611      *            the properties to store
612      * @param uri
613      *            the location of the store
614      * @param title
615      *            the label held in the properties file
616      * @throws IOException
617      */
618     public static void storeProperties(PropertyMap properties, URI uri, String title) throws IOException {
619         OutputStream out = null;
620 
621         try {
622             out = NetUtil.getOutputStream(uri);
623             PropertyMap temp = new PropertyMap();
624             temp.putAll(properties);
625             temp.store(out, title);
626         } finally {
627             IOUtil.close(out);
628         }
629     }
630 
631     /**
632      * @param uri
633      *            the resource whose size is wanted
634      * @return the size of that resource
635      */
636     public static int getSize(URI uri) {
637         return getSize(uri, null, null);
638     }
639 
640     public static int getSize(URI uri, String proxyHost) {
641         return getSize(uri, proxyHost, null);
642     }
643 
644     public static int getSize(URI uri, String proxyHost, Integer proxyPort) {
645         try {
646             if (uri.getScheme().equals(PROTOCOL_HTTP)) {
647                 WebResource resource = new WebResource(uri, proxyHost, proxyPort);
648                 int size = resource.getSize();
649                 resource.shutdown();
650                 return size;
651             }
652 
653             return uri.toURL().openConnection().getContentLength();
654         } catch (IOException e) {
655             return 0;
656         }
657     }
658 
659     /**
660      * When was the given URI last modified. If no modification time is
661      * available then this method return the current time.
662      */
663     public static long getLastModified(URI uri) {
664         return getLastModified(uri, null, null);
665     }
666 
667     public static long getLastModified(URI uri, String proxyHost) {
668         return getLastModified(uri, proxyHost, null);
669     }
670 
671     public static long getLastModified(URI uri, String proxyHost, Integer proxyPort) {
672         try {
673             if (uri.getScheme().equals(PROTOCOL_HTTP)) {
674                 WebResource resource = new WebResource(uri, proxyHost, proxyPort);
675                 long time = resource.getLastModified();
676                 resource.shutdown();
677                 return time;
678             }
679 
680             URLConnection urlConnection = uri.toURL().openConnection();
681             long time = urlConnection.getLastModified();
682 
683             // If it were a jar then time contains the last modified date of the jar.
684             if (urlConnection instanceof JarURLConnection) {
685                 // form is jar:file:.../xxx.jar!.../filename.ext
686                 JarURLConnection jarConnection = (JarURLConnection) urlConnection;
687                 JarEntry jarEntry = jarConnection.getJarEntry();
688                 time = jarEntry.getTime();
689             }
690 
691             return time;
692         } catch (IOException ex) {
693             log.warn("Failed to get modified time", ex);
694             return new Date().getTime();
695         }
696     }
697 
698     /**
699      * Returns whether the left is newer than the right by comparing their last
700      * modified dates.
701      * 
702      * @param left
703      * @param right
704      * @return true if the left is newer
705      */
706     public static boolean isNewer(URI left, URI right) {
707         return isNewer(left, right, null, null);
708     }
709 
710     public static boolean isNewer(URI left, URI right, String proxyHost) {
711         return isNewer(left, right, proxyHost, null);
712     }
713 
714     public static boolean isNewer(URI left, URI right, String proxyHost, Integer proxyPort) {
715         return NetUtil.getLastModified(left, proxyHost, proxyPort) > NetUtil.getLastModified(right, proxyHost, proxyPort);
716     }
717 
718     /**
719      * Quick implementation of FilenameFilter that uses a URIFilter
720      */
721     public static class URIFilterFilenameFilter implements FilenameFilter {
722         /**
723          * Simple ctor
724          */
725         public URIFilterFilenameFilter(URIFilter filter) {
726             this.filter = filter;
727         }
728 
729         /*
730          * (non-Javadoc)
731          * 
732          * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
733          */
734         public boolean accept(File arg0, String name) {
735             return filter.accept(name);
736         }
737 
738         private URIFilter filter;
739     }
740 
741     /**
742      * Throw if the given URI does not use the 'file:' protocol
743      * 
744      * @param uri
745      *            The URI to check
746      * @throws MalformedURLException
747      *             If the protocol is not file:
748      */
749     private static void checkFileURI(URI uri) throws MalformedURLException {
750         if (!uri.getScheme().equals(PROTOCOL_FILE)) {
751             // TRANSLATOR: Error condition: The URL protocol "file:" was expected, but something else was found. {0} is a placeholder for the URL.
752             throw new MalformedURLException(JSMsg.gettext("The URL {0} is not a file.", uri));
753         }
754     }
755 
756     /**
757      * Returns the cache directory.
758      * 
759      * @return File
760      */
761     public static File getURICacheDir() {
762         return cachedir;
763     }
764 
765     /**
766      * Sets the cache directory.
767      * 
768      * @param cachedir
769      *            The cache directory to set
770      */
771     public static void setURICacheDir(File cachedir) {
772         NetUtil.cachedir = cachedir;
773     }
774 
775     /**
776      * Get a URI version of the given file.
777      * 
778      * @param file
779      *            The File to turn into a URI
780      * @return a URI for the given file
781      */
782     public static URI getURI(File file) {
783         return file.toURI();
784     }
785 
786     /**
787      * A URI version of <code>File.createTempFile()</code>
788      * 
789      * @return A new temporary URI
790      * @throws IOException
791      *             If something goes wrong creating the temp URI
792      */
793     public static URI getTemporaryURI(String prefix, String suffix) throws IOException {
794         File tempFile = File.createTempFile(prefix, suffix);
795         return getURI(tempFile);
796     }
797 
798     /**
799      * Convert an URL to an URI.
800      * 
801      * @param url
802      *            to convert
803      * @return the URI representation of the URL
804      */
805     public static URI toURI(URL url) {
806         try {
807             return new URI(url.toExternalForm());
808         } catch (URISyntaxException e) {
809             return null;
810         }
811     }
812 
813     /**
814      * Convert an URI to an URL.
815      * 
816      * @param uri
817      *            to convert
818      * @return the URL representation of the URI
819      */
820     public static URL toURL(URI uri) {
821         try {
822             return uri.toURL();
823         } catch (MalformedURLException e) {
824             return null;
825         }
826     }
827 
828     /**
829      * Check that the directories in the version directory really represent
830      * versions.
831      */
832     public static class IsDirectoryURIFilter implements URIFilter {
833         /**
834          * Simple ctor
835          */
836         public IsDirectoryURIFilter(URI parent) {
837             this.parent = parent;
838         }
839 
840         /*
841          * (non-Javadoc)
842          * 
843          * @see org.crosswire.common.util.URLFilter#accept(java.lang.String)
844          */
845         public boolean accept(String name) {
846             return NetUtil.isDirectory(NetUtil.lengthenURI(parent, name));
847         }
848 
849         private URI parent;
850     }
851 
852     /**
853      * Where are temporary files cached.
854      */
855     private static File cachedir;
856 
857     /**
858      * The log stream
859      */
860     private static final Logger log = Logger.getLogger(NetUtil.class);
861 }
862