source: trunk/abcl/src/org/armedbear/lisp/Pathname.java

Last change on this file was 15780, checked in by Mark Evenson, 13 days ago

Correctly signal error for incorrect MAKE-PATHNAME TYPE

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