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