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

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

Backport r12695: Change messages from trace to warn for failing InputStream?.

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