source: branches/0.20.x/abcl/src/org/armedbear/lisp/Pathname.java

Last change on this file was 12700, checked in by Mark Evenson, 15 years ago

Backport r12696: TRUENAME for URL-PATHNAME ambigiously either a directory or file now (mostly) normalizes to the directory.

If there is no type, query or fragment in a URL-PATHNAME passed to
TRUENAME, it contains a NAME compoment, there is a resource accessible
(via java.net.URL.openConnection()), and there is a resource similarly
accessible via appending a "/" to the namestring, we return a pathname
that refers to the latter resource.

We do this to overcome the bug that autoloading ABCL from a *LISP-HOME*
that is a URL-PATHNAME fails for calls such as

(autoload 'jclass-fields "java")

as Load.findLoadableFile() returns a pathname for which java.net.URL
actually opens "<*LISP-HOME*>/java/". There is no way from the
java.net.URL implementation to determine that this is a directory
without reading from the stream. The more correct solution would be
to program a restart which if the load fails it would retry with
another possible URL, but we hope that this heuristic will cover the
vast majority of usage as providers of URL references used as a
filesystem usually avoid such ambiguous references.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 86.6 KB
Line 
1/*
2 * Pathname.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Pathname.java 12700 2010-05-18 05:17:07Z mevenson $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33package org.armedbear.lisp;
34
35import static org.armedbear.lisp.Lisp.*;
36
37import java.io.File;
38import java.io.IOException;
39import java.io.InputStream;
40import java.io.FileInputStream;
41import java.net.MalformedURLException;
42import java.net.URI;
43import java.net.URISyntaxException;
44import java.net.URL;
45import java.net.URLDecoder;
46import java.net.URLConnection;
47import java.util.Enumeration;
48import java.util.StringTokenizer;
49import java.util.zip.ZipEntry;
50import java.util.zip.ZipFile;
51import java.util.zip.ZipInputStream;
52
53public class Pathname extends LispObject {
54
55    protected LispObject host = NIL;
56    protected LispObject device = NIL;
57    protected LispObject directory = NIL;
58    protected LispObject name = NIL;
59    // A string, NIL, :WILD or :UNSPECIFIC.
60    protected LispObject type = NIL;
61    // A positive integer, or NIL, :WILD, :UNSPECIFIC, or :NEWEST.
62    protected LispObject version = NIL;
63
64    private String namestring;
65
66    /** The protocol for changing any instance field (i.e. 'host', 'type', etc.)
67     *  is to call this method after changing the field to recompute the namestring.
68     *  We could do this with setter/getters, but that choose not to in order to avoid the
69     *  performance indirection penalty.
70     *
71     *  Although, given the number of bugs that crop up when this
72     *  protocol is not adhered to, maybe we should consider it.
73     */
74    public void invalidateNamestring() {
75        namestring = null;
76    }
77   
78    // ### %invalidate-namestring
79    private static final Primitive _INVALIDATE_NAMESTRING = new pf_invalidate_namestring();
80    private static class pf_invalidate_namestring extends Primitive {
81        pf_invalidate_namestring() {
82            super("%invalidate-namestring", PACKAGE_EXT, false);
83        }
84        @Override
85        public LispObject execute(LispObject first) {
86            ((Pathname)coerceToPathname(first)).invalidateNamestring();
87            return first;
88        }
89    }
90
91    protected Pathname() {}
92
93    /** Copy constructor which shares no structure with the original. */
94    protected Pathname(Pathname p) {
95        if (p.host != NIL) {
96            if (p.host instanceof SimpleString) {
97                host = new SimpleString(((SimpleString)p.host).getStringValue());
98            } else  if (p.host instanceof Symbol) {
99                host = p.host;
100            } else if (p.host instanceof Cons) {
101                host = new Cons((Cons)p.host);
102            } else {
103                Debug.assertTrue(false);
104            }
105        }
106        if (p.device != NIL) {
107            if (p.device instanceof SimpleString) {
108                device = new SimpleString(((SimpleString)p.device).getStringValue());
109            } else if (p.device instanceof Cons) {
110                Cons jars = (Cons)p.device;
111                device = new Cons(NIL, NIL);
112                LispObject first = jars.car();
113                if (first instanceof Pathname) {
114                    ((Cons)device).car = new Pathname((Pathname)first);
115                } else {
116                    Debug.assertTrue(false);
117                }
118                if (!jars.cdr().equals(NIL)) {
119                    if (jars.cdr() instanceof Cons) {
120                        ((Cons)device).cdr = new Cons(new Pathname((Pathname)jars.cdr().car()), NIL);
121                    } else { 
122                        Debug.assertTrue(false);
123                    }
124                }
125            } else if (p.device instanceof Symbol) {
126                device = p.device;
127            } else {
128                Debug.assertTrue(false);
129            }               
130        }
131        if (p.directory != NIL) {
132            if (p.directory instanceof Cons) {
133                directory = NIL;
134                for (LispObject list = p.directory; list != NIL; list = list.cdr()) {
135                    LispObject o = list.car();
136                    if (o instanceof Symbol) {
137                        directory = directory.push(o);
138                    } else if (o instanceof SimpleString) {
139                        directory = directory.push(new SimpleString(((SimpleString)o).getStringValue()));
140                    } else {
141                        Debug.assertTrue(false);
142                    }
143                }
144                directory.nreverse();
145            } else {
146                Debug.assertTrue(false);
147            }
148        }
149        if (p.name != NIL) {
150            if (p.name instanceof SimpleString) {
151                name = new SimpleString(((SimpleString)p.name).getStringValue());
152            } else if (p.name instanceof Symbol) {
153                name = p.name;
154            } else {
155                Debug.assertTrue(false);
156            }
157        } 
158        if (p.type != NIL) {
159            if (p.type instanceof SimpleString) {
160                type = new SimpleString(((SimpleString)p.type).getStringValue());
161            } else if (p.type instanceof Symbol) {
162                type = p.type;
163            } else {
164                Debug.assertTrue(false);
165            }
166        }
167    }
168
169    public Pathname(String s) {
170        init(s);
171    }
172
173    public static boolean isSupportedProtocol(String protocol) {
174        // There is no programmatic way to know what protocols will
175        // sucessfully construct a URL, so we check for well known ones...
176        if ("jar".equals(protocol) 
177            || "file".equals(protocol))
178            //            || "http".equals(protocol))  XXX remove this as an optimization
179            {
180                return true;
181            }
182        // ... and try the entire constructor with some hopefully
183        // reasonable parameters for everything else.
184        try {
185            new URL(protocol, "example.org", "foo");
186            return true;
187        }  catch (MalformedURLException e) {
188            return false;
189        }
190    }
191
192    public Pathname(URL url) {
193        if ("file".equals(url.getProtocol())) {
194            String s;
195            try {
196                s = URLDecoder.decode(url.getPath(), "UTF-8");
197            } catch (java.io.UnsupportedEncodingException uee) {
198                // Can't happen: every Java is supposed to support
199                // at least UTF-8 encoding
200                Debug.assertTrue(false);
201                s = null;
202            }
203            if (s != null) {
204    if (Utilities.isPlatformWindows) {
205        //  Workaround for Java's idea of URLs
206        //  new (URL"file:///c:/a/b").getPath() --> "/c:/a/b"
207                    //  whereas we need "c" to be the DEVICE.
208        if (s.length() > 2 
209      && s.charAt(0) == '/'
210      && s.charAt(2) == ':') {
211      s = s.substring(1);
212        }
213    }
214                init(s);
215                return;
216            }
217        } else {
218            init(url.toString());
219            return;
220        }
221        error(new LispError("Failed to construct Pathname from URL: "
222                            + "'" + url.toString() + "'"));
223    }
224
225    static final Symbol SCHEME = internKeyword("SCHEME");
226    static final Symbol AUTHORITY = internKeyword("AUTHORITY");
227    static final Symbol QUERY = internKeyword("QUERY");
228    static final Symbol FRAGMENT = internKeyword("FRAGMENT");
229
230    static final private String jarSeparator = "!/";
231    private final void init(String s) {
232        if (s == null) {
233            return;
234        }
235        if (s.equals(".") || s.equals("./")
236          || (Utilities.isPlatformWindows && s.equals(".\\"))) {
237            directory = new Cons(Keyword.RELATIVE);
238            return;
239        }
240        if (s.equals("..") || s.equals("../")) {
241            directory = list(Keyword.RELATIVE, Keyword.UP);
242            return;
243        }
244        if (Utilities.isPlatformWindows) {
245            if (s.startsWith("\\\\")) {
246                //UNC path support
247                // match \\<server>\<share>\[directories-and-files]
248
249                int shareIndex = s.indexOf('\\', 2);
250                int dirIndex = s.indexOf('\\', shareIndex + 1);
251
252                if (shareIndex == -1 || dirIndex == -1) {
253                    error(new LispError("Unsupported UNC path format: \"" + s + '"'));
254                }
255
256                host = new SimpleString(s.substring(2, shareIndex));
257                device = new SimpleString(s.substring(shareIndex + 1, dirIndex));
258
259                Pathname p = new Pathname(s.substring(dirIndex));
260                directory = p.directory;
261                name = p.name;
262                type = p.type;
263                version = p.version;
264                invalidateNamestring();
265                return;
266            }
267        }
268       
269        // A JAR file
270        if (s.startsWith("jar:") && s.endsWith(jarSeparator)) {
271            LispObject jars = NIL;
272            int i = s.lastIndexOf(jarSeparator, s.length() - jarSeparator.length() - 1);
273            String jar = null;
274            if (i == -1) {
275                jar = s;
276            } else {
277                // There can be no more than two jar references and the
278                // inner one must be a file reference within the outer.
279                jar = "jar:file:" + s.substring(i + jarSeparator.length());
280                s = s.substring("jar:".length(), i + jarSeparator.length());
281                Pathname p = new Pathname(s);
282                jars = jars.push(p.device.car());
283            }
284            if (jar.startsWith("jar:file:")) {
285                String jarString
286                    = jar.substring("jar:".length(),
287                                    jar.length() - jarSeparator.length());
288                // Use URL constructor to normalize Windows' use of device
289                URL url = null;
290                try {
291                    url = new URL(jarString);
292                } catch (MalformedURLException e) {
293                    error(new LispError("Failed to parse '" + jarString + "'"
294                            + " as URL:"
295                            + e.getMessage()));
296                }
297                Pathname jarPathname = new Pathname(url);
298                jars = jars.push(jarPathname);
299            } else {
300                URL url = null;
301                try {
302                    url = new URL(jar.substring("jar:".length(), jar.length() - 2));
303                    Pathname p = new Pathname(url);
304                    jars = jars.push(p);
305                } catch (MalformedURLException e) {
306                    error(new LispError("Failed to parse URL "
307                                        + "'" + url + "'"
308                                        + e.getMessage()));
309                }
310            }
311            jars = jars.nreverse();
312            device = jars;
313            invalidateNamestring();
314            return;
315        }
316
317        // An entry in a JAR file
318        final int separatorIndex = s.lastIndexOf(jarSeparator);
319        if (separatorIndex > 0 && s.startsWith("jar:")) {
320            final String jarURL = s.substring(0, separatorIndex + jarSeparator.length());
321            Pathname d = new Pathname(jarURL);
322            if (device instanceof Cons) {
323                LispObject[] jars = d.copyToArray();
324                //  XXX Is this ever reached?  If so, need to append lists
325                Debug.assertTrue(false);
326            } else {
327                device = d.device;
328            }
329            s = "/" + s.substring(separatorIndex + jarSeparator.length());
330            Pathname p = new Pathname(s);
331            directory = p.directory;
332            name = p.name;
333            type = p.type;
334            version = p.version;
335            return;
336        }
337
338        // A URL
339        if (isValidURL(s)) {
340            URL url = null;
341            try {
342                url = new URL(s);
343            } catch (MalformedURLException e) {
344                Debug.assertTrue(false);
345            }
346            String scheme = url.getProtocol();
347            if (scheme.equals("file")) {
348                Pathname p = new Pathname(url.getFile());
349                this.host = p.host;
350                this.device = p.device;
351                this.directory = p.directory;
352                this.name = p.name;
353                this.type = p.type;
354                this.version = p.version;
355                return;
356            }
357            Debug.assertTrue(scheme != null);
358            URI uri = null;
359            try { 
360                uri = url.toURI().normalize();
361            } catch (URISyntaxException e) {
362                error(new LispError("Could not form URI from "
363                                    + "'" + url + "'"
364                                    + " because: " + e));
365            }
366            String authority = uri.getAuthority();
367            Debug.assertTrue(authority != null);
368
369            host = NIL;
370            host = host.push(SCHEME);
371            host = host.push(new SimpleString(scheme));
372            host = host.push(AUTHORITY);
373            host = host.push(new SimpleString(authority));
374
375            device = NIL;
376           
377            // URI encode necessary characters
378            String path = uri.getRawPath();
379            if (path == null) {
380                path = "";
381            } 
382            String query = uri.getRawQuery();
383            if (query != null) {
384                host = host.push(QUERY);
385                host = host.push(new SimpleString(query));
386            }
387            String fragment = uri.getRawFragment();
388            if (fragment != null) {
389                host = host.push(FRAGMENT);
390                host = host.push(new SimpleString(fragment));
391            }
392            Pathname p = new Pathname(path != null ? path : ""); 
393
394            directory = p.directory;
395            name = p.name;
396            type = p.type;
397           
398            host = host.nreverse();
399            invalidateNamestring();
400            return;
401        }
402
403        if (Utilities.isPlatformWindows) {
404            if (!s.contains(jarSeparator)) {
405                s = s.replace("/", "\\");
406            } else {
407              StringBuilder result = new StringBuilder();
408              for (int i = 0; i < s.length(); i++) {
409                  char c = s.charAt(i);
410                  if ( c != '/') {
411                      result.append(c);
412                  } else {
413                      if (i != 0 && s.charAt(i-1) != '!') {
414                          result.append("\\");
415                      } else {
416                          result.append(c);
417                      }
418                  }
419              }
420              s = result.toString();
421            }
422        }
423
424        // Expand user home directories
425        if (Utilities.isPlatformUnix) {
426            if (s.equals("~")) {
427                s = System.getProperty("user.home").concat("/");
428            } else if (s.startsWith("~/")) {
429                s = System.getProperty("user.home").concat(s.substring(1));
430            }
431        }
432        namestring = s;
433        if (Utilities.isPlatformWindows) {
434            if (s.length() >= 2 && s.charAt(1) == ':') {
435                device = new SimpleString(s.charAt(0));
436                s = s.substring(2);
437            }
438        }
439        String d = null;
440        // Find last file separator char.
441        if (Utilities.isPlatformWindows) {
442            for (int i = s.length(); i-- > 0;) {
443                char c = s.charAt(i);
444                if (c == '/' || c == '\\') {
445                    d = s.substring(0, i + 1);
446                    s = s.substring(i + 1);
447                    break;
448                }
449            }
450        } else {
451            for (int i = s.length(); i-- > 0;) {
452                if (s.charAt(i) == '/') {
453                    d = s.substring(0, i + 1);
454                    s = s.substring(i + 1);
455                    break;
456                }
457            }
458        }
459        if (d != null) {
460            if (s.equals("..")) {
461                d = d.concat(s);
462                s = "";
463            }
464            directory = parseDirectory(d);
465        }
466        if (s.startsWith(".") 
467            // No TYPE can be parsed
468            && (s.indexOf(".", 1) == -1 
469                || s.substring(s.length() -1).equals("."))) {
470            name = new SimpleString(s);
471            return;
472        }
473        int index = s.lastIndexOf('.');
474        String n = null;
475        String t = null;
476        if (index > 0) {
477            n = s.substring(0, index);
478            t = s.substring(index + 1);
479        } else if (s.length() > 0) {
480            n = s;
481        }
482        if (n != null) {
483            if (n.equals("*")) {
484                name = Keyword.WILD;
485            } else {
486                name = new SimpleString(n);
487            }
488        }
489        if (t != null) {
490            if (t.equals("*")) {
491                type = Keyword.WILD;
492            } else {
493                type = new SimpleString(t);
494            }
495        }
496    }
497
498    private static final LispObject parseDirectory(String d) {
499        if (d.equals("/") || (Utilities.isPlatformWindows && d.equals("\\"))) {
500            return new Cons(Keyword.ABSOLUTE);
501        }
502        LispObject result;
503        if (d.startsWith("/") || (Utilities.isPlatformWindows && d.startsWith("\\"))) {
504            result = new Cons(Keyword.ABSOLUTE);
505        } else {
506            result = new Cons(Keyword.RELATIVE);
507        }
508        StringTokenizer st = new StringTokenizer(d, "/\\");
509        while (st.hasMoreTokens()) {
510            String token = st.nextToken();
511            LispObject obj;
512            if (token.equals("*")) {
513                obj = Keyword.WILD;
514            } else if (token.equals("**")) {
515                obj = Keyword.WILD_INFERIORS;
516            } else if (token.equals("..")) {
517                if (result.car() instanceof AbstractString) {
518                    result = result.cdr();
519                    continue;
520                }
521                obj = Keyword.UP;
522            } else {
523                obj = new SimpleString(token);
524            }
525            result = new Cons(obj, result);
526        }
527        return result.nreverse();
528    }
529
530    @Override
531    public LispObject getParts() {
532        LispObject parts = NIL;
533        parts = parts.push(new Cons("HOST", host));
534        parts = parts.push(new Cons("DEVICE", device));
535        parts = parts.push(new Cons("DIRECTORY", directory));
536        parts = parts.push(new Cons("NAME", name));
537        parts = parts.push(new Cons("TYPE", type));
538        parts = parts.push(new Cons("VERSION", version));
539        return parts.nreverse();
540    }
541
542    @Override
543    public LispObject typeOf() {
544        if (isURL()) {
545            return Symbol.URL_PATHNAME;
546        } 
547        if (isJar()) {
548            return Symbol.JAR_PATHNAME;
549        }
550        return Symbol.PATHNAME;
551    }
552
553    @Override
554    public LispObject classOf() {
555        if (isURL()) {
556            return BuiltInClass.URL_PATHNAME;
557        } 
558        if (isJar()) {
559            return BuiltInClass.JAR_PATHNAME;
560        }
561        return BuiltInClass.PATHNAME;
562    }
563
564    @Override
565    public LispObject typep(LispObject type) {
566        if (type == Symbol.PATHNAME) {
567            return T;
568        }
569        if (type == Symbol.JAR_PATHNAME && isJar()) {
570            return T;
571        }
572        if (type == Symbol.URL_PATHNAME && isURL()) {
573            return T;
574        }
575        if (type == BuiltInClass.PATHNAME) {
576            return T;
577        }
578        if (type == BuiltInClass.JAR_PATHNAME && isJar()) {
579            return T;
580        }
581        if (type == BuiltInClass.URL_PATHNAME && isURL()) {
582            return T;
583        }
584        return super.typep(type);
585    }
586
587    public final LispObject getDevice() {
588        return device;
589    }
590
591    public String getNamestring() {
592        if (namestring != null) {
593            return namestring;
594        }
595        if (name == NIL && type != NIL) {
596            Debug.assertTrue(namestring == null);
597            return null;
598        }
599        if (directory instanceof AbstractString) {
600            Debug.assertTrue(false);
601        }
602        StringBuilder sb = new StringBuilder();
603        // "If a pathname is converted to a namestring, the symbols NIL and
604        // :UNSPECIFIC cause the field to be treated as if it were empty. That
605        // is, both NIL and :UNSPECIFIC cause the component not to appear in
606        // the namestring." 19.2.2.2.3.1
607        if (host != NIL) {
608            Debug.assertTrue(host instanceof AbstractString
609                             || host instanceof Cons);
610            if (host instanceof Cons) {
611                LispObject scheme = Symbol.GETF.execute(host, SCHEME, NIL);
612                LispObject authority = Symbol.GETF.execute(host, AUTHORITY, NIL);
613                Debug.assertTrue(scheme != NIL);
614                sb.append(scheme.getStringValue());
615                sb.append(":");
616                if (authority != NIL) {
617                    sb.append("//");
618                    sb.append(authority.getStringValue());
619                }
620            } else {
621                if (!(this instanceof LogicalPathname)) {
622                    sb.append("\\\\"); //UNC file support; if there's a host, it's a UNC path.
623                }
624                sb.append(host.getStringValue());
625                if (this instanceof LogicalPathname) {
626                    sb.append(':');
627                } else {
628                    sb.append(File.separatorChar);
629                }
630            }
631        }
632        if (device == NIL) {
633        } else if (device == Keyword.UNSPECIFIC) {
634        } else if (device instanceof Cons) {
635            LispObject[] jars = ((Cons) device).copyToArray();
636            StringBuilder prefix = new StringBuilder();
637            for (int i = 0; i < jars.length; i++) {
638                prefix.append("jar:");
639                if (!((Pathname)jars[i]).isURL() && i == 0) {
640                    sb.append("file:");
641                }
642                sb.append(((Pathname) jars[i]).getNamestring());
643                sb.append("!/");
644            }
645            sb = prefix.append(sb);
646        } else if (device instanceof AbstractString
647          && device.getStringValue().startsWith("jar:")) {
648            sb.append(device.getStringValue());
649        } else if (device instanceof AbstractString) {
650            sb.append(device.getStringValue());
651            if (this instanceof LogicalPathname
652              || host == NIL) {
653                sb.append(':'); // non-UNC paths
654            }
655        } else {
656            Debug.assertTrue(false);
657        }
658        String directoryNamestring = getDirectoryNamestring();
659        if (isJar()) {
660            if (directoryNamestring.startsWith("/")) {
661                sb.append(directoryNamestring.substring(1));
662            } else {
663                sb.append(directoryNamestring);
664            }
665        } else {
666            sb.append(directoryNamestring);
667        }
668        if (name instanceof AbstractString) {
669            String n = name.getStringValue();
670            if (n.indexOf(File.separatorChar) >= 0) {
671                Debug.assertTrue(namestring == null);
672                return null;
673            }
674            sb.append(n);
675        } else if (name == Keyword.WILD) {
676            sb.append('*');
677        }
678        if (type != NIL && type != Keyword.UNSPECIFIC) {
679            sb.append('.');
680            if (type instanceof AbstractString) {
681                String t = type.getStringValue();
682    // Allow Windows shortcuts to include TYPE
683    if (!(t.endsWith(".lnk") && Utilities.isPlatformWindows)) {
684        if (t.indexOf('.') >= 0) {
685      Debug.assertTrue(namestring == null);
686      return null;
687        }
688    }
689                sb.append(t);
690            } else if (type == Keyword.WILD) {
691                sb.append('*');
692            } else {
693                Debug.assertTrue(false);
694            }
695        }
696       
697        if (isURL()) {
698            LispObject o = Symbol.GETF.execute(host, QUERY, NIL);
699            if (o != NIL) {
700                sb.append("?");
701                sb.append(o.getStringValue());
702            }
703            o = Symbol.GETF.execute(host, FRAGMENT, NIL);
704            if (o != NIL) {
705                sb.append("#");
706                sb.append(o.getStringValue());
707            }
708        }
709           
710        if (this instanceof LogicalPathname) {
711            if (version.integerp()) {
712                sb.append('.');
713                int base = Fixnum.getValue(Symbol.PRINT_BASE.symbolValue());
714                if (version instanceof Fixnum) {
715                    sb.append(Integer.toString(((Fixnum) version).value, base).toUpperCase());
716                } else if (version instanceof Bignum) {
717                    sb.append(((Bignum) version).value.toString(base).toUpperCase());
718                }
719            } else if (version == Keyword.WILD) {
720                sb.append(".*");
721            } else if (version == Keyword.NEWEST) {
722                sb.append(".NEWEST");
723            }
724        }
725        namestring = sb.toString();
726        // XXX Decide when this is necessary
727        // if (isURL()) {
728        //     namestring = Utilities.uriEncode(namestring);
729        // }
730        return namestring;
731    }
732
733    protected String getDirectoryNamestring() {
734        validateDirectory(true);
735        StringBuilder sb = new StringBuilder();
736        // "If a pathname is converted to a namestring, the symbols NIL and
737        // :UNSPECIFIC cause the field to be treated as if it were empty. That
738        // is, both NIL and :UNSPECIFIC cause the component not to appear in
739        // the namestring." 19.2.2.2.3.1
740        if (directory != NIL) {
741            final char separatorChar;
742            if (isJar() || isURL()) {
743                separatorChar = '/'; 
744            } else {
745                separatorChar = File.separatorChar;
746            }
747            LispObject temp = directory;
748            LispObject part = temp.car();
749            temp = temp.cdr();
750            if (part == Keyword.ABSOLUTE) {
751                sb.append(separatorChar);
752            } else if (part == Keyword.RELATIVE) {
753                if (temp == NIL) {
754                    // #p"./"
755                    sb.append('.');
756                    sb.append(separatorChar);
757                }
758                // else: Nothing to do.
759            } else {
760                error(new FileError("Unsupported directory component "
761                  + part.writeToString() + ".",
762                  this));
763            }
764            while (temp != NIL) {
765                part = temp.car();
766                if (part instanceof AbstractString) {
767                    sb.append(part.getStringValue());
768                } else if (part == Keyword.WILD) {
769                    sb.append('*');
770                } else if (part == Keyword.WILD_INFERIORS) {
771                    sb.append("**");
772                } else if (part == Keyword.UP) {
773                    sb.append("..");
774                } else {
775                    error(new FileError("Unsupported directory component " + part.writeToString() + ".",
776                      this));
777                }
778                sb.append(separatorChar);
779                temp = temp.cdr();
780            }
781        }
782        return sb.toString();
783    }
784
785    /** @return The representation of this pathname suitable for referencing an entry in a Zip/JAR file */
786    protected String asEntryPath() {
787        Pathname p = new Pathname();
788        p.directory = directory;
789        p.name = name;
790        p.type = type;
791        p.invalidateNamestring();
792        String path = p.getNamestring();
793        StringBuilder result = new StringBuilder();
794        if (Utilities.isPlatformWindows) {
795      for (int i = 0; i < path.length(); i++) {
796    char c = path.charAt(i);
797    if (c == '\\') {
798        result.append('/');
799    } else {
800        result.append(c);
801    }
802      }
803        } else  {
804            result.append(path);
805        }
806        // Entries in jar files are always relative, but Pathname
807        // directories are :ABSOLUTE.
808        if (result.length() > 1
809          && result.substring(0, 1).equals("/")) {
810            return result.substring(1);
811        }
812        return result.toString();
813    }
814
815    @Override
816    public boolean equal(LispObject obj) {
817        if (this == obj) {
818            return true;
819        }
820        if (obj instanceof Pathname) {
821            Pathname p = (Pathname) obj;
822            if (Utilities.isPlatformWindows) {
823                if (!host.equalp(p.host)) {
824                    return false;
825                }
826                if (!device.equalp(p.device)) {
827                    return false;
828                }
829                if (!directory.equalp(p.directory)) {
830                    return false;
831                }
832                if (!name.equalp(p.name)) {
833                    return false;
834                }
835                if (!type.equalp(p.type)) {
836                    return false;
837                }
838                // Ignore version component.
839                //if (!version.equalp(p.version))
840                //    return false;
841            } else {
842                // Unix.
843                if (!host.equal(p.host)) {
844                    return false;
845                }
846                if (!device.equal(p.device)) {
847                    return false;
848                }
849                if (!directory.equal(p.directory)) {
850                    return false;
851                }
852                if (!name.equal(p.name)) {
853                    return false;
854                }
855                if (!type.equal(p.type)) {
856                    return false;
857                }
858                // Ignore version component.
859                //if (!version.equal(p.version))
860                //    return false;
861            }
862            return true;
863        }
864        return false;
865    }
866
867    @Override
868    public boolean equalp(LispObject obj) {
869        return equal(obj);
870    }
871
872    @Override
873    public int sxhash() {
874        return ((host.sxhash()
875          ^ device.sxhash()
876          ^ directory.sxhash()
877          ^ name.sxhash()
878          ^ type.sxhash()) & 0x7fffffff);
879    }
880
881    @Override
882    public String writeToString() {
883        final LispThread thread = LispThread.currentThread();
884        boolean printReadably = (Symbol.PRINT_READABLY.symbolValue(thread) != NIL);
885        boolean printEscape = (Symbol.PRINT_ESCAPE.symbolValue(thread) != NIL);
886        boolean useNamestring;
887        String s = null;
888        s = getNamestring();
889        if (s != null) {
890            useNamestring = true;
891            if (printReadably) {
892                // We have a namestring. Check for pathname components that
893                // can't be read from the namestring.
894                if ((host != NIL && !isURL())
895                    || version != NIL) 
896                {
897                    useNamestring = false;
898                } else if (name instanceof AbstractString) {
899                    String n = name.getStringValue();
900                    if (n.equals(".") || n.equals("..")) {
901                        useNamestring = false;
902                    } else if (n.indexOf(File.separatorChar) >= 0) {
903                        useNamestring = false;
904                    }
905                }
906            }
907        } else {
908            useNamestring = false;
909        }
910        StringBuilder sb = new StringBuilder();
911        if (useNamestring) {
912            if (printReadably || printEscape) {
913                sb.append("#P\"");
914            }
915            final int limit = s.length();
916            for (int i = 0; i < limit; i++) {
917                char c = s.charAt(i);
918                if (printReadably || printEscape) {
919                    if (c == '\"' || c == '\\') {
920                        sb.append('\\');
921                    }
922                }
923                sb.append(c);
924            }
925            if (printReadably || printEscape) {
926                sb.append('"');
927            }
928        } else {
929            sb.append("#P(");
930            if (host != NIL) {
931                sb.append(":HOST ");
932                sb.append(host.writeToString());
933                sb.append(' ');
934            }
935            if (device != NIL) {
936                sb.append(":DEVICE ");
937                sb.append(device.writeToString());
938                sb.append(' ');
939            }
940            if (directory != NIL) {
941                sb.append(":DIRECTORY ");
942                sb.append(directory.writeToString());
943                sb.append(" ");
944            }
945            if (name != NIL) {
946                sb.append(":NAME ");
947                sb.append(name.writeToString());
948                sb.append(' ');
949            }
950            if (type != NIL) {
951                sb.append(":TYPE ");
952                sb.append(type.writeToString());
953                sb.append(' ');
954            }
955            if (version != NIL) {
956                sb.append(":VERSION ");
957                sb.append(version.writeToString());
958                sb.append(' ');
959            }
960            if (sb.charAt(sb.length() - 1) == ' ') {
961                sb.setLength(sb.length() - 1);
962            }
963            sb.append(')');
964        }
965        return sb.toString();
966    }
967    // A logical host is represented as the string that names it.
968    // (defvar *logical-pathname-translations* (make-hash-table :test 'equal))
969    public static EqualHashTable LOGICAL_PATHNAME_TRANSLATIONS =
970      new EqualHashTable(64, NIL, NIL);
971    private static final Symbol _LOGICAL_PATHNAME_TRANSLATIONS_ =
972      exportSpecial("*LOGICAL-PATHNAME-TRANSLATIONS*", PACKAGE_SYS,
973      LOGICAL_PATHNAME_TRANSLATIONS);
974
975    public static Pathname parseNamestring(String s) {
976        return new Pathname(s);
977    }
978
979    public static boolean isValidURL(String s) {
980        try {
981            URL url = new URL(s);
982        } catch (MalformedURLException e) {
983            return false;
984        }
985        return true;
986    }
987
988    public static URL toURL(Pathname p) {
989        URL url = null;
990        if (!(p.host instanceof Cons)) {
991            Debug.assertTrue(false); // XXX
992        }
993        try {
994            url = new URL(p.getNamestring());
995        } catch (MalformedURLException e) {
996            Debug.assertTrue(false); // XXX
997        }
998        return url;
999    }
1000
1001    URLConnection getURLConnection() {
1002        Debug.assertTrue(isURL());
1003        URL url = Pathname.toURL(this);
1004        URLConnection result = null;
1005        try {
1006            result = url.openConnection();
1007        } catch (IOException e) {
1008            error(new FileError("Failed to open URL connection.",
1009                                this));
1010        }
1011        return result;
1012    }
1013
1014    public static Pathname parseNamestring(AbstractString namestring) {
1015        // Check for a logical pathname host.
1016        String s = namestring.getStringValue();
1017        if (!isValidURL(s)) {
1018            String h = getHostString(s);
1019            if (h != null && LOGICAL_PATHNAME_TRANSLATIONS.get(new SimpleString(h)) != null) {
1020                // A defined logical pathname host.
1021                return new LogicalPathname(h, s.substring(s.indexOf(':') + 1));
1022            }
1023        }
1024        return new Pathname(s);
1025    }
1026
1027    // XXX was @return Pathname
1028    public static LogicalPathname parseNamestring(AbstractString namestring,
1029                                                  AbstractString host) 
1030    {
1031        String s = namestring.getStringValue();
1032
1033        // Look for a logical pathname host in the namestring.       
1034        String h = getHostString(s);
1035        if (h != null) {
1036            if (!h.equals(host.getStringValue())) {
1037                error(new LispError("Host in " + s
1038                  + " does not match requested host "
1039                  + host.getStringValue()));
1040                // Not reached.
1041                return null;
1042            }
1043            // Remove host prefix from namestring.
1044            s = s.substring(s.indexOf(':') + 1);
1045        }
1046        if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) != null) {
1047            // A defined logical pathname host.
1048            return new LogicalPathname(host.getStringValue(), s);
1049        }
1050        error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
1051        // Not reached.
1052        return null;
1053    }
1054
1055    // "one or more uppercase letters, digits, and hyphens"
1056    protected static String getHostString(String s) {
1057        int colon = s.indexOf(':');
1058        if (colon >= 0) {
1059            return s.substring(0, colon).toUpperCase();
1060        } else {
1061            return null;
1062        }
1063    }
1064
1065    static final void checkCaseArgument(LispObject arg) {
1066        if (arg != Keyword.COMMON && arg != Keyword.LOCAL) {
1067            type_error(arg, list(Symbol.MEMBER, Keyword.COMMON,
1068              Keyword.LOCAL));
1069        }
1070    }
1071    // ### %pathname-host
1072    private static final Primitive _PATHNAME_HOST = new pf_pathname_host();
1073    private static class pf_pathname_host extends Primitive {
1074        pf_pathname_host() {
1075            super("%pathname-host", PACKAGE_SYS, false);
1076        }
1077        @Override
1078        public LispObject execute(LispObject first, LispObject second) {
1079            checkCaseArgument(second);
1080            return coerceToPathname(first).host;
1081        }
1082    }
1083    // ### %pathname-device
1084    private static final Primitive _PATHNAME_DEVICE = new pf_pathname_device(); 
1085    private static class pf_pathname_device extends Primitive {
1086        pf_pathname_device() {
1087            super("%pathname-device", PACKAGE_SYS, false);
1088        }
1089        @Override
1090        public LispObject execute(LispObject first, LispObject second) {
1091            checkCaseArgument(second);
1092            return coerceToPathname(first).device;
1093        }
1094    }
1095    // ### %pathname-directory
1096    private static final Primitive _PATHNAME_DIRECTORY = new pf_pathname_directory();
1097    private static class pf_pathname_directory extends Primitive {
1098        pf_pathname_directory() {
1099            super("%pathname-directory", PACKAGE_SYS, false);
1100        }
1101        @Override
1102        public LispObject execute(LispObject first, LispObject second) {
1103            checkCaseArgument(second);
1104            return coerceToPathname(first).directory;
1105        }
1106    }
1107    // ### %pathname-name
1108    private static final Primitive _PATHNAME_NAME = new pf_pathname_name();
1109    private static class  pf_pathname_name extends Primitive {
1110        pf_pathname_name() {
1111            super ("%pathname-name", PACKAGE_SYS, false);
1112        }
1113        @Override
1114        public LispObject execute(LispObject first, LispObject second) {
1115            checkCaseArgument(second);
1116            return coerceToPathname(first).name;
1117        }
1118    }
1119    // ### %pathname-type
1120    private static final Primitive _PATHNAME_TYPE = new pf_pathname_type();
1121    private static class pf_pathname_type extends Primitive {
1122        pf_pathname_type() {
1123            super("%pathname-type", PACKAGE_SYS, false);
1124        }
1125        @Override
1126        public LispObject execute(LispObject first, LispObject second) {
1127            checkCaseArgument(second);
1128            return coerceToPathname(first).type;
1129        }
1130    }
1131    // ### pathname-version
1132    private static final Primitive PATHNAME_VERSION = new pf_pathname_version();
1133    private static class pf_pathname_version extends Primitive {
1134        pf_pathname_version() {
1135            super("pathname-version", "pathname");
1136        }
1137        @Override
1138        public LispObject execute(LispObject arg) {
1139            return coerceToPathname(arg).version;
1140        }
1141    }
1142    // ### namestring
1143    // namestring pathname => namestring
1144    private static final Primitive NAMESTRING = new pf_namestring();
1145    private static class pf_namestring extends Primitive {
1146        pf_namestring() {
1147            super("namestring", "pathname");
1148        }
1149        @Override
1150        public LispObject execute(LispObject arg) {
1151            Pathname pathname = coerceToPathname(arg);
1152            String namestring = pathname.getNamestring();
1153            if (namestring == null) {
1154                error(new SimpleError("Pathname has no namestring: "
1155                                      + pathname.writeToString()));
1156            }
1157            return new SimpleString(namestring);
1158        }
1159    }
1160    // ### directory-namestring
1161    // directory-namestring pathname => namestring
1162    private static final Primitive DIRECTORY_NAMESTRING = new pf_directory_namestring();
1163    private static class pf_directory_namestring extends Primitive {
1164        pf_directory_namestring() {
1165            super("directory-namestring", "pathname");
1166        }
1167        @Override
1168        public LispObject execute(LispObject arg) {
1169            return new SimpleString(coerceToPathname(arg).getDirectoryNamestring());
1170        }
1171    }
1172    // ### pathname pathspec => pathname
1173    private static final Primitive PATHNAME = new pf_pathname();
1174    private static class pf_pathname extends Primitive {
1175        pf_pathname() {
1176            super("pathname", "pathspec");
1177        }
1178        @Override
1179        public LispObject execute(LispObject arg) {
1180            return coerceToPathname(arg);
1181        }
1182    }
1183    // ### %parse-namestring string host default-pathname => pathname, position
1184    private static final Primitive _PARSE_NAMESTRING = new pf_parse_namestring();
1185    private static class pf_parse_namestring extends Primitive {
1186        pf_parse_namestring() {
1187            super("%parse-namestring", PACKAGE_SYS, false,
1188                  "namestring host default-pathname");
1189        }
1190        @Override
1191        public LispObject execute(LispObject first, LispObject second, LispObject third) {
1192            final LispThread thread = LispThread.currentThread();
1193            final AbstractString namestring = checkString(first);
1194            // The HOST parameter must be a string or NIL.
1195            if (second == NIL) {
1196                // "If HOST is NIL, DEFAULT-PATHNAME is a logical pathname, and
1197                // THING is a syntactically valid logical pathname namestring
1198                // without an explicit host, then it is parsed as a logical
1199                // pathname namestring on the host that is the host component
1200                // of DEFAULT-PATHNAME."
1201                third = coerceToPathname(third);
1202                if (third instanceof LogicalPathname) {
1203                    second = ((LogicalPathname) third).host;
1204                } else {
1205                    return thread.setValues(parseNamestring(namestring),
1206                                            namestring.LENGTH());
1207                }
1208            }
1209            Debug.assertTrue(second != NIL);
1210            final AbstractString host = checkString(second);
1211            return thread.setValues(parseNamestring(namestring, host),
1212                                    namestring.LENGTH());
1213        }
1214    }
1215    // ### make-pathname
1216    private static final Primitive MAKE_PATHNAME = new pf_make_pathname();
1217    private static class pf_make_pathname extends Primitive {
1218        pf_make_pathname() {
1219            super("make-pathname",
1220                  "&key host device directory name type version defaults case");
1221        }
1222        @Override
1223        public LispObject execute(LispObject[] args) {
1224            return _makePathname(args);
1225        }
1226    }
1227
1228    // Used by the #p reader.
1229    public static final Pathname makePathname(LispObject args) {
1230        return _makePathname(args.copyToArray());
1231    }
1232
1233    public static final Pathname makePathname(File file) {
1234        String namestring = null;
1235        try {
1236            namestring = file.getCanonicalPath();
1237        } catch (IOException e) {
1238            Debug.trace("Failed to make a Pathname from "
1239              + "." + file + "'");
1240            return null;
1241        }
1242        return new Pathname(namestring);
1243    }
1244
1245    static final Pathname _makePathname(LispObject[] args) {
1246        if (args.length % 2 != 0) {
1247            error(new ProgramError("Odd number of keyword arguments."));
1248        }
1249        LispObject host = NIL;
1250        LispObject device = NIL;
1251        LispObject directory = NIL;
1252        LispObject name = NIL;
1253        LispObject type = NIL;
1254        LispObject version = NIL;
1255        Pathname defaults = null;
1256        boolean deviceSupplied = false;
1257        boolean nameSupplied = false;
1258        boolean typeSupplied = false;
1259        for (int i = 0; i < args.length; i += 2) {
1260            LispObject key = args[i];
1261            LispObject value = args[i + 1];
1262            if (key == Keyword.HOST) {
1263                host = value;
1264            } else if (key == Keyword.DEVICE) {
1265                device = value;
1266                deviceSupplied = true;
1267            } else if (key == Keyword.DIRECTORY) {
1268                if (value instanceof AbstractString) {
1269                    directory = list(Keyword.ABSOLUTE, value);
1270                } else if (value == Keyword.WILD) {
1271                    directory = list(Keyword.ABSOLUTE, Keyword.WILD);
1272                } else {
1273                    directory = value;
1274                }
1275            } else if (key == Keyword.NAME) {
1276                name = value;
1277                nameSupplied = true;
1278            } else if (key == Keyword.TYPE) {
1279                type = value;
1280                typeSupplied = true;
1281            } else if (key == Keyword.VERSION) {
1282                version = value;
1283            } else if (key == Keyword.DEFAULTS) {
1284                defaults = coerceToPathname(value);
1285            } else if (key == Keyword.CASE) {
1286                // Ignored.
1287            }
1288        }
1289        if (defaults != null) {
1290            if (host == NIL) {
1291                host = defaults.host;
1292            }
1293            if (directory == NIL && defaults != null) {
1294                directory = defaults.directory;
1295            }
1296            if (!deviceSupplied) {
1297                device = defaults.device;
1298            }
1299            if (!nameSupplied) {
1300                name = defaults.name;
1301            }
1302            if (!typeSupplied) {
1303                type = defaults.type;
1304            }
1305        }
1306        final Pathname p;
1307        final boolean logical;
1308        if (host != NIL) {
1309            if (host instanceof AbstractString) {
1310                host = LogicalPathname.canonicalizeStringComponent((AbstractString) host);
1311            }
1312            if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) == null) {
1313                // Not a defined logical pathname host.
1314                error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
1315            }
1316            p = new LogicalPathname();
1317            logical = true;
1318            p.host = host;
1319            p.device = Keyword.UNSPECIFIC;
1320        } else {
1321            p = new Pathname();
1322            logical = false;
1323        }
1324        if (device != NIL) {
1325            if (logical) {
1326                // "The device component of a logical pathname is always :UNSPECIFIC."
1327                if (device != Keyword.UNSPECIFIC) {
1328                    error(new LispError("The device component of a logical pathname must be :UNSPECIFIC."));
1329                }
1330            } else {
1331                p.device = device;
1332            }
1333        }
1334        if (directory != NIL) {
1335            if (logical) {
1336                if (directory.listp()) {
1337                    LispObject d = NIL;
1338                    while (directory != NIL) {
1339                        LispObject component = directory.car();
1340                        if (component instanceof AbstractString) {
1341                            d = d.push(LogicalPathname.canonicalizeStringComponent((AbstractString) component));
1342                        } else {
1343                            d = d.push(component);
1344                        }
1345                        directory = directory.cdr();
1346                    }
1347                    p.directory = d.nreverse();
1348                } else if (directory == Keyword.WILD || directory == Keyword.WILD_INFERIORS) {
1349                    p.directory = directory;
1350                } else {
1351                    error(new LispError("Invalid directory component for logical pathname: " + directory.writeToString()));
1352                }
1353            } else {
1354                p.directory = directory;
1355            }
1356        }
1357        if (name != NIL) {
1358            if (logical && name instanceof AbstractString) {
1359                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString) name);
1360            } else if (name instanceof AbstractString) {
1361                p.name = validateStringComponent((AbstractString) name);
1362            } else {
1363                p.name = name;
1364            }
1365        }
1366        if (type != NIL) {
1367            if (logical && type instanceof AbstractString) {
1368                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString) type);
1369            } else {
1370                p.type = type;
1371            }
1372        }
1373        p.version = version;
1374        return p;
1375    }
1376
1377    private static final AbstractString validateStringComponent(AbstractString s) {
1378        final int limit = s.length();
1379        for (int i = 0; i < limit; i++) {
1380            char c = s.charAt(i);
1381            if (c == '/' || c == '\\' && Utilities.isPlatformWindows) {
1382                error(new LispError("Invalid character #\\" + c
1383                  + " in pathname component \"" + s
1384                  + '"'));
1385                // Not reached.
1386                return null;
1387            }
1388        }
1389        return s;
1390    }
1391
1392    private final boolean validateDirectory(boolean signalError) {
1393        LispObject temp = directory;
1394        while (temp != NIL) {
1395            LispObject first = temp.car();
1396            temp = temp.cdr();
1397            if (first == Keyword.ABSOLUTE || first == Keyword.WILD_INFERIORS) {
1398                LispObject second = temp.car();
1399                if (second == Keyword.UP || second == Keyword.BACK) {
1400                    if (signalError) {
1401                        StringBuilder sb = new StringBuilder();
1402                        sb.append(first.writeToString());
1403                        sb.append(" may not be followed immediately by ");
1404                        sb.append(second.writeToString());
1405                        sb.append('.');
1406                        error(new FileError(sb.toString(), this));
1407                    }
1408                    return false;
1409                }
1410            }
1411        }
1412        return true;
1413    }
1414    // ### pathnamep
1415    private static final Primitive PATHNAMEP = new pf_pathnamep();
1416    private static class pf_pathnamep extends Primitive  {
1417        pf_pathnamep() {
1418            super("pathnamep", "object");
1419        }
1420        @Override
1421        public LispObject execute(LispObject arg) {
1422            return arg instanceof Pathname ? T : NIL;
1423        }
1424    }
1425    // ### logical-pathname-p
1426    private static final Primitive LOGICAL_PATHNAME_P = new pf_logical_pathname_p();
1427    private static class pf_logical_pathname_p extends Primitive {
1428        pf_logical_pathname_p() {
1429            super("logical-pathname-p", PACKAGE_SYS, true, "object");
1430        }
1431        @Override
1432        public LispObject execute(LispObject arg) {
1433            return arg instanceof LogicalPathname ? T : NIL;
1434        }
1435    }
1436    // ### user-homedir-pathname &optional host => pathname
1437    private static final Primitive USER_HOMEDIR_PATHNAME = new pf_user_homedir_pathname();
1438    private static class pf_user_homedir_pathname extends Primitive {
1439        pf_user_homedir_pathname() {
1440            super("user-homedir-pathname", "&optional host");
1441        }
1442        @Override
1443        public LispObject execute(LispObject[] args) {
1444            switch (args.length) {
1445            case 0: {
1446                String s = System.getProperty("user.home");
1447                if (!s.endsWith(File.separator)) {
1448                    s = s.concat(File.separator);
1449                }
1450                return new Pathname(s);
1451            }
1452            case 1:
1453                return NIL; 
1454            default:
1455                return error(new WrongNumberOfArgumentsException(this));
1456            }
1457        }
1458    }
1459
1460    // ### list-directory directory
1461    private static final Primitive LIST_DIRECTORY = new pf_list_directory();
1462    private static class pf_list_directory extends Primitive {
1463        pf_list_directory() {
1464            super("list-directory", PACKAGE_SYS, true, "directory");
1465        }
1466        @Override
1467        public LispObject execute(LispObject arg) {
1468            Pathname pathname = coerceToPathname(arg);
1469            if (pathname instanceof LogicalPathname) {
1470                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1471            }
1472
1473            LispObject result = NIL;
1474            if (pathname.isJar()) {
1475                String directory = pathname.asEntryPath();
1476                Debug.assertTrue(directory != null);  // We should only be listing directories
1477
1478                if (pathname.device.cdr() instanceof Cons) {
1479                    return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1480                }
1481
1482                if (directory.length() == 0) {
1483                    directory = "/*";
1484                } else {
1485                    if (directory.endsWith("/")) {
1486                        directory = "/" + directory + "*";
1487                    } else {
1488                        directory = "/" + directory + "/*";
1489                    }
1490                }
1491                SimpleString wildcard = new SimpleString(directory);
1492                SimpleString wildcardDirectory = new SimpleString(directory + "/");
1493
1494                ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1495                LispObject matches;
1496                for (Enumeration<? extends ZipEntry> entries = jar.entries(); 
1497                     entries.hasMoreElements();) {
1498                    ZipEntry entry = entries.nextElement();
1499                    String entryName = "/" + entry.getName();
1500
1501                    if (entryName.endsWith("/")) {
1502                        matches = Symbol.PATHNAME_MATCH_P
1503                            .execute(new SimpleString(entryName), wildcardDirectory);
1504                    } else {
1505                        matches = Symbol.PATHNAME_MATCH_P.
1506                            execute(new SimpleString(entryName), wildcard);
1507                    }
1508                    if (!matches.equals(NIL)) {
1509                        String namestring = new String(pathname.getNamestring());
1510                        namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1511                                 + entry.getName();
1512                        Pathname p = new Pathname(namestring);
1513                        result = new Cons(p, result);
1514                    }
1515                }
1516                return result;
1517            }
1518
1519            if (pathname.isURL()) {
1520                return error(new LispError("Unimplemented.")); // XXX
1521            }
1522
1523            String s = pathname.getNamestring();
1524            if (s != null) {
1525                File f = new File(s);
1526                if (f.isDirectory()) {
1527                    try {
1528                        File[] files = f.listFiles();
1529                        for (int i = files.length; i-- > 0;) {
1530                            File file = files[i];
1531                            Pathname p;
1532                            if (file.isDirectory()) {
1533                                p = Utilities.getDirectoryPathname(file);
1534                            } else {
1535                                p = new Pathname(file.getCanonicalPath());
1536                            }
1537                            result = new Cons(p, result);
1538                        }
1539                    } catch (IOException e) {
1540                        return error(new FileError("Unable to list directory " + pathname.writeToString() + ".",
1541                                                   pathname));
1542                    } catch (SecurityException e) {
1543                        Debug.trace(e);
1544                    } catch (NullPointerException e) {
1545                        Debug.trace(e);
1546                    }
1547                }
1548            }
1549            return result;
1550        }
1551    }
1552
1553    // ### match-wild-jar-pathname wild-jar-pathname
1554    static final Primitive MATCH_WILD_JAR_PATHNAME = new pf_match_wild_jar_pathname();
1555    private static class pf_match_wild_jar_pathname extends Primitive {
1556        pf_match_wild_jar_pathname() {
1557            super("match-wild-jar-pathname", PACKAGE_SYS, false, "wild-jar-pathname");
1558        }
1559        @Override
1560        public LispObject execute(LispObject arg) {
1561            Pathname pathname = coerceToPathname(arg);
1562            if (pathname instanceof LogicalPathname) {
1563                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1564            }
1565            if (!pathname.isJar()) {
1566                return new FileError("Not a jar pathname.", pathname);
1567            }
1568            if (!pathname.isWild()) {
1569                return new FileError("Not a wild pathname.", pathname);
1570            }
1571            Pathname jarPathname = new Pathname(pathname);
1572            jarPathname.directory = NIL;
1573            jarPathname.name = NIL;
1574            jarPathname.type = NIL;
1575            jarPathname.invalidateNamestring();
1576            LispObject jarTruename = truename(jarPathname, false); 
1577           
1578            // We can't match anything in a non-existent jar
1579            if (jarTruename == NIL) {
1580                return NIL;
1581            }
1582           
1583            LispObject result = NIL;
1584            String wild = "/" + pathname.asEntryPath();
1585
1586            if (pathname.device.cdr() instanceof Cons) {
1587                return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1588            }
1589           
1590            final SimpleString wildcard = new SimpleString(wild);
1591
1592            ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1593
1594            for (Enumeration<? extends ZipEntry> entries = jar.entries(); entries.hasMoreElements();) {
1595                ZipEntry entry = entries.nextElement();
1596                String entryName = "/" + entry.getName();
1597               
1598                LispObject matches = Symbol.PATHNAME_MATCH_P
1599                    .execute(new SimpleString(entryName), wildcard);
1600
1601                if (!matches.equals(NIL)) {
1602                    String namestring = new String(pathname.getNamestring());
1603                    namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1604                        + entry.getName();
1605                    Pathname p = new Pathname(namestring);
1606                    result = new Cons(p, result);
1607                }
1608            }
1609            return result;
1610        }
1611    }
1612
1613    public boolean isAbsolute()  {
1614        if (!directory.equals(NIL) || !(directory == null)) {
1615            if (directory instanceof Cons) {
1616                if (((Cons)directory).car().equals(Keyword.ABSOLUTE)) {
1617                    return true;
1618                }
1619            }
1620        }
1621        return false;
1622    }
1623
1624    // ### PATHNAME-JAR-P
1625    private static final Primitive PATHNAME_JAR_P = new pf_pathname_jar_p();
1626    private static class pf_pathname_jar_p extends Primitive {
1627        pf_pathname_jar_p() {
1628            super("pathname-jar-p", PACKAGE_EXT, true, "pathname",
1629                  "Predicate for whether PATHNAME references a JAR.");
1630        }
1631        @Override
1632        public LispObject execute(LispObject arg) {
1633            Pathname p = coerceToPathname(arg);
1634            return p.isJar() ? T : NIL;
1635        }
1636    }
1637
1638    public boolean isJar() {
1639        return (device instanceof Cons);
1640    }
1641
1642    // ### PATHNAME-URL-P
1643    private static final Primitive PATHNAME_URL_P = new pf_pathname_url_p();
1644    private static class pf_pathname_url_p extends Primitive {
1645        pf_pathname_url_p() {
1646            super("pathname-url-p", PACKAGE_EXT, true, "pathname",
1647                  "Predicate for whether PATHNAME references a URL.");
1648        }
1649        @Override
1650        public LispObject execute(LispObject arg) {
1651            Pathname p = coerceToPathname(arg);
1652            return p.isURL() ? T : NIL;
1653        }
1654    }
1655
1656    public boolean isURL() {
1657        return (host instanceof Cons);
1658    }
1659
1660    public boolean isWild() {
1661        if (host == Keyword.WILD || host == Keyword.WILD_INFERIORS) {
1662            return true;
1663        }
1664        if (device == Keyword.WILD || device == Keyword.WILD_INFERIORS) {
1665            return true;
1666        }
1667        if (directory instanceof Cons) {
1668            if (memq(Keyword.WILD, directory)) {
1669                return true;
1670            }
1671            if (memq(Keyword.WILD_INFERIORS, directory)) {
1672                return true;
1673            }
1674            Cons d = (Cons) directory;
1675            while (true) {
1676                if (d.car() instanceof AbstractString) {
1677                    String s = d.car().writeToString();
1678                    if (s.contains("*")) {
1679                        return true;
1680                    }
1681                }
1682                if (d.cdr() == NIL || ! (d.cdr() instanceof Cons)) {
1683                    break;
1684                }
1685                d = (Cons)d.cdr();
1686            }
1687        }
1688        if (name == Keyword.WILD || name == Keyword.WILD_INFERIORS) {
1689            return true;
1690        }
1691        if (name instanceof AbstractString) {
1692            if (name.writeToString().contains("*")) {
1693                return true;
1694            }
1695        }
1696        if (type == Keyword.WILD || type == Keyword.WILD_INFERIORS) {
1697            return true;
1698        }
1699        if (type instanceof AbstractString) {
1700            if (type.writeToString().contains("*")) {
1701                return true;
1702            }
1703        }
1704        if (version == Keyword.WILD || version == Keyword.WILD_INFERIORS) {
1705            return true;
1706        }
1707        return false;
1708    }
1709    // ### %wild-pathname-p
1710    private static final Primitive _WILD_PATHNAME_P = new pf_wild_pathname_p();
1711    static final class pf_wild_pathname_p extends Primitive {
1712        pf_wild_pathname_p() {
1713            super("%wild-pathname-p", PACKAGE_SYS, true);
1714        }
1715        @Override
1716        public LispObject execute(LispObject first, LispObject second) {
1717            Pathname pathname = coerceToPathname(first);
1718            if (second == NIL) {
1719                return pathname.isWild() ? T : NIL;
1720            }
1721            if (second == Keyword.DIRECTORY) {
1722                if (pathname.directory instanceof Cons) {
1723                    if (memq(Keyword.WILD, pathname.directory)) {
1724                        return T;
1725                    }
1726                    if (memq(Keyword.WILD_INFERIORS, pathname.directory)) {
1727                        return T;
1728                    }
1729                }
1730                return NIL;
1731            }
1732            LispObject value;
1733            if (second == Keyword.HOST) {
1734                value = pathname.host;
1735            } else if (second == Keyword.DEVICE) {
1736                value = pathname.device;
1737            } else if (second == Keyword.NAME) {
1738                value = pathname.name;
1739            } else if (second == Keyword.TYPE) {
1740                value = pathname.type;
1741            } else if (second == Keyword.VERSION) {
1742                value = pathname.version;
1743            } else {
1744                return error(new ProgramError("Unrecognized keyword "
1745                                              + second.writeToString() + "."));
1746            }
1747            if (value == Keyword.WILD || value == Keyword.WILD_INFERIORS) {
1748                return T;
1749            } else {
1750                return NIL;
1751            }
1752        }
1753    }
1754
1755    // ### merge-pathnames pathname &optional default-pathname default-version"
1756    private static final Primitive MERGE_PATHNAMES = new pf_merge_pathnames();
1757    static final class pf_merge_pathnames extends Primitive {
1758        pf_merge_pathnames() {
1759            super("merge-pathnames", "pathname &optional default-pathname default-version");
1760        }
1761        @Override
1762        public LispObject execute(LispObject arg) {
1763            Pathname pathname = coerceToPathname(arg);
1764            Pathname defaultPathname =
1765                coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
1766            LispObject defaultVersion = Keyword.NEWEST;
1767            return mergePathnames(pathname, defaultPathname, defaultVersion);
1768        }
1769        @Override
1770        public LispObject execute(LispObject first, LispObject second) {
1771            Pathname pathname = coerceToPathname(first);
1772            Pathname defaultPathname =
1773                coerceToPathname(second);
1774            LispObject defaultVersion = Keyword.NEWEST;
1775            return mergePathnames(pathname, defaultPathname, defaultVersion);
1776        }
1777        @Override
1778        public LispObject execute(LispObject first, LispObject second,
1779                                  LispObject third) {
1780            Pathname pathname = coerceToPathname(first);
1781            Pathname defaultPathname =
1782                coerceToPathname(second);
1783            LispObject defaultVersion = third;
1784            return mergePathnames(pathname, defaultPathname, defaultVersion);
1785        }
1786    }
1787
1788    public static final Pathname mergePathnames(Pathname pathname, Pathname defaultPathname) {
1789        return mergePathnames(pathname, defaultPathname, Keyword.NEWEST);
1790    }
1791
1792    public static final Pathname mergePathnames(final Pathname pathname,
1793                                                final Pathname defaultPathname,
1794                                                final LispObject defaultVersion) 
1795    {
1796        Pathname result;
1797        Pathname p = new Pathname(pathname);
1798        Pathname d;
1799
1800        if (pathname instanceof LogicalPathname) {
1801            result = new LogicalPathname();
1802            d = new Pathname(defaultPathname);
1803        } else {
1804            result = new Pathname();
1805            if (defaultPathname instanceof LogicalPathname) {
1806                d = LogicalPathname.translateLogicalPathname((LogicalPathname) defaultPathname);
1807            } else {
1808                d = new Pathname(defaultPathname);
1809            }
1810        }
1811        if (pathname.host != NIL) {
1812            result.host = p.host;
1813        } else {
1814            result.host = d.host;
1815        }
1816
1817        if (pathname.device != NIL) { // XXX if device represent JARs we want to merge
1818            result.device = p.device;
1819        } else {
1820            if (!p.isURL()) {
1821                result.device = d.device;
1822            }
1823        }
1824
1825        if (pathname.isJar()) {
1826            Cons jars = (Cons)result.device;
1827            LispObject jar = jars.car;
1828            if (jar instanceof Pathname) {
1829                Pathname defaults = new Pathname(d);
1830                if (defaults.isJar()) {
1831                    defaults.device = NIL;
1832                }
1833                Pathname o = mergePathnames((Pathname)jar, defaults);
1834                if (o.directory instanceof Cons
1835                    && ((Cons)o.directory).length() == 1) { // i.e. (:ABSOLUTE) or (:RELATIVE)
1836                    o.directory = NIL;
1837                }
1838                ((Cons)result.device).car = o;
1839            }
1840            result.directory = p.directory;
1841        } else {
1842            result.directory = mergeDirectories(p.directory, d.directory);
1843        }
1844
1845        if (pathname.name != NIL) {
1846            result.name = p.name;
1847        } else {
1848            result.name = d.name;
1849        }
1850        if (pathname.type != NIL) {
1851            result.type = p.type;
1852        } else {
1853            result.type = d.type;
1854        }
1855        if (pathname.version != NIL) {
1856            result.version = pathname.version;
1857        } else if (pathname.name instanceof AbstractString) {
1858            result.version = defaultVersion;
1859        } else if (defaultPathname.version != NIL) {
1860            result.version = defaultPathname.version;
1861        } else {
1862            result.version = defaultVersion;
1863        }
1864        if (pathname instanceof LogicalPathname) {
1865            // When we're returning a logical
1866            result.device = Keyword.UNSPECIFIC;
1867            if (result.directory.listp()) {
1868                LispObject original = result.directory;
1869                LispObject canonical = NIL;
1870                while (original != NIL) {
1871                    LispObject component = original.car();
1872                    if (component instanceof AbstractString) {
1873                        component = LogicalPathname.canonicalizeStringComponent((AbstractString) component);
1874                    }
1875                    canonical = canonical.push(component);
1876                    original = original.cdr();
1877                }
1878                result.directory = canonical.nreverse();
1879            }
1880            if (result.name instanceof AbstractString) {
1881                result.name = LogicalPathname.canonicalizeStringComponent((AbstractString) result.name);
1882            }
1883            if (result.type instanceof AbstractString) {
1884                result.type = LogicalPathname.canonicalizeStringComponent((AbstractString) result.type);
1885            }
1886        }
1887        result.invalidateNamestring();
1888        return result;
1889    }
1890
1891    private static final LispObject mergeDirectories(LispObject dir,
1892                                                     LispObject defaultDir) {
1893        if (dir == NIL) {
1894            return defaultDir;
1895        }
1896        if (dir.car() == Keyword.RELATIVE && defaultDir != NIL) {
1897            LispObject result = NIL;
1898            while (defaultDir != NIL) {
1899                result = new Cons(defaultDir.car(), result);
1900                defaultDir = defaultDir.cdr();
1901            }
1902            dir = dir.cdr(); // Skip :RELATIVE.
1903            while (dir != NIL) {
1904                result = new Cons(dir.car(), result);
1905                dir = dir.cdr();
1906            }
1907            LispObject[] array = result.copyToArray();
1908            for (int i = 0; i < array.length - 1; i++) {
1909                if (array[i] == Keyword.BACK) {
1910                    if (array[i + 1] instanceof AbstractString || array[i + 1] == Keyword.WILD) {
1911                        array[i] = null;
1912                        array[i + 1] = null;
1913                    }
1914                }
1915            }
1916            result = NIL;
1917            for (int i = 0; i < array.length; i++) {
1918                if (array[i] != null) {
1919                    result = new Cons(array[i], result);
1920                }
1921            }
1922            return result;
1923        }
1924        return dir;
1925    }
1926
1927    public static final LispObject truename(Pathname pathname) {
1928        return truename(pathname, false);
1929    }
1930
1931    public static final LispObject truename(LispObject arg) {
1932        return truename(arg, false);
1933    }
1934
1935    public static final LispObject truename(LispObject arg, boolean errorIfDoesNotExist) {
1936        final Pathname pathname = coerceToPathname(arg);
1937        return truename(pathname, errorIfDoesNotExist);
1938    }
1939
1940    /** @return The canonical TRUENAME as a Pathname if the pathname
1941     * exists, otherwise returns NIL or possibly a subtype of
1942     * LispError if there are logical problems with the input.
1943     */
1944    public static final LispObject truename(Pathname pathname,
1945                                            boolean errorIfDoesNotExist) 
1946    {
1947        if (pathname instanceof LogicalPathname) {
1948            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1949        }
1950        if (pathname.isWild()) {
1951            return error(new FileError("Bad place for a wild pathname.",
1952                                       pathname));
1953        }
1954        if (!(pathname.isJar() || pathname.isURL())) {
1955            pathname
1956                = mergePathnames(pathname,
1957                                 coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1958                                 NIL);
1959            final String namestring = pathname.getNamestring();
1960            if (namestring == null) {
1961                return error(new FileError("Pathname has no namestring: " 
1962                                           + pathname.writeToString(),
1963                                           pathname));
1964            }
1965           
1966            final File file = new File(namestring);
1967            if (file.isDirectory()) {
1968                return Utilities.getDirectoryPathname(file);
1969            }
1970            if (file.exists()) {
1971                try {
1972                    return new Pathname(file.getCanonicalPath());
1973                } catch (IOException e) {
1974                    return error(new FileError(e.getMessage(), pathname));
1975                }
1976            }
1977        } else if (pathname.isURL()) {
1978            if (pathname.getInputStream() != null) {
1979              // If there is no type, query or fragment, we check to
1980              // see if there is URL available "underneath".
1981              if (pathname.name != NIL
1982                  && pathname.type == NIL
1983                  && Symbol.GETF.execute(pathname.host, QUERY, NIL) == NIL
1984                  && Symbol.GETF.execute(pathname.host, FRAGMENT, NIL) == NIL) {
1985                Pathname p = new Pathname(pathname.getNamestring() + "/");
1986                if (p.getInputStream() != null) {
1987                  return p;
1988                }
1989              }
1990              return pathname;
1991            }
1992        } else
1993        jarfile: {
1994            // Possibly canonicalize jar file directory
1995            Cons jars = (Cons) pathname.device;
1996            LispObject o = jars.car();
1997            if (o instanceof Pathname && ! (((Pathname)o).isURL())) {
1998                LispObject truename = Pathname.truename((Pathname)o, errorIfDoesNotExist);
1999                if (truename != null
2000                    && truename instanceof Pathname) {
2001                    jars.car = (Pathname)truename;
2002                } else {
2003                    break jarfile;
2004                }
2005            }
2006
2007            // Check for existence of a JAR file and/or JarEntry
2008            //
2009            // Cases:
2010            // 1.  JAR
2011            // 2.  JAR in JAR
2012            // 3.  JAR with Entry
2013            // 4.  JAR in JAR with Entry
2014            ZipFile jarFile = ZipCache.get((Pathname)jars.car());
2015            String entryPath = pathname.asEntryPath();
2016            if (jarFile != null) {
2017                if (jars.cdr() instanceof Cons) {
2018                  Pathname inner = (Pathname) jars.cdr().car();
2019                  InputStream inputStream = Utilities.getInputStream(jarFile, inner);
2020                  if (inputStream != null) {
2021                      if (entryPath.length() == 0) {
2022                          return pathname; // Case 2
2023                      } else {
2024                          ZipInputStream zipInputStream
2025                              = new ZipInputStream(inputStream);
2026                          ZipEntry entry = Utilities.getEntry(zipInputStream,
2027                                                              entryPath,
2028                                                              false);
2029                          if (entry != null) {
2030                              // XXX this could possibly be a directory?
2031                              return pathname; // Case 4
2032                         }
2033                      }
2034                  }
2035                } else {
2036                    if (entryPath.length() == 0) {
2037                        return pathname; // Case 1
2038                    } else {
2039                        ZipEntry entry = jarFile.getEntry(entryPath);
2040                        if (entry != null) {
2041                            // ensure this isn't a directory
2042                            try {
2043                                InputStream input = jarFile.getInputStream(entry);
2044                                if (input != null) {
2045                                    return pathname; // Case 3
2046                                }
2047                            } catch (IOException e) {
2048                                break jarfile;
2049                            }
2050                        }
2051                    }
2052                }
2053            }
2054        }
2055        error:
2056        if (errorIfDoesNotExist) {
2057            StringBuilder sb = new StringBuilder("The file ");
2058            sb.append(pathname.writeToString());
2059            sb.append(" does not exist.");
2060            return error(new FileError(sb.toString(), pathname));
2061        }
2062        return NIL;
2063    }
2064
2065
2066    /** Make a JarURL from a Pathname that references a file */
2067    private static URL makeJarURL(Pathname p) {
2068        String jarURL = "jar:file:" + p.getNamestring() + "!/";
2069        URL result = null;
2070        try {
2071            result = new URL(jarURL);
2072        } catch (MalformedURLException ex) {
2073            // XXX
2074            Debug.trace("Could not form URL from pathname "
2075              + "'" + jarURL + "'"
2076              + " because " + ex);
2077        }
2078        return result;
2079    }
2080
2081    protected static URL makeURL(Pathname pathname) {
2082        URL result = null;
2083        try {
2084            if (pathname.isURL()) {
2085                result = new URL(pathname.getNamestring());
2086            } else {
2087                // XXX ensure that we have cannonical path.
2088                result = new URL("file://" + pathname.getNamestring());
2089            }
2090        } catch (MalformedURLException e) {
2091            Debug.trace("Could not form URL from " + pathname);
2092        }
2093        return result;
2094    }
2095
2096    public InputStream getInputStream() {
2097        InputStream result = null;
2098        if (isJar()) {
2099            String entryPath = asEntryPath();
2100            // XXX We only return the bytes of an entry in a JAR
2101            Debug.assertTrue(entryPath != null);
2102            ZipFile jarFile = ZipCache.get((Pathname)device.car());
2103            Debug.assertTrue(jarFile != null);
2104            // Is this a JAR within a JAR?
2105            if (device.cdr() instanceof Cons) {
2106                Pathname inner = (Pathname) device.cdr().car();
2107                InputStream input = Utilities.getInputStream(jarFile, inner);
2108                ZipInputStream zipInputStream = new ZipInputStream(input);
2109                result =  Utilities.getEntryAsInputStream(zipInputStream, entryPath);
2110            } else {
2111                ZipEntry entry = jarFile.getEntry(entryPath);
2112    if (entry == null) {
2113        Debug.trace("Failed to get InputStream for "   
2114        + "'" + getNamestring() + "'");
2115                    // XXX should this be fatal?
2116        Debug.assertTrue(false);
2117    }
2118                try {
2119                    result = jarFile.getInputStream(entry);
2120                } catch (IOException e) {
2121                    Debug.warn("Failed to get InputStream from "
2122                                + "'" + getNamestring() + "'"
2123                                + ": " + e);
2124                }
2125            }
2126        } else if (isURL()) {
2127            URL url = toURL(this);
2128            try { 
2129                result = url.openStream();
2130            } catch (IOException e) {
2131                Debug.warn("Failed to get InputStream from "
2132                            + "'" + getNamestring() + "'"
2133                            + ": " + e);
2134            }
2135        } else {
2136            File file = Utilities.getFile(this);
2137            try { 
2138                result = new FileInputStream(file);
2139            } catch (IOException e) {
2140                Debug.warn("Failed to get InputStream from "
2141                            + "'" + getNamestring() + "'"
2142                            + ": " + e);
2143            }
2144        }
2145        return result;
2146    }
2147
2148    /** @return Time in milliseconds since the UNIX epoch at which the
2149     * resource was last modified, or 0 if the time is unknown.
2150     */
2151    public long getLastModified() {
2152        if (!(isJar() || isURL())) {
2153            File f = Utilities.getFile(this);
2154            return f.lastModified();
2155        }
2156
2157        if (isJar()) {
2158            // JAR cases
2159            // 0.  JAR from URL
2160            // 1.  JAR
2161            // 2.  JAR in JAR
2162            // 3.  Entry in JAR
2163            // 4.  Entry in JAR in JAR
2164            String entryPath = asEntryPath();
2165            Cons d = (Cons)device;
2166            if (d.cdr().equals(NIL)) {
2167                if (entryPath.length() == 0) {
2168                    LispObject o = d.car();
2169                        // 0. JAR from URL
2170                        // 1. JAR
2171                    return ((Pathname)o).getLastModified();
2172                } else {
2173                    // 3. Entry in JAR
2174                    final ZipEntry entry
2175                        = ZipCache.get((Pathname)device.car()).getEntry(entryPath);
2176                    if (entry == null) {
2177                        return 0;
2178                    }
2179                    final long time = entry.getTime();
2180                    if (time == -1) {
2181                        return 0;
2182                    }
2183                    return time;
2184                }
2185            } else {
2186                ZipFile outerJar = ZipCache.get((Pathname)d.car());
2187                if (entryPath.length() == 0) {
2188                    // 4.  JAR in JAR
2189                    String jarPath = ((Pathname)d.cdr()).asEntryPath();
2190                    final ZipEntry entry = outerJar.getEntry(jarPath);
2191                    final long time = entry.getTime();
2192                    if (time == -1) {
2193                        return 0;
2194                    }
2195                    return time;
2196                } else {
2197                    // 5. Entry in JAR in JAR
2198                    String innerJarPath = ((Pathname)d.cdr()).asEntryPath();
2199                    ZipEntry entry = outerJar.getEntry(entryPath);
2200                    ZipInputStream innerJarInputStream
2201                        = Utilities.getZipInputStream(outerJar, innerJarPath);
2202                    ZipEntry innerEntry = Utilities.getEntry(innerJarInputStream,
2203                                                             entryPath);
2204                    long time = innerEntry.getTime();
2205                    if (time == -1) {
2206                        return 0;
2207                    }
2208                    return time;
2209                }
2210            }
2211        }
2212        if (isURL()) {
2213            return getURLConnection().getLastModified();
2214        }
2215        return 0;
2216    }
2217
2218    // ### mkdir pathname
2219    private static final Primitive MKDIR = new pf_mkdir();
2220    private static class pf_mkdir extends Primitive {
2221        pf_mkdir() {
2222            super("mkdir", PACKAGE_SYS, false, "pathname");
2223        }
2224
2225        @Override
2226        public LispObject execute(LispObject arg) {
2227            final Pathname pathname = coerceToPathname(arg);
2228            if (pathname.isWild()) {
2229                error(new FileError("Bad place for a wild pathname.", pathname));
2230            }
2231            Pathname defaultedPathname =
2232                mergePathnames(pathname,
2233                               coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
2234                               NIL);
2235            if (defaultedPathname.isURL() || defaultedPathname.isJar()) {
2236                return new FileError("Cannot mkdir with a " 
2237                                     + (defaultedPathname.isURL() ? "URL" : "jar")
2238                                     + " Pathname.",
2239                                     defaultedPathname);
2240            }
2241                   
2242            File file = Utilities.getFile(defaultedPathname);
2243            return file.mkdir() ? T : NIL;
2244        }
2245    }
2246
2247    // ### rename-file filespec new-name => defaulted-new-name, old-truename, new-truename
2248    private static final Primitive RENAME_FILE = new pf_rename_file();
2249    private static class pf_rename_file extends Primitive {
2250        pf_rename_file() {
2251            super("rename-file", "filespec new-name");
2252        }
2253        @Override
2254        public LispObject execute(LispObject first, LispObject second) {
2255            final Pathname original = (Pathname) truename(first, true);
2256            final String originalNamestring = original.getNamestring();
2257            Pathname newName = coerceToPathname(second);
2258            if (newName.isWild()) {
2259                error(new FileError("Bad place for a wild pathname.", newName));
2260            }
2261            if (original.isJar()) {
2262                error(new FileError("Bad place for a jar pathname.", original));
2263            }
2264            if (newName.isJar()) {
2265                error(new FileError("Bad place for a jar pathname.", newName));
2266            }
2267            if (original.isURL()) {
2268                error(new FileError("Bad place for a URL pathname.", original));
2269            }
2270            if (newName.isURL()) {
2271                error(new FileError("Bad place for a jar pathname.", newName));
2272            }
2273               
2274            newName = mergePathnames(newName, original, NIL);
2275            final String newNamestring;
2276            if (newName instanceof LogicalPathname) {
2277                newNamestring = LogicalPathname.translateLogicalPathname((LogicalPathname) newName).getNamestring();
2278            } else {
2279                newNamestring = newName.getNamestring();
2280            }
2281            if (originalNamestring != null && newNamestring != null) {
2282                final File source = new File(originalNamestring);
2283                final File destination = new File(newNamestring);
2284                if (Utilities.isPlatformWindows) {
2285                    if (destination.isFile()) {
2286      ZipCache.remove(destination);
2287                        destination.delete();
2288                    }
2289                }
2290                if (source.renameTo(destination)) { // Success!
2291                        return LispThread.currentThread().setValues(newName, original,
2292                                                                    truename(newName, true));
2293                }
2294            }
2295            return error(new FileError("Unable to rename "
2296                                       + original.writeToString()
2297                                       + " to " + newName.writeToString()
2298                                       + "."));
2299        }
2300    }
2301
2302    // ### file-namestring pathname => namestring
2303    private static final Primitive FILE_NAMESTRING = new pf_file_namestring();
2304    private static class pf_file_namestring extends Primitive {
2305        pf_file_namestring() {
2306            super("file-namestring", "pathname");
2307        }
2308        @Override
2309        public LispObject execute(LispObject arg) {
2310            Pathname p = coerceToPathname(arg);
2311            StringBuilder sb = new StringBuilder();
2312            if (p.name instanceof AbstractString) {
2313                sb.append(p.name.getStringValue());
2314            } else if (p.name == Keyword.WILD) {
2315                sb.append('*');
2316            } else {
2317                return NIL;
2318            }
2319            if (p.type instanceof AbstractString) {
2320                sb.append('.');
2321                sb.append(p.type.getStringValue());
2322            } else if (p.type == Keyword.WILD) {
2323                sb.append(".*");
2324            }
2325            return new SimpleString(sb);
2326        }
2327    }
2328
2329    // ### host-namestring pathname => namestring
2330    private static final Primitive HOST_NAMESTRING = new pf_host_namestring();
2331    private static class pf_host_namestring extends Primitive {
2332        pf_host_namestring() {
2333            super("host-namestring", "pathname");
2334        }
2335        @Override
2336        public LispObject execute(LispObject arg) {
2337            return coerceToPathname(arg).host; // XXX URL-PATHNAME
2338        }
2339    }
2340   
2341    public String toString() {
2342        return getNamestring();
2343    }
2344
2345    static {
2346        LispObject obj = Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue();
2347        Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(coerceToPathname(obj));
2348    }
2349
2350}
2351
Note: See TracBrowser for help on using the repository browser.