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