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

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

Backport r12549: Allow Pathname TYPE to be :UNSPECIFIC.

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