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

Last change on this file was 12810, checked in by Mark Evenson, 14 years ago

PATHNAME without namestring now has a non-printable representation.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 85.6 KB
Line 
1/*
2 * Pathname.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Pathname.java 12810 2010-07-17 10:26:33Z 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 volatile 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("\\\\")) { // XXX What if string starts with '//'?
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 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("\\")) {
405                s = s.replace("\\", "/");
406            } 
407        }
408
409        // Expand user home directories
410        if (Utilities.isPlatformUnix) {
411            if (s.equals("~")) {
412                s = System.getProperty("user.home").concat("/");
413            } else if (s.startsWith("~/")) {
414                s = System.getProperty("user.home").concat(s.substring(1));
415            }
416        }
417        namestring = s;
418        if (Utilities.isPlatformWindows) {
419            if (s.length() >= 2 && s.charAt(1) == ':') {
420                device = new SimpleString(s.charAt(0));
421                s = s.substring(2);
422            }
423        }
424        String d = null;
425        // Find last file separator char.
426        for (int i = s.length(); i-- > 0;) {
427            if (s.charAt(i) == '/') {
428                d = s.substring(0, i + 1);
429                s = s.substring(i + 1);
430                break;
431            }
432        }
433        if (d != null) {
434            if (s.equals("..")) {
435                d = d.concat(s);
436                s = "";
437            }
438            directory = parseDirectory(d);
439        }
440        if (s.startsWith(".") 
441            // No TYPE can be parsed
442            && (s.indexOf(".", 1) == -1 
443                || s.substring(s.length() -1).equals("."))) {
444            name = new SimpleString(s);
445            return;
446        }
447        int index = s.lastIndexOf('.');
448        String n = null;
449        String t = null;
450        if (index > 0) {
451            n = s.substring(0, index);
452            t = s.substring(index + 1);
453        } else if (s.length() > 0) {
454            n = s;
455        }
456        if (n != null) {
457            if (n.equals("*")) {
458                name = Keyword.WILD;
459            } else {
460                name = new SimpleString(n);
461            }
462        }
463        if (t != null) {
464            if (t.equals("*")) {
465                type = Keyword.WILD;
466            } else {
467                type = new SimpleString(t);
468            }
469        }
470    }
471
472    private static final LispObject parseDirectory(String d) {
473        if (d.equals("/") || (Utilities.isPlatformWindows && d.equals("\\"))) {
474            return new Cons(Keyword.ABSOLUTE);
475        }
476        LispObject result;
477        if (d.startsWith("/") || (Utilities.isPlatformWindows && d.startsWith("\\"))) {
478            result = new Cons(Keyword.ABSOLUTE);
479        } else {
480            result = new Cons(Keyword.RELATIVE);
481        }
482        StringTokenizer st = new StringTokenizer(d, "/\\");
483        while (st.hasMoreTokens()) {
484            String token = st.nextToken();
485            LispObject obj;
486            if (token.equals("*")) {
487                obj = Keyword.WILD;
488            } else if (token.equals("**")) {
489                obj = Keyword.WILD_INFERIORS;
490            } else if (token.equals("..")) {
491                if (result.car() instanceof AbstractString) {
492                    result = result.cdr();
493                    continue;
494                }
495                obj = Keyword.UP;
496            } else {
497                obj = new SimpleString(token);
498            }
499            result = new Cons(obj, result);
500        }
501        return result.nreverse();
502    }
503
504    @Override
505    public LispObject getParts() {
506        LispObject parts = NIL;
507        parts = parts.push(new Cons("HOST", host));
508        parts = parts.push(new Cons("DEVICE", device));
509        parts = parts.push(new Cons("DIRECTORY", directory));
510        parts = parts.push(new Cons("NAME", name));
511        parts = parts.push(new Cons("TYPE", type));
512        parts = parts.push(new Cons("VERSION", version));
513        return parts.nreverse();
514    }
515
516    @Override
517    public LispObject typeOf() {
518        if (isURL()) {
519            return Symbol.URL_PATHNAME;
520        } 
521        if (isJar()) {
522            return Symbol.JAR_PATHNAME;
523        }
524        return Symbol.PATHNAME;
525    }
526
527    @Override
528    public LispObject classOf() {
529        if (isURL()) {
530            return BuiltInClass.URL_PATHNAME;
531        } 
532        if (isJar()) {
533            return BuiltInClass.JAR_PATHNAME;
534        }
535        return BuiltInClass.PATHNAME;
536    }
537
538    @Override
539    public LispObject typep(LispObject type) {
540        if (type == Symbol.PATHNAME) {
541            return T;
542        }
543        if (type == Symbol.JAR_PATHNAME && isJar()) {
544            return T;
545        }
546        if (type == Symbol.URL_PATHNAME && isURL()) {
547            return T;
548        }
549        if (type == BuiltInClass.PATHNAME) {
550            return T;
551        }
552        if (type == BuiltInClass.JAR_PATHNAME && isJar()) {
553            return T;
554        }
555        if (type == BuiltInClass.URL_PATHNAME && isURL()) {
556            return T;
557        }
558        return super.typep(type);
559    }
560
561    public final LispObject getDevice() {
562        return device;
563    }
564
565    public String getNamestring() {
566        if (namestring != null) {
567            return namestring;
568        }
569        if (name == NIL && type != NIL) {
570            Debug.assertTrue(namestring == null);
571            return null;
572        }
573        if (directory instanceof AbstractString) {
574            Debug.assertTrue(false);
575        }
576        StringBuilder sb = new StringBuilder();
577        // "If a pathname is converted to a namestring, the symbols NIL and
578        // :UNSPECIFIC cause the field to be treated as if it were empty. That
579        // is, both NIL and :UNSPECIFIC cause the component not to appear in
580        // the namestring." 19.2.2.2.3.1
581        if (host != NIL) {
582            Debug.assertTrue(host instanceof AbstractString
583                             || isURL());
584            if (isURL()) {
585                LispObject scheme = Symbol.GETF.execute(host, SCHEME, NIL);
586                LispObject authority = Symbol.GETF.execute(host, AUTHORITY, NIL);
587                Debug.assertTrue(scheme != NIL);
588                sb.append(scheme.getStringValue());
589                sb.append(":");
590                if (authority != NIL) {
591                    sb.append("//");
592                    sb.append(authority.getStringValue());
593                }
594            } else if (this instanceof LogicalPathname) {
595                sb.append(host.getStringValue());
596                sb.append(':');
597            } else { 
598                // UNC paths now use unprintable representation
599                return null;
600            }
601        }
602        if (device == NIL) {
603        } else if (device == Keyword.UNSPECIFIC) {
604        } else if (isJar()) {
605            LispObject[] jars = ((Cons) device).copyToArray();
606            StringBuilder prefix = new StringBuilder();
607            for (int i = 0; i < jars.length; i++) {
608                prefix.append("jar:");
609                if (!((Pathname)jars[i]).isURL() && i == 0) {
610                    sb.append("file:");
611                }
612                sb.append(((Pathname) jars[i]).getNamestring());
613                sb.append("!/");
614            }
615            sb = prefix.append(sb);
616        } else if (device instanceof AbstractString) {
617            sb.append(device.getStringValue());
618            if (this instanceof LogicalPathname
619              || host == NIL) {
620                sb.append(':'); // non-UNC paths
621            }
622        } else {
623            Debug.assertTrue(false);
624        }
625        String directoryNamestring = getDirectoryNamestring();
626        if (isJar()) {
627            if (directoryNamestring.startsWith("/")) {
628                sb.append(directoryNamestring.substring(1));
629            } else {
630                sb.append(directoryNamestring);
631            }
632        } else {
633            sb.append(directoryNamestring);
634        }
635        if (name instanceof AbstractString) {
636            String n = name.getStringValue();
637            if (n.indexOf('/') >= 0) {
638                Debug.assertTrue(namestring == null);
639                return null;
640            }
641            sb.append(n);
642        } else if (name == Keyword.WILD) {
643            sb.append('*');
644        }
645        if (type != NIL && type != Keyword.UNSPECIFIC) {
646            sb.append('.');
647            if (type instanceof AbstractString) {
648                String t = type.getStringValue();
649    // Allow Windows shortcuts to include TYPE
650    if (!(t.endsWith(".lnk") && Utilities.isPlatformWindows)) {
651        if (t.indexOf('.') >= 0) {
652      Debug.assertTrue(namestring == null);
653      return null;
654        }
655    }
656                sb.append(t);
657            } else if (type == Keyword.WILD) {
658                sb.append('*');
659            } else {
660                Debug.assertTrue(false);
661            }
662        }
663       
664        if (isURL()) {
665            LispObject o = Symbol.GETF.execute(host, QUERY, NIL);
666            if (o != NIL) {
667                sb.append("?");
668                sb.append(o.getStringValue());
669            }
670            o = Symbol.GETF.execute(host, FRAGMENT, NIL);
671            if (o != NIL) {
672                sb.append("#");
673                sb.append(o.getStringValue());
674            }
675        }
676           
677        if (this instanceof LogicalPathname) {
678            if (version.integerp()) {
679                sb.append('.');
680                int base = Fixnum.getValue(Symbol.PRINT_BASE.symbolValue());
681                if (version instanceof Fixnum) {
682                    sb.append(Integer.toString(((Fixnum) version).value, base).toUpperCase());
683                } else if (version instanceof Bignum) {
684                    sb.append(((Bignum) version).value.toString(base).toUpperCase());
685                }
686            } else if (version == Keyword.WILD) {
687                sb.append(".*");
688            } else if (version == Keyword.NEWEST) {
689                sb.append(".NEWEST");
690            }
691        }
692        namestring = sb.toString();
693        // XXX Decide if this is necessary
694        // if (isURL()) {
695        //     namestring = Utilities.uriEncode(namestring);
696        // }
697        return namestring;
698    }
699
700    protected String getDirectoryNamestring() {
701        validateDirectory(true);
702        StringBuilder sb = new StringBuilder();
703        // "If a pathname is converted to a namestring, the symbols NIL and
704        // :UNSPECIFIC cause the field to be treated as if it were empty. That
705        // is, both NIL and :UNSPECIFIC cause the component not to appear in
706        // the namestring." 19.2.2.2.3.1
707        if (directory != NIL) {
708            final char separatorChar = '/';
709            LispObject temp = directory;
710            LispObject part = temp.car();
711            temp = temp.cdr();
712            if (part == Keyword.ABSOLUTE) {
713                sb.append(separatorChar);
714            } else if (part == Keyword.RELATIVE) {
715                if (temp == NIL) {
716                    // #p"./"
717                    sb.append('.');
718                    sb.append(separatorChar);
719                }
720                // else: Nothing to do.
721            } else {
722                error(new FileError("Unsupported directory component "
723                  + part.writeToString() + ".",
724                  this));
725            }
726            while (temp != NIL) {
727                part = temp.car();
728                if (part instanceof AbstractString) {
729                    sb.append(part.getStringValue());
730                } else if (part == Keyword.WILD) {
731                    sb.append('*');
732                } else if (part == Keyword.WILD_INFERIORS) {
733                    sb.append("**");
734                } else if (part == Keyword.UP) {
735                    sb.append("..");
736                } else {
737                    error(new FileError("Unsupported directory component " + part.writeToString() + ".",
738                      this));
739                }
740                sb.append(separatorChar);
741                temp = temp.cdr();
742            }
743        }
744        return sb.toString();
745    }
746
747    /** @return The representation of this pathname suitable for referencing an entry in a Zip/JAR file */
748    protected String asEntryPath() {
749        Pathname p = new Pathname();
750        p.directory = directory;
751        p.name = name;
752        p.type = type;
753        p.invalidateNamestring();
754        String path = p.getNamestring();
755        StringBuilder result = new StringBuilder();
756        result.append(path);
757
758        // Entries in jar files are always relative, but Pathname
759        // directories are :ABSOLUTE.
760        if (result.length() > 1
761          && result.substring(0, 1).equals("/")) {
762            return result.substring(1);
763        }
764        return result.toString();
765    }
766
767    @Override
768    public boolean equal(LispObject obj) {
769        if (this == obj) {
770            return true;
771        }
772        if (obj instanceof Pathname) {
773            Pathname p = (Pathname) obj;
774            if (Utilities.isPlatformWindows) {
775                if (!host.equalp(p.host)) {
776                    return false;
777                }
778                if (!device.equalp(p.device)) {
779                    return false;
780                }
781                if (!directory.equalp(p.directory)) {
782                    return false;
783                }
784                if (!name.equalp(p.name)) {
785                    return false;
786                }
787                if (!type.equalp(p.type)) {
788                    return false;
789                }
790                // Ignore version component.
791                //if (!version.equalp(p.version))
792                //    return false;
793            } else {
794                // Unix.
795                if (!host.equal(p.host)) {
796                    return false;
797                }
798                if (!device.equal(p.device)) {
799                    return false;
800                }
801                if (!directory.equal(p.directory)) {
802                    return false;
803                }
804                if (!name.equal(p.name)) {
805                    return false;
806                }
807                if (!type.equal(p.type)) {
808                    return false;
809                }
810                // Ignore version component.
811                //if (!version.equal(p.version))
812                //    return false;
813            }
814            return true;
815        }
816        return false;
817    }
818
819    @Override
820    public boolean equalp(LispObject obj) {
821        return equal(obj);
822    }
823
824    @Override
825    public int sxhash() {
826        return ((host.sxhash()
827          ^ device.sxhash()
828          ^ directory.sxhash()
829          ^ name.sxhash()
830          ^ type.sxhash()) & 0x7fffffff);
831    }
832
833    @Override
834    public String writeToString() {
835        final LispThread thread = LispThread.currentThread();
836        final boolean printReadably = (Symbol.PRINT_READABLY.symbolValue(thread) != NIL);
837        final boolean printEscape = (Symbol.PRINT_ESCAPE.symbolValue(thread) != NIL);
838        boolean useNamestring;
839        String s = null;
840        s = getNamestring();
841        if (s != null) {
842            useNamestring = true;
843            if (printReadably) {
844                // We have a namestring. Check for pathname components that
845                // can't be read from the namestring.
846                if ((host != NIL && !isURL())
847                    || version != NIL) 
848                {
849                    useNamestring = false;
850                } else if (name instanceof AbstractString) {
851                    String n = name.getStringValue();
852                    if (n.equals(".") || n.equals("..")) {
853                        useNamestring = false;
854                    } else if (n.indexOf(File.separatorChar) >= 0) {
855                        useNamestring = false;
856                    }
857                }
858            }
859        } else { 
860            useNamestring = false;
861        }
862        StringBuilder sb = new StringBuilder();
863
864        if (useNamestring) {
865            if (printReadably || printEscape) {
866                sb.append("#P\"");
867            }
868            final int limit = s.length();
869            for (int i = 0; i < limit; i++) {
870                char c = s.charAt(i);
871                if (printReadably || printEscape) {
872                    if (c == '\"' || c == '\\') {
873                        sb.append('\\');
874                    }
875                }
876                sb.append(c);
877            }
878            if (printReadably || printEscape) {
879                sb.append('"');
880            }
881            return sb.toString();
882        } 
883
884        sb.append("PATHNAME (with no namestring) ");
885        if (host != NIL) {
886            sb.append(":HOST ");
887            sb.append(host.writeToString());
888            sb.append(" ");
889        }
890        if (device != NIL) {
891            sb.append(":DEVICE ");
892            sb.append(device.writeToString());
893            sb.append(" ");
894        }
895        if (directory != NIL) {
896            sb.append(":DIRECTORY ");
897            sb.append(directory.writeToString());
898            sb.append(" ");
899        }
900        if (name != NIL) {
901            sb.append(":NAME ");
902            sb.append(name.writeToString());
903            sb.append(" ");
904        }
905        if (type != NIL) {
906            sb.append(":TYPE ");
907            sb.append(type.writeToString());
908            sb.append(" ");
909        }
910        if (version != NIL) {
911            sb.append(":VERSION ");
912            sb.append(version.writeToString());
913            sb.append(" ");
914        }
915        if (sb.charAt(sb.length() - 1) == ' ') { 
916            sb.setLength(sb.length() - 1);
917        }
918
919        return unreadableString(sb.toString());
920    }
921    // A logical host is represented as the string that names it.
922    // (defvar *logical-pathname-translations* (make-hash-table :test 'equal))
923    public static EqualHashTable LOGICAL_PATHNAME_TRANSLATIONS =
924      new EqualHashTable(64, NIL, NIL);
925    private static final Symbol _LOGICAL_PATHNAME_TRANSLATIONS_ =
926      exportSpecial("*LOGICAL-PATHNAME-TRANSLATIONS*", PACKAGE_SYS,
927      LOGICAL_PATHNAME_TRANSLATIONS);
928
929    public static Pathname parseNamestring(String s) {
930        return new Pathname(s);
931    }
932
933    public static boolean isValidURL(String s) {
934        try {
935            URL url = new URL(s);
936        } catch (MalformedURLException e) {
937            return false;
938        }
939        return true;
940    }
941
942    public static URL toURL(Pathname p) {
943        URL url = null;
944        if (!(p.host instanceof Cons)) {
945            Debug.assertTrue(false); // XXX
946        }
947        try {
948            url = new URL(p.getNamestring());
949        } catch (MalformedURLException e) {
950            Debug.assertTrue(false); // XXX
951        }
952        return url;
953    }
954
955    URLConnection getURLConnection() {
956        Debug.assertTrue(isURL());
957        URL url = Pathname.toURL(this);
958        URLConnection result = null;
959        try {
960            result = url.openConnection();
961        } catch (IOException e) {
962            error(new FileError("Failed to open URL connection.",
963                                this));
964        }
965        return result;
966    }
967
968    public static Pathname parseNamestring(AbstractString namestring) {
969        // Check for a logical pathname host.
970        String s = namestring.getStringValue();
971        if (!isValidURL(s)) {
972            String h = getHostString(s);
973            if (h != null && LOGICAL_PATHNAME_TRANSLATIONS.get(new SimpleString(h)) != null) {
974                // A defined logical pathname host.
975                return new LogicalPathname(h, s.substring(s.indexOf(':') + 1));
976            }
977        }
978        return new Pathname(s);
979    }
980
981    // XXX was @return Pathname
982    public static LogicalPathname parseNamestring(AbstractString namestring,
983                                                  AbstractString host) 
984    {
985        String s = namestring.getStringValue();
986
987        // Look for a logical pathname host in the namestring.       
988        String h = getHostString(s);
989        if (h != null) {
990            if (!h.equals(host.getStringValue())) {
991                error(new LispError("Host in " + s
992                  + " does not match requested host "
993                  + host.getStringValue()));
994                // Not reached.
995                return null;
996            }
997            // Remove host prefix from namestring.
998            s = s.substring(s.indexOf(':') + 1);
999        }
1000        if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) != null) {
1001            // A defined logical pathname host.
1002            return new LogicalPathname(host.getStringValue(), s);
1003        }
1004        error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
1005        // Not reached.
1006        return null;
1007    }
1008
1009    // "one or more uppercase letters, digits, and hyphens"
1010    protected static String getHostString(String s) {
1011        int colon = s.indexOf(':');
1012        if (colon >= 0) {
1013            return s.substring(0, colon).toUpperCase();
1014        } else {
1015            return null;
1016        }
1017    }
1018
1019    static final void checkCaseArgument(LispObject arg) {
1020        if (arg != Keyword.COMMON && arg != Keyword.LOCAL) {
1021            type_error(arg, list(Symbol.MEMBER, Keyword.COMMON,
1022              Keyword.LOCAL));
1023        }
1024    }
1025    // ### %pathname-host
1026    private static final Primitive _PATHNAME_HOST = new pf_pathname_host();
1027    private static class pf_pathname_host extends Primitive {
1028        pf_pathname_host() {
1029            super("%pathname-host", PACKAGE_SYS, false);
1030        }
1031        @Override
1032        public LispObject execute(LispObject first, LispObject second) {
1033            checkCaseArgument(second);
1034            return coerceToPathname(first).host;
1035        }
1036    }
1037    // ### %pathname-device
1038    private static final Primitive _PATHNAME_DEVICE = new pf_pathname_device(); 
1039    private static class pf_pathname_device extends Primitive {
1040        pf_pathname_device() {
1041            super("%pathname-device", PACKAGE_SYS, false);
1042        }
1043        @Override
1044        public LispObject execute(LispObject first, LispObject second) {
1045            checkCaseArgument(second);
1046            return coerceToPathname(first).device;
1047        }
1048    }
1049    // ### %pathname-directory
1050    private static final Primitive _PATHNAME_DIRECTORY = new pf_pathname_directory();
1051    private static class pf_pathname_directory extends Primitive {
1052        pf_pathname_directory() {
1053            super("%pathname-directory", PACKAGE_SYS, false);
1054        }
1055        @Override
1056        public LispObject execute(LispObject first, LispObject second) {
1057            checkCaseArgument(second);
1058            return coerceToPathname(first).directory;
1059        }
1060    }
1061    // ### %pathname-name
1062    private static final Primitive _PATHNAME_NAME = new pf_pathname_name();
1063    private static class  pf_pathname_name extends Primitive {
1064        pf_pathname_name() {
1065            super ("%pathname-name", PACKAGE_SYS, false);
1066        }
1067        @Override
1068        public LispObject execute(LispObject first, LispObject second) {
1069            checkCaseArgument(second);
1070            return coerceToPathname(first).name;
1071        }
1072    }
1073    // ### %pathname-type
1074    private static final Primitive _PATHNAME_TYPE = new pf_pathname_type();
1075    private static class pf_pathname_type extends Primitive {
1076        pf_pathname_type() {
1077            super("%pathname-type", PACKAGE_SYS, false);
1078        }
1079        @Override
1080        public LispObject execute(LispObject first, LispObject second) {
1081            checkCaseArgument(second);
1082            return coerceToPathname(first).type;
1083        }
1084    }
1085    // ### pathname-version
1086    private static final Primitive PATHNAME_VERSION = new pf_pathname_version();
1087    private static class pf_pathname_version extends Primitive {
1088        pf_pathname_version() {
1089            super("pathname-version", "pathname");
1090        }
1091        @Override
1092        public LispObject execute(LispObject arg) {
1093            return coerceToPathname(arg).version;
1094        }
1095    }
1096    // ### namestring
1097    // namestring pathname => namestring
1098    private static final Primitive NAMESTRING = new pf_namestring();
1099    private static class pf_namestring extends Primitive {
1100        pf_namestring() {
1101            super("namestring", "pathname");
1102        }
1103        @Override
1104        public LispObject execute(LispObject arg) {
1105            Pathname pathname = coerceToPathname(arg);
1106            String namestring = pathname.getNamestring();
1107            if (namestring == null) {
1108                error(new SimpleError("Pathname has no namestring: "
1109                                      + pathname.writeToString()));
1110            }
1111            return new SimpleString(namestring);
1112        }
1113    }
1114    // ### directory-namestring
1115    // directory-namestring pathname => namestring
1116    private static final Primitive DIRECTORY_NAMESTRING = new pf_directory_namestring();
1117    private static class pf_directory_namestring extends Primitive {
1118        pf_directory_namestring() {
1119            super("directory-namestring", "pathname");
1120        }
1121        @Override
1122        public LispObject execute(LispObject arg) {
1123            return new SimpleString(coerceToPathname(arg).getDirectoryNamestring());
1124        }
1125    }
1126    // ### pathname pathspec => pathname
1127    private static final Primitive PATHNAME = new pf_pathname();
1128    private static class pf_pathname extends Primitive {
1129        pf_pathname() {
1130            super("pathname", "pathspec");
1131        }
1132        @Override
1133        public LispObject execute(LispObject arg) {
1134            return coerceToPathname(arg);
1135        }
1136    }
1137    // ### %parse-namestring string host default-pathname => pathname, position
1138    private static final Primitive _PARSE_NAMESTRING = new pf_parse_namestring();
1139    private static class pf_parse_namestring extends Primitive {
1140        pf_parse_namestring() {
1141            super("%parse-namestring", PACKAGE_SYS, false,
1142                  "namestring host default-pathname");
1143        }
1144        @Override
1145        public LispObject execute(LispObject first, LispObject second, LispObject third) {
1146            final LispThread thread = LispThread.currentThread();
1147            final AbstractString namestring = checkString(first);
1148            // The HOST parameter must be a string or NIL.
1149            if (second == NIL) {
1150                // "If HOST is NIL, DEFAULT-PATHNAME is a logical pathname, and
1151                // THING is a syntactically valid logical pathname namestring
1152                // without an explicit host, then it is parsed as a logical
1153                // pathname namestring on the host that is the host component
1154                // of DEFAULT-PATHNAME."
1155                third = coerceToPathname(third);
1156                if (third instanceof LogicalPathname) {
1157                    second = ((LogicalPathname) third).host;
1158                } else {
1159                    return thread.setValues(parseNamestring(namestring),
1160                                            namestring.LENGTH());
1161                }
1162            }
1163            Debug.assertTrue(second != NIL);
1164            final AbstractString host = checkString(second);
1165            return thread.setValues(parseNamestring(namestring, host),
1166                                    namestring.LENGTH());
1167        }
1168    }
1169    // ### make-pathname
1170    private static final Primitive MAKE_PATHNAME = new pf_make_pathname();
1171    private static class pf_make_pathname extends Primitive {
1172        pf_make_pathname() {
1173            super("make-pathname",
1174                  "&key host device directory name type version defaults case");
1175        }
1176        @Override
1177        public LispObject execute(LispObject[] args) {
1178            return _makePathname(args);
1179        }
1180    }
1181
1182    // Used by the #p reader.
1183    public static final Pathname makePathname(LispObject args) {
1184        return _makePathname(args.copyToArray());
1185    }
1186
1187    public static final Pathname makePathname(File file) {
1188        String namestring = null;
1189        try {
1190            namestring = file.getCanonicalPath();
1191        } catch (IOException e) {
1192            Debug.trace("Failed to make a Pathname from "
1193              + "." + file + "'");
1194            return null;
1195        }
1196        return new Pathname(namestring);
1197    }
1198
1199    static final Pathname _makePathname(LispObject[] args) {
1200        if (args.length % 2 != 0) {
1201            error(new ProgramError("Odd number of keyword arguments."));
1202        }
1203        LispObject host = NIL;
1204        LispObject device = NIL;
1205        LispObject directory = NIL;
1206        LispObject name = NIL;
1207        LispObject type = NIL;
1208        LispObject version = NIL;
1209        Pathname defaults = null;
1210        boolean deviceSupplied = false;
1211        boolean nameSupplied = false;
1212        boolean typeSupplied = false;
1213        for (int i = 0; i < args.length; i += 2) {
1214            LispObject key = args[i];
1215            LispObject value = args[i + 1];
1216            if (key == Keyword.HOST) {
1217                host = value;
1218            } else if (key == Keyword.DEVICE) {
1219                device = value;
1220                deviceSupplied = true;
1221            } else if (key == Keyword.DIRECTORY) {
1222                if (value instanceof AbstractString) {
1223                    directory = list(Keyword.ABSOLUTE, value);
1224                } else if (value == Keyword.WILD) {
1225                    directory = list(Keyword.ABSOLUTE, Keyword.WILD);
1226                } else {
1227                    directory = value;
1228                }
1229            } else if (key == Keyword.NAME) {
1230                name = value;
1231                nameSupplied = true;
1232            } else if (key == Keyword.TYPE) {
1233                type = value;
1234                typeSupplied = true;
1235            } else if (key == Keyword.VERSION) {
1236                version = value;
1237            } else if (key == Keyword.DEFAULTS) {
1238                defaults = coerceToPathname(value);
1239            } else if (key == Keyword.CASE) {
1240                // Ignored.
1241            }
1242        }
1243        if (defaults != null) {
1244            if (host == NIL) {
1245                host = defaults.host;
1246            }
1247            if (directory == NIL) {
1248                directory = defaults.directory;
1249            }
1250            if (!deviceSupplied) {
1251                device = defaults.device;
1252            }
1253            if (!nameSupplied) {
1254                name = defaults.name;
1255            }
1256            if (!typeSupplied) {
1257                type = defaults.type;
1258            }
1259        }
1260        final Pathname p;
1261        final boolean logical;
1262        LispObject logicalHost = NIL;
1263        if (host != NIL) {
1264            if (host instanceof AbstractString) {
1265                logicalHost = LogicalPathname.canonicalizeStringComponent((AbstractString) host);
1266            }
1267            if (LOGICAL_PATHNAME_TRANSLATIONS.get(logicalHost) == null) {
1268                // Not a defined logical pathname host -- A UNC path
1269                //warning(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
1270                p = new Pathname();
1271                logical = false;
1272                p.host = host;
1273            } else { 
1274                p = new LogicalPathname();
1275                logical = true;
1276                p.host = logicalHost;
1277            }
1278            p.device = Keyword.UNSPECIFIC;
1279        } else {
1280            p = new Pathname();
1281            logical = false;
1282        }
1283        if (device != NIL) {
1284            if (logical) {
1285                // "The device component of a logical pathname is always :UNSPECIFIC."
1286                if (device != Keyword.UNSPECIFIC) {
1287                    error(new LispError("The device component of a logical pathname must be :UNSPECIFIC."));
1288                }
1289            } else {
1290                p.device = device;
1291            }
1292        }
1293        if (directory != NIL) {
1294            if (logical) {
1295                if (directory.listp()) {
1296                    LispObject d = NIL;
1297                    while (directory != NIL) {
1298                        LispObject component = directory.car();
1299                        if (component instanceof AbstractString) {
1300                            d = d.push(LogicalPathname.canonicalizeStringComponent((AbstractString) component));
1301                        } else {
1302                            d = d.push(component);
1303                        }
1304                        directory = directory.cdr();
1305                    }
1306                    p.directory = d.nreverse();
1307                } else if (directory == Keyword.WILD || directory == Keyword.WILD_INFERIORS) {
1308                    p.directory = directory;
1309                } else {
1310                    error(new LispError("Invalid directory component for logical pathname: " + directory.writeToString()));
1311                }
1312            } else {
1313                p.directory = directory;
1314            }
1315        }
1316        if (name != NIL) {
1317            if (logical && name instanceof AbstractString) {
1318                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString) name);
1319            } else if (name instanceof AbstractString) {
1320                p.name = validateStringComponent((AbstractString) name);
1321            } else {
1322                p.name = name;
1323            }
1324        }
1325        if (type != NIL) {
1326            if (logical && type instanceof AbstractString) {
1327                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString) type);
1328            } else {
1329                p.type = type;
1330            }
1331        }
1332        p.version = version;
1333        return p;
1334    }
1335
1336    private static final AbstractString validateStringComponent(AbstractString s) {
1337        final int limit = s.length();
1338        for (int i = 0; i < limit; i++) {
1339            char c = s.charAt(i);
1340            // XXX '\\' should be illegal in all Pathnames at this point?
1341            if (c == '/' || c == '\\' && Utilities.isPlatformWindows) {
1342                error(new LispError("Invalid character #\\" + c
1343                  + " in pathname component \"" + s
1344                  + '"'));
1345                // Not reached.
1346                return null;
1347            }
1348        }
1349        return s;
1350    }
1351
1352    private final boolean validateDirectory(boolean signalError) {
1353        LispObject temp = directory;
1354        while (temp != NIL) {
1355            LispObject first = temp.car();
1356            temp = temp.cdr();
1357            if (first == Keyword.ABSOLUTE || first == Keyword.WILD_INFERIORS) {
1358                LispObject second = temp.car();
1359                if (second == Keyword.UP || second == Keyword.BACK) {
1360                    if (signalError) {
1361                        StringBuilder sb = new StringBuilder();
1362                        sb.append(first.writeToString());
1363                        sb.append(" may not be followed immediately by ");
1364                        sb.append(second.writeToString());
1365                        sb.append('.');
1366                        error(new FileError(sb.toString(), this));
1367                    }
1368                    return false;
1369                }
1370            }
1371        }
1372        return true;
1373    }
1374    // ### pathnamep
1375    private static final Primitive PATHNAMEP = new pf_pathnamep();
1376    private static class pf_pathnamep extends Primitive  {
1377        pf_pathnamep() {
1378            super("pathnamep", "object");
1379        }
1380        @Override
1381        public LispObject execute(LispObject arg) {
1382            return arg instanceof Pathname ? T : NIL;
1383        }
1384    }
1385    // ### logical-pathname-p
1386    private static final Primitive LOGICAL_PATHNAME_P = new pf_logical_pathname_p();
1387    private static class pf_logical_pathname_p extends Primitive {
1388        pf_logical_pathname_p() {
1389            super("logical-pathname-p", PACKAGE_SYS, true, "object");
1390        }
1391        @Override
1392        public LispObject execute(LispObject arg) {
1393            return arg instanceof LogicalPathname ? T : NIL;
1394        }
1395    }
1396    // ### user-homedir-pathname &optional host => pathname
1397    private static final Primitive USER_HOMEDIR_PATHNAME = new pf_user_homedir_pathname();
1398    private static class pf_user_homedir_pathname extends Primitive {
1399        pf_user_homedir_pathname() {
1400            super("user-homedir-pathname", "&optional host");
1401        }
1402        @Override
1403        public LispObject execute(LispObject[] args) {
1404            switch (args.length) {
1405            case 0: {
1406                String s = System.getProperty("user.home");
1407                if (!s.endsWith(File.separator)) {
1408                    s = s.concat(File.separator);
1409                }
1410                return new Pathname(s);
1411            }
1412            case 1:
1413                return NIL; 
1414            default:
1415                return error(new WrongNumberOfArgumentsException(this));
1416            }
1417        }
1418    }
1419
1420    // ### list-directory directory
1421    private static final Primitive LIST_DIRECTORY = new pf_list_directory();
1422    private static class pf_list_directory extends Primitive {
1423        pf_list_directory() {
1424            super("list-directory", PACKAGE_SYS, true, "directory");
1425        }
1426        @Override
1427        public LispObject execute(LispObject arg) {
1428            Pathname pathname = coerceToPathname(arg);
1429            if (pathname instanceof LogicalPathname) {
1430                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1431            }
1432
1433            LispObject result = NIL;
1434            if (pathname.isJar()) {
1435                String directory = pathname.asEntryPath();
1436                Debug.assertTrue(directory != null);  // We should only be listing directories
1437
1438                if (pathname.device.cdr() instanceof Cons) {
1439                    return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1440                }
1441
1442                if (directory.length() == 0) {
1443                    directory = "/*";
1444                } else {
1445                    if (directory.endsWith("/")) {
1446                        directory = "/" + directory + "*";
1447                    } else {
1448                        directory = "/" + directory + "/*";
1449                    }
1450                }
1451                SimpleString wildcard = new SimpleString(directory);
1452                SimpleString wildcardDirectory = new SimpleString(directory + "/");
1453
1454                ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1455                LispObject matches;
1456                for (Enumeration<? extends ZipEntry> entries = jar.entries(); 
1457                     entries.hasMoreElements();) {
1458                    ZipEntry entry = entries.nextElement();
1459                    String entryName = "/" + entry.getName();
1460
1461                    if (entryName.endsWith("/")) {
1462                        matches = Symbol.PATHNAME_MATCH_P
1463                            .execute(new SimpleString(entryName), wildcardDirectory);
1464                    } else {
1465                        matches = Symbol.PATHNAME_MATCH_P.
1466                            execute(new SimpleString(entryName), wildcard);
1467                    }
1468                    if (!matches.equals(NIL)) {
1469                        String namestring = new String(pathname.getNamestring());
1470                        namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1471                                 + entry.getName();
1472                        Pathname p = new Pathname(namestring);
1473                        result = new Cons(p, result);
1474                    }
1475                }
1476                return result;
1477            }
1478
1479            if (pathname.isURL()) {
1480                return error(new LispError("Unimplemented.")); // XXX
1481            }
1482
1483            String s = pathname.getNamestring();
1484            if (s != null) {
1485                File f = new File(s);
1486                if (f.isDirectory()) {
1487                    try {
1488                        File[] files = f.listFiles();
1489                        for (int i = files.length; i-- > 0;) {
1490                            File file = files[i];
1491                            Pathname p;
1492                            if (file.isDirectory()) {
1493                                p = Utilities.getDirectoryPathname(file);
1494                            } else {
1495                                p = new Pathname(file.getCanonicalPath());
1496                            }
1497                            result = new Cons(p, result);
1498                        }
1499                    } catch (IOException e) {
1500                        return error(new FileError("Unable to list directory " + pathname.writeToString() + ".",
1501                                                   pathname));
1502                    } catch (SecurityException e) {
1503                        Debug.trace(e);
1504                    } catch (NullPointerException e) {
1505                        Debug.trace(e);
1506                    }
1507                }
1508            }
1509            return result;
1510        }
1511    }
1512
1513    // ### match-wild-jar-pathname wild-jar-pathname
1514    static final Primitive MATCH_WILD_JAR_PATHNAME = new pf_match_wild_jar_pathname();
1515    private static class pf_match_wild_jar_pathname extends Primitive {
1516        pf_match_wild_jar_pathname() {
1517            super("match-wild-jar-pathname", PACKAGE_SYS, false, "wild-jar-pathname");
1518        }
1519        @Override
1520        public LispObject execute(LispObject arg) {
1521            Pathname pathname = coerceToPathname(arg);
1522            if (pathname instanceof LogicalPathname) {
1523                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1524            }
1525            if (!pathname.isJar()) {
1526                return new FileError("Not a jar pathname.", pathname);
1527            }
1528            if (!pathname.isWild()) {
1529                return new FileError("Not a wild pathname.", pathname);
1530            }
1531            Pathname jarPathname = new Pathname(pathname);
1532            jarPathname.directory = NIL;
1533            jarPathname.name = NIL;
1534            jarPathname.type = NIL;
1535            jarPathname.invalidateNamestring();
1536            LispObject jarTruename = truename(jarPathname, false); 
1537           
1538            // We can't match anything in a non-existent jar
1539            if (jarTruename == NIL) {
1540                return NIL;
1541            }
1542           
1543            LispObject result = NIL;
1544            String wild = "/" + pathname.asEntryPath();
1545
1546            if (pathname.device.cdr() instanceof Cons) {
1547                return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1548            }
1549           
1550            final SimpleString wildcard = new SimpleString(wild);
1551
1552            ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1553
1554            for (Enumeration<? extends ZipEntry> entries = jar.entries(); entries.hasMoreElements();) {
1555                ZipEntry entry = entries.nextElement();
1556                String entryName = "/" + entry.getName();
1557               
1558                LispObject matches = Symbol.PATHNAME_MATCH_P
1559                    .execute(new SimpleString(entryName), wildcard);
1560
1561                if (!matches.equals(NIL)) {
1562                    String namestring = new String(pathname.getNamestring());
1563                    namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1564                        + entry.getName();
1565                    Pathname p = new Pathname(namestring);
1566                    result = new Cons(p, result);
1567                }
1568            }
1569            return result;
1570        }
1571    }
1572
1573    public boolean isAbsolute()  {
1574        if (!directory.equals(NIL) || !(directory == null)) {
1575            if (directory instanceof Cons) {
1576                if (((Cons)directory).car().equals(Keyword.ABSOLUTE)) {
1577                    return true;
1578                }
1579            }
1580        }
1581        return false;
1582    }
1583
1584    // ### PATHNAME-JAR-P
1585    private static final Primitive PATHNAME_JAR_P = new pf_pathname_jar_p();
1586    private static class pf_pathname_jar_p extends Primitive {
1587        pf_pathname_jar_p() {
1588            super("pathname-jar-p", PACKAGE_EXT, true, "pathname",
1589                  "Predicate for whether PATHNAME references a JAR.");
1590        }
1591        @Override
1592        public LispObject execute(LispObject arg) {
1593            Pathname p = coerceToPathname(arg);
1594            return p.isJar() ? T : NIL;
1595        }
1596    }
1597
1598    public boolean isJar() {
1599        return (device instanceof Cons);
1600    }
1601
1602    // ### PATHNAME-URL-P
1603    private static final Primitive PATHNAME_URL_P = new pf_pathname_url_p();
1604    private static class pf_pathname_url_p extends Primitive {
1605        pf_pathname_url_p() {
1606            super("pathname-url-p", PACKAGE_EXT, true, "pathname",
1607                  "Predicate for whether PATHNAME references a URL.");
1608        }
1609        @Override
1610        public LispObject execute(LispObject arg) {
1611            Pathname p = coerceToPathname(arg);
1612            return p.isURL() ? T : NIL;
1613        }
1614    }
1615
1616    public boolean isURL() {
1617        return (host instanceof Cons);
1618    }
1619
1620    public boolean isWild() {
1621        if (host == Keyword.WILD || host == Keyword.WILD_INFERIORS) {
1622            return true;
1623        }
1624        if (device == Keyword.WILD || device == Keyword.WILD_INFERIORS) {
1625            return true;
1626        }
1627        if (directory instanceof Cons) {
1628            if (memq(Keyword.WILD, directory)) {
1629                return true;
1630            }
1631            if (memq(Keyword.WILD_INFERIORS, directory)) {
1632                return true;
1633            }
1634            Cons d = (Cons) directory;
1635            while (true) {
1636                if (d.car() instanceof AbstractString) {
1637                    String s = d.car().writeToString();
1638                    if (s.contains("*")) {
1639                        return true;
1640                    }
1641                }
1642                if (d.cdr() == NIL || ! (d.cdr() instanceof Cons)) {
1643                    break;
1644                }
1645                d = (Cons)d.cdr();
1646            }
1647        }
1648        if (name == Keyword.WILD || name == Keyword.WILD_INFERIORS) {
1649            return true;
1650        }
1651        if (name instanceof AbstractString) {
1652            if (name.writeToString().contains("*")) {
1653                return true;
1654            }
1655        }
1656        if (type == Keyword.WILD || type == Keyword.WILD_INFERIORS) {
1657            return true;
1658        }
1659        if (type instanceof AbstractString) {
1660            if (type.writeToString().contains("*")) {
1661                return true;
1662            }
1663        }
1664        if (version == Keyword.WILD || version == Keyword.WILD_INFERIORS) {
1665            return true;
1666        }
1667        return false;
1668    }
1669    // ### %wild-pathname-p
1670    private static final Primitive _WILD_PATHNAME_P = new pf_wild_pathname_p();
1671    static final class pf_wild_pathname_p extends Primitive {
1672        pf_wild_pathname_p() {
1673            super("%wild-pathname-p", PACKAGE_SYS, true);
1674        }
1675        @Override
1676        public LispObject execute(LispObject first, LispObject second) {
1677            Pathname pathname = coerceToPathname(first);
1678            if (second == NIL) {
1679                return pathname.isWild() ? T : NIL;
1680            }
1681            if (second == Keyword.DIRECTORY) {
1682                if (pathname.directory instanceof Cons) {
1683                    if (memq(Keyword.WILD, pathname.directory)) {
1684                        return T;
1685                    }
1686                    if (memq(Keyword.WILD_INFERIORS, pathname.directory)) {
1687                        return T;
1688                    }
1689                }
1690                return NIL;
1691            }
1692            LispObject value;
1693            if (second == Keyword.HOST) {
1694                value = pathname.host;
1695            } else if (second == Keyword.DEVICE) {
1696                value = pathname.device;
1697            } else if (second == Keyword.NAME) {
1698                value = pathname.name;
1699            } else if (second == Keyword.TYPE) {
1700                value = pathname.type;
1701            } else if (second == Keyword.VERSION) {
1702                value = pathname.version;
1703            } else {
1704                return error(new ProgramError("Unrecognized keyword "
1705                                              + second.writeToString() + "."));
1706            }
1707            if (value == Keyword.WILD || value == Keyword.WILD_INFERIORS) {
1708                return T;
1709            } else {
1710                return NIL;
1711            }
1712        }
1713    }
1714
1715    // ### merge-pathnames pathname &optional default-pathname default-version"
1716    private static final Primitive MERGE_PATHNAMES = new pf_merge_pathnames();
1717    static final class pf_merge_pathnames extends Primitive {
1718        pf_merge_pathnames() {
1719            super("merge-pathnames", "pathname &optional default-pathname default-version");
1720        }
1721        @Override
1722        public LispObject execute(LispObject arg) {
1723            Pathname pathname = coerceToPathname(arg);
1724            Pathname defaultPathname =
1725                coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
1726            LispObject defaultVersion = Keyword.NEWEST;
1727            return mergePathnames(pathname, defaultPathname, defaultVersion);
1728        }
1729        @Override
1730        public LispObject execute(LispObject first, LispObject second) {
1731            Pathname pathname = coerceToPathname(first);
1732            Pathname defaultPathname =
1733                coerceToPathname(second);
1734            LispObject defaultVersion = Keyword.NEWEST;
1735            return mergePathnames(pathname, defaultPathname, defaultVersion);
1736        }
1737        @Override
1738        public LispObject execute(LispObject first, LispObject second,
1739                                  LispObject third) {
1740            Pathname pathname = coerceToPathname(first);
1741            Pathname defaultPathname =
1742                coerceToPathname(second);
1743            LispObject defaultVersion = third;
1744            return mergePathnames(pathname, defaultPathname, defaultVersion);
1745        }
1746    }
1747
1748    public static final Pathname mergePathnames(Pathname pathname, Pathname defaultPathname) {
1749        return mergePathnames(pathname, defaultPathname, Keyword.NEWEST);
1750    }
1751
1752    public static final Pathname mergePathnames(final Pathname pathname,
1753                                                final Pathname defaultPathname,
1754                                                final LispObject defaultVersion) 
1755    {
1756        Pathname result;
1757        Pathname p = new Pathname(pathname);
1758        Pathname d;
1759
1760        if (pathname instanceof LogicalPathname) {
1761            result = new LogicalPathname();
1762            d = new Pathname(defaultPathname);
1763        } else {
1764            result = new Pathname();
1765            if (defaultPathname instanceof LogicalPathname) {
1766                d = LogicalPathname.translateLogicalPathname((LogicalPathname) defaultPathname);
1767            } else {
1768                d = new Pathname(defaultPathname);
1769            }
1770        }
1771        if (pathname.host != NIL) {
1772            result.host = p.host;
1773        } else {
1774            result.host = d.host;
1775        }
1776
1777        if (pathname.device != NIL) { // XXX if device represent JARs we want to merge
1778            result.device = p.device;
1779        } else {
1780            if (!p.isURL()) {
1781                result.device = d.device;
1782            }
1783        }
1784
1785        if (pathname.isJar()) {
1786            Cons jars = (Cons)result.device;
1787            LispObject jar = jars.car;
1788            if (jar instanceof Pathname) {
1789                Pathname defaults = new Pathname(d);
1790                if (defaults.isJar()) {
1791                    defaults.device = NIL;
1792                }
1793                Pathname o = mergePathnames((Pathname)jar, defaults);
1794                if (o.directory instanceof Cons
1795                    && ((Cons)o.directory).length() == 1) { // i.e. (:ABSOLUTE) or (:RELATIVE)
1796                    o.directory = NIL;
1797                }
1798                ((Cons)result.device).car = o;
1799            }
1800            result.directory = p.directory;
1801        } else {
1802            result.directory = mergeDirectories(p.directory, d.directory);
1803        }
1804
1805        if (pathname.name != NIL) {
1806            result.name = p.name;
1807        } else {
1808            result.name = d.name;
1809        }
1810        if (pathname.type != NIL) {
1811            result.type = p.type;
1812        } else {
1813            result.type = d.type;
1814        }
1815        if (pathname.version != NIL) {
1816            result.version = pathname.version;
1817        } else if (pathname.name instanceof AbstractString) {
1818            result.version = defaultVersion;
1819        } else if (defaultPathname.version != NIL) {
1820            result.version = defaultPathname.version;
1821        } else {
1822            result.version = defaultVersion;
1823        }
1824        if (pathname instanceof LogicalPathname) {
1825            // When we're returning a logical
1826            result.device = Keyword.UNSPECIFIC;
1827            if (result.directory.listp()) {
1828                LispObject original = result.directory;
1829                LispObject canonical = NIL;
1830                while (original != NIL) {
1831                    LispObject component = original.car();
1832                    if (component instanceof AbstractString) {
1833                        component = LogicalPathname.canonicalizeStringComponent((AbstractString) component);
1834                    }
1835                    canonical = canonical.push(component);
1836                    original = original.cdr();
1837                }
1838                result.directory = canonical.nreverse();
1839            }
1840            if (result.name instanceof AbstractString) {
1841                result.name = LogicalPathname.canonicalizeStringComponent((AbstractString) result.name);
1842            }
1843            if (result.type instanceof AbstractString) {
1844                result.type = LogicalPathname.canonicalizeStringComponent((AbstractString) result.type);
1845            }
1846        }
1847        result.invalidateNamestring();
1848        return result;
1849    }
1850
1851    private static final LispObject mergeDirectories(LispObject dir,
1852                                                     LispObject defaultDir) {
1853        if (dir == NIL) {
1854            return defaultDir;
1855        }
1856        if (dir.car() == Keyword.RELATIVE && defaultDir != NIL) {
1857            LispObject result = NIL;
1858            while (defaultDir != NIL) {
1859                result = new Cons(defaultDir.car(), result);
1860                defaultDir = defaultDir.cdr();
1861            }
1862            dir = dir.cdr(); // Skip :RELATIVE.
1863            while (dir != NIL) {
1864                result = new Cons(dir.car(), result);
1865                dir = dir.cdr();
1866            }
1867            LispObject[] array = result.copyToArray();
1868            for (int i = 0; i < array.length - 1; i++) {
1869                if (array[i] == Keyword.BACK) {
1870                    if (array[i + 1] instanceof AbstractString || array[i + 1] == Keyword.WILD) {
1871                        array[i] = null;
1872                        array[i + 1] = null;
1873                    }
1874                }
1875            }
1876            result = NIL;
1877            for (int i = 0; i < array.length; i++) {
1878                if (array[i] != null) {
1879                    result = new Cons(array[i], result);
1880                }
1881            }
1882            return result;
1883        }
1884        return dir;
1885    }
1886
1887    public static final LispObject truename(Pathname pathname) {
1888        return truename(pathname, false);
1889    }
1890
1891    public static final LispObject truename(LispObject arg) {
1892        return truename(arg, false);
1893    }
1894
1895    public static final LispObject truename(LispObject arg, boolean errorIfDoesNotExist) {
1896        final Pathname pathname = coerceToPathname(arg);
1897        return truename(pathname, errorIfDoesNotExist);
1898    }
1899
1900    /** @return The canonical TRUENAME as a Pathname if the pathname
1901     * exists, otherwise returns NIL or possibly a subtype of
1902     * LispError if there are logical problems with the input.
1903     */
1904    public static final LispObject truename(Pathname pathname,
1905                                            boolean errorIfDoesNotExist) 
1906    {
1907        if (pathname instanceof LogicalPathname) {
1908            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1909        }
1910        if (pathname.isWild()) {
1911            return error(new FileError("Bad place for a wild pathname.",
1912                                       pathname));
1913        }
1914        if (!(pathname.isJar() || pathname.isURL())) {
1915            pathname
1916                = mergePathnames(pathname,
1917                                 coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1918                                 NIL);
1919            final String namestring = pathname.getNamestring();
1920            if (namestring == null) {
1921                return error(new FileError("Pathname has no namestring: " 
1922                                           + pathname.writeToString(),
1923                                           pathname));
1924            }
1925           
1926            final File file = new File(namestring);
1927            if (file.isDirectory()) {
1928                return Utilities.getDirectoryPathname(file);
1929            }
1930            if (file.exists()) {
1931                try {
1932                    return new Pathname(file.getCanonicalPath());
1933                } catch (IOException e) {
1934                    return error(new FileError(e.getMessage(), pathname));
1935                }
1936            }
1937        } else if (pathname.isURL()) {
1938            if (pathname.getInputStream() != null) {
1939              // If there is no type, query or fragment, we check to
1940              // see if there is URL available "underneath".
1941              if (pathname.name != NIL
1942                  && pathname.type == NIL
1943                  && Symbol.GETF.execute(pathname.host, QUERY, NIL) == NIL
1944                  && Symbol.GETF.execute(pathname.host, FRAGMENT, NIL) == NIL) {
1945                Pathname p = new Pathname(pathname.getNamestring() + "/");
1946                if (p.getInputStream() != null) {
1947                  return p;
1948                }
1949              }
1950              return pathname;
1951            }
1952        } else
1953        jarfile: {
1954            // Possibly canonicalize jar file directory
1955            Cons jars = (Cons) pathname.device;
1956            LispObject o = jars.car();
1957            if (o instanceof Pathname && ! (((Pathname)o).isURL())) {
1958                LispObject truename = Pathname.truename((Pathname)o, errorIfDoesNotExist);
1959                if (truename != null
1960                    && truename instanceof Pathname) {
1961                    jars.car = (Pathname)truename;
1962                } else {
1963                    break jarfile;
1964                }
1965            }
1966
1967            // Check for existence of a JAR file and/or JarEntry
1968            //
1969            // Cases:
1970            // 1.  JAR
1971            // 2.  JAR in JAR
1972            // 3.  JAR with Entry
1973            // 4.  JAR in JAR with Entry
1974            ZipFile jarFile = ZipCache.get((Pathname)jars.car());
1975            String entryPath = pathname.asEntryPath();
1976            if (jarFile != null) {
1977                if (jars.cdr() instanceof Cons) {
1978                  Pathname inner = (Pathname) jars.cdr().car();
1979                  InputStream inputStream = Utilities.getInputStream(jarFile, inner);
1980                  if (inputStream != null) {
1981                      if (entryPath.length() == 0) {
1982                          return pathname; // Case 2
1983                      } else {
1984                          ZipInputStream zipInputStream
1985                              = new ZipInputStream(inputStream);
1986                          ZipEntry entry = Utilities.getEntry(zipInputStream,
1987                                                              entryPath,
1988                                                              false);
1989                          if (entry != null) {
1990                              // XXX this could possibly be a directory?
1991                              return pathname; // Case 4
1992                         }
1993                      }
1994                  }
1995                } else {
1996                    if (entryPath.length() == 0) {
1997                        return pathname; // Case 1
1998                    } else {
1999                        ZipEntry entry = jarFile.getEntry(entryPath);
2000                        if (entry != null) {
2001                            // ensure this isn't a directory
2002                            try {
2003                                InputStream input = jarFile.getInputStream(entry);
2004                                if (input != null) {
2005                                    return pathname; // Case 3
2006                                }
2007                            } catch (IOException e) {
2008                                break jarfile;
2009                            }
2010                        }
2011                    }
2012                }
2013            }
2014        }
2015        error:
2016        if (errorIfDoesNotExist) {
2017            StringBuilder sb = new StringBuilder("The file ");
2018            sb.append(pathname.writeToString());
2019            sb.append(" does not exist.");
2020            return error(new FileError(sb.toString(), pathname));
2021        }
2022        return NIL;
2023    }
2024
2025
2026    /** Make a JarURL from a Pathname that references a file */
2027    private static URL makeJarURL(Pathname p) {
2028        String jarURL = "jar:file:" + p.getNamestring() + "!/";
2029        URL result = null;
2030        try {
2031            result = new URL(jarURL);
2032        } catch (MalformedURLException ex) {
2033            // XXX
2034            Debug.trace("Could not form URL from pathname "
2035              + "'" + jarURL + "'"
2036              + " because " + ex);
2037        }
2038        return result;
2039    }
2040
2041    protected static URL makeURL(Pathname pathname) {
2042        URL result = null;
2043        try {
2044            if (pathname.isURL()) {
2045                result = new URL(pathname.getNamestring());
2046            } else {
2047                // XXX Properly encode Windows drive letters and UNC paths
2048                // XXX ensure that we have cannonical path?
2049                result = new URL("file://" + pathname.getNamestring());
2050            }
2051        } catch (MalformedURLException e) {
2052            Debug.trace("Could not form URL from " + pathname);
2053        }
2054        return result;
2055    }
2056
2057    public InputStream getInputStream() {
2058        InputStream result = null;
2059        if (isJar()) {
2060            String entryPath = asEntryPath();
2061            // XXX We only return the bytes of an entry in a JAR
2062            Debug.assertTrue(entryPath != null);
2063            ZipFile jarFile = ZipCache.get((Pathname)device.car());
2064            Debug.assertTrue(jarFile != null);
2065            // Is this a JAR within a JAR?
2066            if (device.cdr() instanceof Cons) {
2067                Pathname inner = (Pathname) device.cdr().car();
2068                InputStream input = Utilities.getInputStream(jarFile, inner);
2069                ZipInputStream zipInputStream = new ZipInputStream(input);
2070                result =  Utilities.getEntryAsInputStream(zipInputStream, entryPath);
2071            } else {
2072                ZipEntry entry = jarFile.getEntry(entryPath);
2073    if (entry == null) {
2074        Debug.trace("Failed to get InputStream for "   
2075        + "'" + getNamestring() + "'");
2076                    // XXX should this be fatal?
2077        Debug.assertTrue(false);
2078    }
2079                try {
2080                    result = jarFile.getInputStream(entry);
2081                } catch (IOException e) {
2082                    Debug.warn("Failed to get InputStream from "
2083                                + "'" + getNamestring() + "'"
2084                                + ": " + e);
2085                }
2086            }
2087        } else if (isURL()) {
2088            URL url = toURL(this);
2089            try { 
2090                result = url.openStream();
2091            } catch (IOException e) {
2092                Debug.warn("Failed to get InputStream from "
2093                            + "'" + getNamestring() + "'"
2094                            + ": " + e);
2095            }
2096        } else {
2097            File file = Utilities.getFile(this);
2098            try { 
2099                result = new FileInputStream(file);
2100            } catch (IOException e) {
2101                Debug.warn("Failed to get InputStream from "
2102                            + "'" + getNamestring() + "'"
2103                            + ": " + e);
2104            }
2105        }
2106        return result;
2107    }
2108
2109    /** @return Time in milliseconds since the UNIX epoch at which the
2110     * resource was last modified, or 0 if the time is unknown.
2111     */
2112    public long getLastModified() {
2113        if (!(isJar() || isURL())) {
2114            File f = Utilities.getFile(this);
2115            return f.lastModified();
2116        }
2117
2118        if (isJar()) {
2119            // JAR cases
2120            // 0.  JAR from URL
2121            // 1.  JAR
2122            // 2.  JAR in JAR
2123            // 3.  Entry in JAR
2124            // 4.  Entry in JAR in JAR
2125            String entryPath = asEntryPath();
2126            Cons d = (Cons)device;
2127            if (d.cdr().equals(NIL)) {
2128                if (entryPath.length() == 0) {
2129                    LispObject o = d.car();
2130                        // 0. JAR from URL
2131                        // 1. JAR
2132                    return ((Pathname)o).getLastModified();
2133                } else {
2134                    // 3. Entry in JAR
2135                    final ZipEntry entry
2136                        = ZipCache.get((Pathname)device.car()).getEntry(entryPath);
2137                    if (entry == null) {
2138                        return 0;
2139                    }
2140                    final long time = entry.getTime();
2141                    if (time == -1) {
2142                        return 0;
2143                    }
2144                    return time;
2145                }
2146            } else {
2147                ZipFile outerJar = ZipCache.get((Pathname)d.car());
2148                if (entryPath.length() == 0) {
2149                    // 4.  JAR in JAR
2150                    String jarPath = ((Pathname)d.cdr()).asEntryPath();
2151                    final ZipEntry entry = outerJar.getEntry(jarPath);
2152                    final long time = entry.getTime();
2153                    if (time == -1) {
2154                        return 0;
2155                    }
2156                    return time;
2157                } else {
2158                    // 5. Entry in JAR in JAR
2159                    String innerJarPath = ((Pathname)d.cdr()).asEntryPath();
2160                    ZipEntry entry = outerJar.getEntry(entryPath);
2161                    ZipInputStream innerJarInputStream
2162                        = Utilities.getZipInputStream(outerJar, innerJarPath);
2163                    ZipEntry innerEntry = Utilities.getEntry(innerJarInputStream,
2164                                                             entryPath);
2165                    long time = innerEntry.getTime();
2166                    if (time == -1) {
2167                        return 0;
2168                    }
2169                    return time;
2170                }
2171            }
2172        }
2173        if (isURL()) {
2174            return getURLConnection().getLastModified();
2175        }
2176        return 0;
2177    }
2178
2179    // ### mkdir pathname
2180    private static final Primitive MKDIR = new pf_mkdir();
2181    private static class pf_mkdir extends Primitive {
2182        pf_mkdir() {
2183            super("mkdir", PACKAGE_SYS, false, "pathname");
2184        }
2185
2186        @Override
2187        public LispObject execute(LispObject arg) {
2188            final Pathname pathname = coerceToPathname(arg);
2189            if (pathname.isWild()) {
2190                error(new FileError("Bad place for a wild pathname.", pathname));
2191            }
2192            Pathname defaultedPathname =
2193                mergePathnames(pathname,
2194                               coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
2195                               NIL);
2196            if (defaultedPathname.isURL() || defaultedPathname.isJar()) {
2197                return new FileError("Cannot mkdir with a " 
2198                                     + (defaultedPathname.isURL() ? "URL" : "jar")
2199                                     + " Pathname.",
2200                                     defaultedPathname);
2201            }
2202                   
2203            File file = Utilities.getFile(defaultedPathname);
2204            return file.mkdir() ? T : NIL;
2205        }
2206    }
2207
2208    // ### rename-file filespec new-name => defaulted-new-name, old-truename, new-truename
2209    private static final Primitive RENAME_FILE = new pf_rename_file();
2210    private static class pf_rename_file extends Primitive {
2211        pf_rename_file() {
2212            super("rename-file", "filespec new-name");
2213        }
2214        @Override
2215        public LispObject execute(LispObject first, LispObject second) {
2216            final Pathname original = (Pathname) truename(first, true);
2217            final String originalNamestring = original.getNamestring();
2218            Pathname newName = coerceToPathname(second);
2219            if (newName.isWild()) {
2220                error(new FileError("Bad place for a wild pathname.", newName));
2221            }
2222            if (original.isJar()) {
2223                error(new FileError("Bad place for a jar pathname.", original));
2224            }
2225            if (newName.isJar()) {
2226                error(new FileError("Bad place for a jar pathname.", newName));
2227            }
2228            if (original.isURL()) {
2229                error(new FileError("Bad place for a URL pathname.", original));
2230            }
2231            if (newName.isURL()) {
2232                error(new FileError("Bad place for a jar pathname.", newName));
2233            }
2234               
2235            newName = mergePathnames(newName, original, NIL);
2236            final String newNamestring;
2237            if (newName instanceof LogicalPathname) {
2238                newNamestring = LogicalPathname.translateLogicalPathname((LogicalPathname) newName).getNamestring();
2239            } else {
2240                newNamestring = newName.getNamestring();
2241            }
2242            if (originalNamestring != null && newNamestring != null) {
2243                final File source = new File(originalNamestring);
2244                final File destination = new File(newNamestring);
2245                if (Utilities.isPlatformWindows) {
2246                    if (destination.isFile()) {
2247      ZipCache.remove(destination);
2248                        destination.delete();
2249                    }
2250                }
2251                if (source.renameTo(destination)) { // Success!
2252                        return LispThread.currentThread().setValues(newName, original,
2253                                                                    truename(newName, true));
2254                }
2255            }
2256            return error(new FileError("Unable to rename "
2257                                       + original.writeToString()
2258                                       + " to " + newName.writeToString()
2259                                       + "."));
2260        }
2261    }
2262
2263    // ### file-namestring pathname => namestring
2264    private static final Primitive FILE_NAMESTRING = new pf_file_namestring();
2265    private static class pf_file_namestring extends Primitive {
2266        pf_file_namestring() {
2267            super("file-namestring", "pathname");
2268        }
2269        @Override
2270        public LispObject execute(LispObject arg) {
2271            Pathname p = coerceToPathname(arg);
2272            StringBuilder sb = new StringBuilder();
2273            if (p.name instanceof AbstractString) {
2274                sb.append(p.name.getStringValue());
2275            } else if (p.name == Keyword.WILD) {
2276                sb.append('*');
2277            } else {
2278                return NIL;
2279            }
2280            if (p.type instanceof AbstractString) {
2281                sb.append('.');
2282                sb.append(p.type.getStringValue());
2283            } else if (p.type == Keyword.WILD) {
2284                sb.append(".*");
2285            }
2286            return new SimpleString(sb);
2287        }
2288    }
2289
2290    // ### host-namestring pathname => namestring
2291    private static final Primitive HOST_NAMESTRING = new pf_host_namestring();
2292    private static class pf_host_namestring extends Primitive {
2293        pf_host_namestring() {
2294            super("host-namestring", "pathname");
2295        }
2296        @Override
2297        public LispObject execute(LispObject arg) {
2298            return coerceToPathname(arg).host; // XXX URL-PATHNAME
2299        }
2300    }
2301   
2302    public String toString() {
2303        return getNamestring();
2304    }
2305
2306    public URL toURL() throws MalformedURLException {
2307  if(isURL()) {
2308      return new URL(getNamestring());
2309  } else {
2310      return toFile().toURL();
2311  }
2312    }
2313
2314    public File toFile() {
2315  if(!isURL()) {
2316      return new File(getNamestring());
2317  } else {
2318      throw new RuntimeException(this + " does not represent a file");
2319  }
2320    }
2321
2322    static {
2323        LispObject obj = Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue();
2324        Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(coerceToPathname(obj));
2325    }
2326
2327}
2328
Note: See TracBrowser for help on using the repository browser.