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

Last change on this file was 13105, checked in by ehuelsmann, 15 years ago

Fix Pathname.java failing to find boot.lisp in an "unpacked JAR" situation
found by running ABCL in the Glassfish v3 servlet container.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 89.7 KB
Line 
1/*
2 * Pathname.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Pathname.java 13105 2010-12-27 22:06:54Z ehuelsmann $
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");
1479        }
1480        @Override
1481        public LispObject execute(LispObject arg) {
1482            Pathname pathname = coerceToPathname(arg);
1483            if (pathname instanceof LogicalPathname) {
1484                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1485            }
1486
1487            LispObject result = NIL;
1488            if (pathname.isJar()) {
1489                String directory = pathname.asEntryPath();
1490                Debug.assertTrue(directory != null);  // We should only be listing directories
1491
1492                if (pathname.device.cdr() instanceof Cons) {
1493                    return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1494                }
1495
1496                if (directory.length() == 0) {
1497                    directory = "/*";
1498                } else {
1499                    if (directory.endsWith("/")) {
1500                        directory = "/" + directory + "*";
1501                    } else {
1502                        directory = "/" + directory + "/*";
1503                    }
1504                }
1505                SimpleString wildcard = new SimpleString(directory);
1506                SimpleString wildcardDirectory = new SimpleString(directory + "/");
1507
1508                ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1509                LispObject matches;
1510                for (Enumeration<? extends ZipEntry> entries = jar.entries(); 
1511                     entries.hasMoreElements();) {
1512                    ZipEntry entry = entries.nextElement();
1513                    String entryName = "/" + entry.getName();
1514
1515                    if (entryName.endsWith("/")) {
1516                        matches = Symbol.PATHNAME_MATCH_P
1517                            .execute(new SimpleString(entryName), wildcardDirectory);
1518                    } else {
1519                        matches = Symbol.PATHNAME_MATCH_P.
1520                            execute(new SimpleString(entryName), wildcard);
1521                    }
1522                    if (!matches.equals(NIL)) {
1523                        String namestring = new String(pathname.getNamestring());
1524                        namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1525                                 + entry.getName();
1526                        Pathname p = new Pathname(namestring);
1527                        result = new Cons(p, result);
1528                    }
1529                }
1530                return result;
1531            }
1532
1533            if (pathname.isURL()) {
1534                return error(new LispError("Unimplemented.")); // XXX
1535            }
1536
1537            String s = pathname.getNamestring();
1538            if (s != null) {
1539                File f = new File(s);
1540                if (f.isDirectory()) {
1541                    try {
1542                        File[] files = f.listFiles();
1543                        for (int i = files.length; i-- > 0;) {
1544                            File file = files[i];
1545                            Pathname p;
1546                            if (file.isDirectory()) {
1547                                p = Utilities.getDirectoryPathname(file);
1548                            } else {
1549                                p = new Pathname(file.getCanonicalPath());
1550                            }
1551                            result = new Cons(p, result);
1552                        }
1553                    } catch (IOException e) {
1554                        return error(new FileError("Unable to list directory " + pathname.writeToString() + ".",
1555                                                   pathname));
1556                    } catch (SecurityException e) {
1557                        Debug.trace(e);
1558                    } catch (NullPointerException e) {
1559                        Debug.trace(e);
1560                    }
1561                }
1562            }
1563            return result;
1564        }
1565    }
1566
1567    // ### match-wild-jar-pathname wild-jar-pathname
1568    static final Primitive MATCH_WILD_JAR_PATHNAME = new pf_match_wild_jar_pathname();
1569    private static class pf_match_wild_jar_pathname extends Primitive {
1570        pf_match_wild_jar_pathname() {
1571            super("match-wild-jar-pathname", PACKAGE_SYS, false, "wild-jar-pathname");
1572        }
1573        @Override
1574        public LispObject execute(LispObject arg) {
1575            Pathname pathname = coerceToPathname(arg);
1576            if (pathname instanceof LogicalPathname) {
1577                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1578            }
1579            if (!pathname.isJar()) {
1580                return new FileError("Not a jar pathname.", pathname);
1581            }
1582            if (!pathname.isWild()) {
1583                return new FileError("Not a wild pathname.", pathname);
1584            }
1585            Pathname jarPathname = new Pathname(pathname);
1586            jarPathname.directory = NIL;
1587            jarPathname.name = NIL;
1588            jarPathname.type = NIL;
1589            jarPathname.invalidateNamestring();
1590            LispObject jarTruename = truename(jarPathname, false); 
1591           
1592            // We can't match anything in a non-existent jar
1593            if (jarTruename == NIL) {
1594                return NIL;
1595            }
1596           
1597            LispObject result = NIL;
1598            String wild = "/" + pathname.asEntryPath();
1599
1600            if (pathname.device.cdr() instanceof Cons) {
1601                return error(new FileError("Unimplemented directory listing of JAR within JAR.", pathname));
1602            }
1603           
1604            final SimpleString wildcard = new SimpleString(wild);
1605
1606            ZipFile jar = ZipCache.get((Pathname)pathname.device.car());
1607
1608            for (Enumeration<? extends ZipEntry> entries = jar.entries(); entries.hasMoreElements();) {
1609                ZipEntry entry = entries.nextElement();
1610                String entryName = "/" + entry.getName();
1611               
1612                LispObject matches = Symbol.PATHNAME_MATCH_P
1613                    .execute(new SimpleString(entryName), wildcard);
1614
1615                if (!matches.equals(NIL)) {
1616                    String namestring = new String(pathname.getNamestring());
1617                    namestring = namestring.substring(0, namestring.lastIndexOf("!/") + 2)
1618                        + entry.getName();
1619                    Pathname p = new Pathname(namestring);
1620                    result = new Cons(p, result);
1621                }
1622            }
1623            return result;
1624        }
1625    }
1626
1627    public boolean isAbsolute()  {
1628        if (!directory.equals(NIL) || !(directory == null)) {
1629            if (directory instanceof Cons) {
1630                if (((Cons)directory).car().equals(Keyword.ABSOLUTE)) {
1631                    return true;
1632                }
1633            }
1634        }
1635        return false;
1636    }
1637
1638    // ### PATHNAME-JAR-P
1639    private static final Primitive PATHNAME_JAR_P = new pf_pathname_jar_p();
1640    private static class pf_pathname_jar_p extends Primitive {
1641        pf_pathname_jar_p() {
1642            super("pathname-jar-p", PACKAGE_EXT, true, "pathname",
1643                  "Predicate for whether PATHNAME references a JAR.");
1644        }
1645        @Override
1646        public LispObject execute(LispObject arg) {
1647            Pathname p = coerceToPathname(arg);
1648            return p.isJar() ? T : NIL;
1649        }
1650    }
1651
1652    public boolean isJar() {
1653        return (device instanceof Cons);
1654    }
1655
1656    // ### PATHNAME-URL-P
1657    private static final Primitive PATHNAME_URL_P = new pf_pathname_url_p();
1658    private static class pf_pathname_url_p extends Primitive {
1659        pf_pathname_url_p() {
1660            super("pathname-url-p", PACKAGE_EXT, true, "pathname",
1661                  "Predicate for whether PATHNAME references a URL.");
1662        }
1663        @Override
1664        public LispObject execute(LispObject arg) {
1665            Pathname p = coerceToPathname(arg);
1666            return p.isURL() ? T : NIL;
1667        }
1668    }
1669
1670    public boolean isURL() {
1671        return (host instanceof Cons);
1672    }
1673
1674    public boolean isWild() {
1675        if (host == Keyword.WILD || host == Keyword.WILD_INFERIORS) {
1676            return true;
1677        }
1678        if (device == Keyword.WILD || device == Keyword.WILD_INFERIORS) {
1679            return true;
1680        }
1681        if (directory instanceof Cons) {
1682            if (memq(Keyword.WILD, directory)) {
1683                return true;
1684            }
1685            if (memq(Keyword.WILD_INFERIORS, directory)) {
1686                return true;
1687            }
1688            Cons d = (Cons) directory;
1689            while (true) {
1690                if (d.car() instanceof AbstractString) {
1691                    String s = d.car().writeToString();
1692                    if (s.contains("*")) {
1693                        return true;
1694                    }
1695                }
1696                if (d.cdr() == NIL || ! (d.cdr() instanceof Cons)) {
1697                    break;
1698                }
1699                d = (Cons)d.cdr();
1700            }
1701        }
1702        if (name == Keyword.WILD || name == Keyword.WILD_INFERIORS) {
1703            return true;
1704        }
1705        if (name instanceof AbstractString) {
1706            if (name.writeToString().contains("*")) {
1707                return true;
1708            }
1709        }
1710        if (type == Keyword.WILD || type == Keyword.WILD_INFERIORS) {
1711            return true;
1712        }
1713        if (type instanceof AbstractString) {
1714            if (type.writeToString().contains("*")) {
1715                return true;
1716            }
1717        }
1718        if (version == Keyword.WILD || version == Keyword.WILD_INFERIORS) {
1719            return true;
1720        }
1721        return false;
1722    }
1723    // ### %wild-pathname-p
1724    private static final Primitive _WILD_PATHNAME_P = new pf_wild_pathname_p();
1725    static final class pf_wild_pathname_p extends Primitive {
1726        pf_wild_pathname_p() {
1727            super("%wild-pathname-p", PACKAGE_SYS, true);
1728        }
1729        @Override
1730        public LispObject execute(LispObject first, LispObject second) {
1731            Pathname pathname = coerceToPathname(first);
1732            if (second == NIL) {
1733                return pathname.isWild() ? T : NIL;
1734            }
1735            if (second == Keyword.DIRECTORY) {
1736                if (pathname.directory instanceof Cons) {
1737                    if (memq(Keyword.WILD, pathname.directory)) {
1738                        return T;
1739                    }
1740                    if (memq(Keyword.WILD_INFERIORS, pathname.directory)) {
1741                        return T;
1742                    }
1743                }
1744                return NIL;
1745            }
1746            LispObject value;
1747            if (second == Keyword.HOST) {
1748                value = pathname.host;
1749            } else if (second == Keyword.DEVICE) {
1750                value = pathname.device;
1751            } else if (second == Keyword.NAME) {
1752                value = pathname.name;
1753            } else if (second == Keyword.TYPE) {
1754                value = pathname.type;
1755            } else if (second == Keyword.VERSION) {
1756                value = pathname.version;
1757            } else {
1758                return error(new ProgramError("Unrecognized keyword "
1759                                              + second.writeToString() + "."));
1760            }
1761            if (value == Keyword.WILD || value == Keyword.WILD_INFERIORS) {
1762                return T;
1763            } else {
1764                return NIL;
1765            }
1766        }
1767    }
1768
1769    // ### merge-pathnames pathname &optional default-pathname default-version"
1770    private static final Primitive MERGE_PATHNAMES = new pf_merge_pathnames();
1771    static final class pf_merge_pathnames extends Primitive {
1772        pf_merge_pathnames() {
1773            super("merge-pathnames", "pathname &optional default-pathname default-version");
1774        }
1775        @Override
1776        public LispObject execute(LispObject arg) {
1777            Pathname pathname = coerceToPathname(arg);
1778            Pathname defaultPathname =
1779                coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
1780            LispObject defaultVersion = Keyword.NEWEST;
1781            return mergePathnames(pathname, defaultPathname, defaultVersion);
1782        }
1783        @Override
1784        public LispObject execute(LispObject first, LispObject second) {
1785            Pathname pathname = coerceToPathname(first);
1786            Pathname defaultPathname =
1787                coerceToPathname(second);
1788            LispObject defaultVersion = Keyword.NEWEST;
1789            return mergePathnames(pathname, defaultPathname, defaultVersion);
1790        }
1791        @Override
1792        public LispObject execute(LispObject first, LispObject second,
1793                                  LispObject third) {
1794            Pathname pathname = coerceToPathname(first);
1795            Pathname defaultPathname =
1796                coerceToPathname(second);
1797            LispObject defaultVersion = third;
1798            return mergePathnames(pathname, defaultPathname, defaultVersion);
1799        }
1800    }
1801
1802    public static final Pathname mergePathnames(Pathname pathname, Pathname defaultPathname) {
1803        return mergePathnames(pathname, defaultPathname, Keyword.NEWEST);
1804    }
1805
1806    public static final Pathname mergePathnames(final Pathname pathname,
1807                                                final Pathname defaultPathname,
1808                                                final LispObject defaultVersion) 
1809    {
1810        Pathname result;
1811        Pathname p = new Pathname(pathname);
1812        Pathname d;
1813
1814        if (pathname instanceof LogicalPathname) {
1815            result = new LogicalPathname();
1816            d = new Pathname(defaultPathname);
1817        } else {
1818            result = new Pathname();
1819            if (defaultPathname instanceof LogicalPathname) {
1820                d = LogicalPathname.translateLogicalPathname((LogicalPathname) defaultPathname);
1821            } else {
1822                d = new Pathname(defaultPathname);
1823            }
1824        }
1825        if (pathname.host != NIL) {
1826            result.host = p.host;
1827        } else {
1828            result.host = d.host;
1829        }
1830
1831        if (pathname.device != NIL) { // XXX if device represent JARs we want to merge
1832            result.device = p.device;
1833        } else {
1834            if (!p.isURL()) {
1835                result.device = d.device;
1836            }
1837        }
1838
1839        if (pathname.isJar()) {
1840            Cons jars = (Cons)result.device;
1841            LispObject jar = jars.car;
1842            if (jar instanceof Pathname) {
1843                Pathname defaults = new Pathname(d);
1844                if (defaults.isJar()) {
1845                    defaults.device = NIL;
1846                }
1847                Pathname o = mergePathnames((Pathname)jar, defaults);
1848                if (o.directory instanceof Cons
1849                    && ((Cons)o.directory).length() == 1) { // i.e. (:ABSOLUTE) or (:RELATIVE)
1850                    o.directory = NIL;
1851                }
1852                ((Cons)result.device).car = o;
1853            }
1854            result.directory = p.directory;
1855        } else {
1856            result.directory = mergeDirectories(p.directory, d.directory);
1857        }
1858
1859        if (pathname.name != NIL) {
1860            result.name = p.name;
1861        } else {
1862            result.name = d.name;
1863        }
1864        if (pathname.type != NIL) {
1865            result.type = p.type;
1866        } else {
1867            result.type = d.type;
1868        }
1869        if (pathname.version != NIL) {
1870            result.version = pathname.version;
1871        } else if (pathname.name instanceof AbstractString) {
1872            result.version = defaultVersion;
1873        } else if (defaultPathname.version != NIL) {
1874            result.version = defaultPathname.version;
1875        } else {
1876            result.version = defaultVersion;
1877        }
1878        if (pathname instanceof LogicalPathname) {
1879            // When we're returning a logical
1880            result.device = Keyword.UNSPECIFIC;
1881            if (result.directory.listp()) {
1882                LispObject original = result.directory;
1883                LispObject canonical = NIL;
1884                while (original != NIL) {
1885                    LispObject component = original.car();
1886                    if (component instanceof AbstractString) {
1887                        component = LogicalPathname.canonicalizeStringComponent((AbstractString) component);
1888                    }
1889                    canonical = canonical.push(component);
1890                    original = original.cdr();
1891                }
1892                result.directory = canonical.nreverse();
1893            }
1894            if (result.name instanceof AbstractString) {
1895                result.name = LogicalPathname.canonicalizeStringComponent((AbstractString) result.name);
1896            }
1897            if (result.type instanceof AbstractString) {
1898                result.type = LogicalPathname.canonicalizeStringComponent((AbstractString) result.type);
1899            }
1900        }
1901        result.invalidateNamestring();
1902        return result;
1903    }
1904
1905    private static final LispObject mergeDirectories(LispObject dir,
1906                                                     LispObject defaultDir) {
1907        if (dir == NIL) {
1908            return defaultDir;
1909        }
1910        if (dir.car() == Keyword.RELATIVE && defaultDir != NIL) {
1911            LispObject result = NIL;
1912            while (defaultDir != NIL) {
1913                result = new Cons(defaultDir.car(), result);
1914                defaultDir = defaultDir.cdr();
1915            }
1916            dir = dir.cdr(); // Skip :RELATIVE.
1917            while (dir != NIL) {
1918                result = new Cons(dir.car(), result);
1919                dir = dir.cdr();
1920            }
1921            LispObject[] array = result.copyToArray();
1922            for (int i = 0; i < array.length - 1; i++) {
1923                if (array[i] == Keyword.BACK) {
1924                    if (array[i + 1] instanceof AbstractString || array[i + 1] == Keyword.WILD) {
1925                        array[i] = null;
1926                        array[i + 1] = null;
1927                    }
1928                }
1929            }
1930            result = NIL;
1931            for (int i = 0; i < array.length; i++) {
1932                if (array[i] != null) {
1933                    result = new Cons(array[i], result);
1934                }
1935            }
1936            return result;
1937        }
1938        return dir;
1939    }
1940
1941    public static final LispObject truename(Pathname pathname) {
1942        return truename(pathname, false);
1943    }
1944
1945    public static final LispObject truename(LispObject arg) {
1946        return truename(arg, false);
1947    }
1948
1949    public static final LispObject truename(LispObject arg, boolean errorIfDoesNotExist) {
1950        final Pathname pathname = coerceToPathname(arg);
1951        return truename(pathname, errorIfDoesNotExist);
1952    }
1953
1954    /** @return The canonical TRUENAME as a Pathname if the pathname
1955     * exists, otherwise returns NIL or possibly a subtype of
1956     * LispError if there are logical problems with the input.
1957     */
1958    public static final LispObject truename(Pathname pathname,
1959                                            boolean errorIfDoesNotExist) 
1960    {
1961        if (pathname instanceof LogicalPathname) {
1962            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname) pathname);
1963        }
1964        if (pathname.isWild()) {
1965            return error(new FileError("Bad place for a wild pathname.",
1966                                       pathname));
1967        }
1968        if (!(pathname.isJar() || pathname.isURL())) {
1969            pathname
1970                = mergePathnames(pathname,
1971                                 coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1972                                 NIL);
1973            final String namestring = pathname.getNamestring();
1974            if (namestring == null) {
1975                return error(new FileError("Pathname has no namestring: " 
1976                                           + pathname.writeToString(),
1977                                           pathname));
1978            }
1979           
1980            final File file = new File(namestring);
1981            if (file.isDirectory()) {
1982                return Utilities.getDirectoryPathname(file);
1983            }
1984            if (file.exists()) {
1985                try {
1986                    return new Pathname(file.getCanonicalPath());
1987                } catch (IOException e) {
1988                    return error(new FileError(e.getMessage(), pathname));
1989                }
1990            }
1991        } else if (pathname.isURL()) {
1992            if (pathname.getInputStream() != null) {
1993              // If there is no type, query or fragment, we check to
1994              // see if there is URL available "underneath".
1995              if (pathname.name != NIL
1996                  && pathname.type == NIL
1997                  && Symbol.GETF.execute(pathname.host, QUERY, NIL) == NIL
1998                  && Symbol.GETF.execute(pathname.host, FRAGMENT, NIL) == NIL) {
1999                Pathname p = new Pathname(pathname.getNamestring() + "/");
2000                if (p.getInputStream() != null) {
2001                  return p;
2002                }
2003              }
2004              return pathname;
2005            }
2006        } else
2007        jarfile: {
2008            // Possibly canonicalize jar file directory
2009            Cons jars = (Cons) pathname.device;
2010            LispObject o = jars.car();
2011            if (o instanceof Pathname && ! (((Pathname)o).isURL())) {
2012                LispObject truename = Pathname.truename((Pathname)o, errorIfDoesNotExist);
2013                if (truename != null
2014                    && truename instanceof Pathname) {
2015                    Pathname truePathname = (Pathname)truename;
2016                    // A jar that is a directory makes no sense, so exit
2017                    if (truePathname.getNamestring().endsWith("/")) {
2018                        break jarfile;
2019                    }
2020                    jars.car = truePathname;
2021                } else {
2022                    break jarfile;
2023                }
2024            }
2025
2026            // Check for existence of a JAR file and/or JarEntry
2027            //
2028            // Cases:
2029            // 1.  JAR
2030            // 2.  JAR in JAR
2031            // 3.  JAR with Entry
2032            // 4.  JAR in JAR with Entry
2033
2034            ZipFile jarFile = ZipCache.get((Pathname)jars.car());
2035            String entryPath = pathname.asEntryPath();
2036            if (jarFile != null) {
2037                if (jars.cdr() instanceof Cons) {
2038                  Pathname inner = (Pathname) jars.cdr().car();
2039                  InputStream inputStream = Utilities.getInputStream(jarFile, inner);
2040                  if (inputStream != null) {
2041                      if (entryPath.length() == 0) {
2042                          return pathname; // Case 2
2043                      } else {
2044                          ZipInputStream zipInputStream
2045                              = new ZipInputStream(inputStream);
2046                          ZipEntry entry = Utilities.getEntry(zipInputStream,
2047                                                              entryPath,
2048                                                              false);
2049                          if (entry != null) {
2050                              // XXX this could possibly be a directory?
2051                              return pathname; // Case 4
2052                         }
2053                      }
2054                  }
2055                } else {
2056                    if (entryPath.length() == 0) {
2057                        return pathname; // Case 1
2058                    } else {
2059                        ZipEntry entry = jarFile.getEntry(entryPath);
2060                        if (entry != null) {
2061                            // ensure this isn't a directory
2062                            if (entry.isDirectory()) {
2063                                break jarfile;
2064                            }
2065                            try {
2066                                InputStream input = jarFile.getInputStream(entry);
2067                                if (input != null) {
2068                                    return pathname; // Case 3
2069                                }
2070                            } catch (IOException e) {
2071                                break jarfile;
2072                            }
2073                        }
2074                    }
2075                }
2076            }
2077        }
2078        error:
2079        if (errorIfDoesNotExist) {
2080            StringBuilder sb = new StringBuilder("The file ");
2081            sb.append(pathname.writeToString());
2082            sb.append(" does not exist.");
2083            return error(new FileError(sb.toString(), pathname));
2084        }
2085        return NIL;
2086    }
2087
2088
2089    protected static URL makeURL(Pathname pathname) {
2090        URL result = null;
2091        try {
2092            if (pathname.isURL()) {
2093                result = new URL(pathname.getNamestring());
2094            } else {
2095                // XXX Properly encode Windows drive letters and UNC paths
2096                // XXX ensure that we have cannonical path?
2097                result = new URL("file://" + pathname.getNamestring());
2098            }
2099        } catch (MalformedURLException e) {
2100            Debug.trace("Could not form URL from " + pathname);
2101        }
2102        return result;
2103    }
2104
2105    public InputStream getInputStream() {
2106        InputStream result = null;
2107        if (isJar()) {
2108            String entryPath = asEntryPath();
2109            // XXX We only return the bytes of an entry in a JAR
2110            Debug.assertTrue(entryPath != null);
2111            ZipFile jarFile = ZipCache.get((Pathname)device.car());
2112            Debug.assertTrue(jarFile != null);
2113            // Is this a JAR within a JAR?
2114            if (device.cdr() instanceof Cons) {
2115                Pathname inner = (Pathname) device.cdr().car();
2116                InputStream input = Utilities.getInputStream(jarFile, inner);
2117                ZipInputStream zipInputStream = new ZipInputStream(input);
2118                result =  Utilities.getEntryAsInputStream(zipInputStream, entryPath);
2119            } else {
2120                ZipEntry entry = jarFile.getEntry(entryPath);
2121                if (entry == null) {
2122                    Debug.trace("Failed to get InputStream for "   
2123                                + "'" + getNamestring() + "'");
2124                    // XXX should this be fatal?
2125                    Debug.assertTrue(false);
2126                }
2127                try {
2128                    result = jarFile.getInputStream(entry);
2129                } catch (IOException e) {
2130                    Debug.warn("Failed to get InputStream from "
2131                                + "'" + getNamestring() + "'"
2132                                + ": " + e);
2133                }
2134            }
2135        } else if (isURL()) {
2136            URL url = this.toURL();
2137            try { 
2138                result = url.openStream();
2139            } catch (IOException e) {
2140                Debug.warn("Failed to get InputStream from "
2141                            + "'" + getNamestring() + "'"
2142                            + ": " + e);
2143            }
2144        } else {
2145            File file = Utilities.getFile(this);
2146            try { 
2147                result = new FileInputStream(file);
2148            } catch (IOException e) {
2149                Debug.warn("Failed to get InputStream from "
2150                            + "'" + getNamestring() + "'"
2151                            + ": " + e);
2152            }
2153        }
2154        return result;
2155    }
2156
2157    /** @return Time in milliseconds since the UNIX epoch at which the
2158     * resource was last modified, or 0 if the time is unknown.
2159     */
2160    public long getLastModified() {
2161        if (!(isJar() || isURL())) {
2162            File f = Utilities.getFile(this);
2163            return f.lastModified();
2164        }
2165
2166        if (isJar()) {
2167            // JAR cases
2168            // 0.  JAR from URL
2169            // 1.  JAR
2170            // 2.  JAR in JAR
2171            // 3.  Entry in JAR
2172            // 4.  Entry in JAR in JAR
2173            String entryPath = asEntryPath();
2174            Cons d = (Cons)device;
2175            if (d.cdr().equals(NIL)) {
2176                if (entryPath.length() == 0) {
2177                    LispObject o = d.car();
2178                        // 0. JAR from URL
2179                        // 1. JAR
2180                    return ((Pathname)o).getLastModified();
2181                } else {
2182                    // 3. Entry in JAR
2183                    final ZipEntry entry
2184                        = ZipCache.get((Pathname)device.car()).getEntry(entryPath);
2185                    if (entry == null) {
2186                        return 0;
2187                    }
2188                    final long time = entry.getTime();
2189                    if (time == -1) {
2190                        return 0;
2191                    }
2192                    return time;
2193                }
2194            } else {
2195                ZipFile outerJar = ZipCache.get((Pathname)d.car());
2196                if (entryPath.length() == 0) {
2197                    // 4.  JAR in JAR
2198                    String jarPath = ((Pathname)d.cdr()).asEntryPath();
2199                    final ZipEntry entry = outerJar.getEntry(jarPath);
2200                    final long time = entry.getTime();
2201                    if (time == -1) {
2202                        return 0;
2203                    }
2204                    return time;
2205                } else {
2206                    // 5. Entry in JAR in JAR
2207                    String innerJarPath = ((Pathname)d.cdr()).asEntryPath();
2208                    ZipEntry entry = outerJar.getEntry(entryPath);
2209                    ZipInputStream innerJarInputStream
2210                        = Utilities.getZipInputStream(outerJar, innerJarPath);
2211                    ZipEntry innerEntry = Utilities.getEntry(innerJarInputStream,
2212                                                             entryPath);
2213                    long time = innerEntry.getTime();
2214                    if (time == -1) {
2215                        return 0;
2216                    }
2217                    return time;
2218                }
2219            }
2220        }
2221        if (isURL()) {
2222            return getURLConnection().getLastModified();
2223        }
2224        return 0;
2225    }
2226
2227    // ### mkdir pathname
2228    private static final Primitive MKDIR = new pf_mkdir();
2229    private static class pf_mkdir extends Primitive {
2230        pf_mkdir() {
2231            super("mkdir", PACKAGE_SYS, false, "pathname");
2232        }
2233
2234        @Override
2235        public LispObject execute(LispObject arg) {
2236            final Pathname pathname = coerceToPathname(arg);
2237            if (pathname.isWild()) {
2238                error(new FileError("Bad place for a wild pathname.", pathname));
2239            }
2240            Pathname defaultedPathname =
2241                mergePathnames(pathname,
2242                               coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
2243                               NIL);
2244            if (defaultedPathname.isURL() || defaultedPathname.isJar()) {
2245                return new FileError("Cannot mkdir with a " 
2246                                     + (defaultedPathname.isURL() ? "URL" : "jar")
2247                                     + " Pathname.",
2248                                     defaultedPathname);
2249            }
2250                   
2251            File file = Utilities.getFile(defaultedPathname);
2252            return file.mkdir() ? T : NIL;
2253        }
2254    }
2255
2256    // ### rename-file filespec new-name => defaulted-new-name, old-truename, new-truename
2257    private static final Primitive RENAME_FILE = new pf_rename_file();
2258    private static class pf_rename_file extends Primitive {
2259        pf_rename_file() {
2260            super("rename-file", "filespec new-name");
2261        }
2262        @Override
2263        public LispObject execute(LispObject first, LispObject second) {
2264            final Pathname original = (Pathname) truename(first, true);
2265            final String originalNamestring = original.getNamestring();
2266            Pathname newName = coerceToPathname(second);
2267            if (newName.isWild()) {
2268                error(new FileError("Bad place for a wild pathname.", newName));
2269            }
2270            if (original.isJar()) {
2271                error(new FileError("Bad place for a jar pathname.", original));
2272            }
2273            if (newName.isJar()) {
2274                error(new FileError("Bad place for a jar pathname.", newName));
2275            }
2276            if (original.isURL()) {
2277                error(new FileError("Bad place for a URL pathname.", original));
2278            }
2279            if (newName.isURL()) {
2280                error(new FileError("Bad place for a jar pathname.", newName));
2281            }
2282               
2283            newName = mergePathnames(newName, original, NIL);
2284            final String newNamestring;
2285            if (newName instanceof LogicalPathname) {
2286                newNamestring = LogicalPathname.translateLogicalPathname((LogicalPathname) newName).getNamestring();
2287            } else {
2288                newNamestring = newName.getNamestring();
2289            }
2290            if (originalNamestring != null && newNamestring != null) {
2291                final File source = new File(originalNamestring);
2292                final File destination = new File(newNamestring);
2293                if (Utilities.isPlatformWindows) {
2294                    if (destination.isFile()) {
2295                        ZipCache.remove(destination);
2296                        destination.delete();
2297                    }
2298                }
2299                if (source.renameTo(destination)) { // Success!
2300                        return LispThread.currentThread().setValues(newName, original,
2301                                                                    truename(newName, true));
2302                }
2303            }
2304            return error(new FileError("Unable to rename "
2305                                       + original.writeToString()
2306                                       + " to " + newName.writeToString()
2307                                       + "."));
2308        }
2309    }
2310
2311    // ### file-namestring pathname => namestring
2312    private static final Primitive FILE_NAMESTRING = new pf_file_namestring();
2313    private static class pf_file_namestring extends Primitive {
2314        pf_file_namestring() {
2315            super("file-namestring", "pathname");
2316        }
2317        @Override
2318        public LispObject execute(LispObject arg) {
2319            Pathname p = coerceToPathname(arg);
2320            StringBuilder sb = new StringBuilder();
2321            if (p.name instanceof AbstractString) {
2322                sb.append(p.name.getStringValue());
2323            } else if (p.name == Keyword.WILD) {
2324                sb.append('*');
2325            } else {
2326                return NIL;
2327            }
2328            if (p.type instanceof AbstractString) {
2329                sb.append('.');
2330                sb.append(p.type.getStringValue());
2331            } else if (p.type == Keyword.WILD) {
2332                sb.append(".*");
2333            }
2334            return new SimpleString(sb);
2335        }
2336    }
2337
2338    // ### host-namestring pathname => namestring
2339    private static final Primitive HOST_NAMESTRING = new pf_host_namestring();
2340    private static class pf_host_namestring extends Primitive {
2341        pf_host_namestring() {
2342            super("host-namestring", "pathname");
2343        }
2344        @Override
2345        public LispObject execute(LispObject arg) {
2346            return coerceToPathname(arg).host; // XXX URL-PATHNAME
2347        }
2348    }
2349   
2350    public String toString() {
2351        return getNamestring();
2352    }
2353
2354    public URL toURL() {
2355        try {
2356            if (isURL()) {
2357                return new URL(getNamestring());
2358            } else {
2359                return toFile().toURI().toURL();
2360            }
2361        } catch (MalformedURLException e) {
2362            error(new LispError(getNamestring() + " is not a valid URL"));
2363            return null; // not reached
2364        }
2365    }
2366
2367    public File toFile() {
2368        if(!isURL()) {
2369            return new File(getNamestring());
2370        } else {
2371            throw new RuntimeException(this + " does not represent a file");
2372        }
2373    }
2374
2375    static {
2376        LispObject obj = Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue();
2377        Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(coerceToPathname(obj));
2378    }
2379
2380    static String uriDecode(String s) {
2381        try {
2382            URI uri = new URI(null, null, null, s, null);
2383            return uri.toASCIIString().substring(1);
2384        } catch (URISyntaxException e) {}
2385        return null;  // Error
2386    }
2387
2388    static String uriEncode(String s) {
2389        // The constructor we use here only allows absolute paths, so
2390        // we manipulate the input and output correspondingly.
2391        String u;
2392        if (!s.startsWith("/")) {
2393            u = "/" + s;
2394        } else {
2395            u = new String(s);
2396        }
2397        try {
2398            URI uri = new URI("file", "", u, "");
2399            String result = uri.getRawPath();
2400            if (!s.startsWith("/")) {
2401                return result.substring(1);
2402            } 
2403            return result;
2404        } catch (URISyntaxException e) {
2405            Debug.assertTrue(false);
2406        }
2407        return null; // Error
2408    }
2409}
2410
Note: See TracBrowser for help on using the repository browser.