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

Last change on this file was 13230, checked in by vvoutilainen, 14 years ago

Fix ticket #136: ABCL should allow DIRECTORY listings that don't follow symlinks, and/or provide a function for deleting a directory tree.

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