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

Last change on this file was 11815, checked in by ehuelsmann, 16 years ago

Be sure to decode URL.getPath() results before
using it as paths. Result from an audit after
finding we didn't build on paths with spaces in them.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 51.0 KB
Line 
1/*
2 * Pathname.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Pathname.java 11815 2009-05-02 20:40:54Z ehuelsmann $
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License
9 * as published by the Free Software Foundation; either version 2
10 * of the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20 *
21 * As a special exception, the copyright holders of this library give you
22 * permission to link this library with independent modules to produce an
23 * executable, regardless of the license terms of these independent
24 * modules, and to copy and distribute the resulting executable under
25 * terms of your choice, provided that you also meet, for each linked
26 * independent module, the terms and conditions of the license of that
27 * module.  An independent module is a module which is not derived from
28 * or based on this library.  If you modify this library, you may extend
29 * this exception to your version of the library, but you are not
30 * obligated to do so.  If you do not wish to do so, delete this
31 * exception statement from your version.
32 */
33
34package org.armedbear.lisp;
35
36import java.io.File;
37import java.io.IOException;
38import java.net.URL;
39import java.net.URLDecoder;
40import java.util.StringTokenizer;
41
42public class Pathname extends LispObject
43{
44    protected LispObject host = NIL;
45    protected LispObject device = NIL;
46    protected LispObject directory = NIL;
47    protected LispObject name = NIL;
48
49    // A string, NIL, :WILD or :UNSPECIFIC.
50    protected LispObject type = NIL;
51
52    // A positive integer, or NIL, :WILD, :UNSPECIFIC, or :NEWEST.
53    protected LispObject version = NIL;
54
55    private String namestring;
56
57    protected Pathname()
58    {
59    }
60
61    public Pathname(String s) throws ConditionThrowable
62    {
63        init(s);
64    }
65
66    public Pathname(URL url) throws ConditionThrowable
67    {
68        String protocol = url.getProtocol();
69        if ("jar".equals(protocol)) {
70            String s;
71            try {
72                s = URLDecoder.decode(url.getPath(),"UTF-8");
73            }
74            catch (java.io.UnsupportedEncodingException uee) {
75                // Can't happen: every Java is supposed to support
76                // at least UTF-8 encoding
77                s = null;
78            }
79            if (s.startsWith("file:")) {
80                int index = s.indexOf("!/");
81                String container = s.substring(5, index);
82                if (Utilities.isPlatformWindows) {
83                    if (container.length() > 0 && container.charAt(0) == '/')
84                        container = container.substring(1);
85                }
86                device = new Pathname(container);
87                s = s.substring(index + 1);
88                Pathname p = new Pathname(s);
89                directory = p.directory;
90                name = p.name;
91                type = p.type;
92                return;
93            }
94        } else if ("file".equals(protocol)) {
95            String s;
96            try {
97                s = URLDecoder.decode(url.getPath(),"UTF-8");
98            }
99            catch (java.io.UnsupportedEncodingException uee) {
100                // Can't happen: every Java is supposed to support
101                // at least UTF-8 encoding
102                s = null;
103            }
104            if (s != null && s.startsWith("file:")) {
105                init(s.substring(5));
106                return;
107            }
108        }
109        error(new LispError("Unsupported URL: \"" + url.toString() + '"'));
110    }
111
112    private final void init(String s) throws ConditionThrowable
113    {
114        if (s == null)
115            return;
116        if (s.equals(".") || s.equals("./") ||
117            (Utilities.isPlatformWindows && s.equals(".\\"))) {
118            directory = new Cons(Keyword.RELATIVE);
119            return;
120        }
121        if (s.equals("..") || s.equals("../")) {
122            directory = list(Keyword.RELATIVE, Keyword.UP);
123            return;
124        }
125        if (Utilities.isPlatformWindows) {
126            if (s.startsWith("\\\\")) {
127               //UNC path support
128               // match \\<server>\<share>\[directories-and-files]
129
130               int shareIndex = s.indexOf('\\', 2);
131               int dirIndex = s.indexOf('\\', shareIndex + 1);
132
133               if (shareIndex == -1 || dirIndex == -1)
134                  error(new LispError("Unsupported UNC path format: \"" + s + '"'));
135
136               host = new SimpleString(s.substring(2, shareIndex));
137               device = new SimpleString(s.substring(shareIndex + 1, dirIndex));
138
139               Pathname p = new Pathname(s.substring(dirIndex));
140               directory = p.directory;
141               name = p.name;
142               type = p.type;
143               version = p.version;
144               return;
145            }
146
147            s = s.replace('/', '\\');
148        }
149        // Jar file support.
150        int bang = s.indexOf("!/");
151        if (bang >= 0) {
152            Pathname container = new Pathname(s.substring(0, bang));
153            LispObject containerType = container.type;
154            if (containerType instanceof AbstractString) {
155                if (containerType.getStringValue().equalsIgnoreCase("jar")) {
156                    device = container;
157                    s = s.substring(bang + 1);
158                    Pathname p = new Pathname(s);
159                    directory = p.directory;
160                    name = p.name;
161                    type = p.type;
162                    return;
163                }
164            }
165        }
166        if (Utilities.isPlatformUnix) {
167            if (s.equals("~"))
168                s = System.getProperty("user.home").concat("/");
169            else if (s.startsWith("~/"))
170                s = System.getProperty("user.home").concat(s.substring(1));
171        }
172        namestring = s;
173        if (Utilities.isPlatformWindows) {
174            if (s.length() >= 2 && s.charAt(1) == ':') {
175                device = new SimpleString(s.charAt(0));
176                s = s.substring(2);
177            }
178        }
179        String d = null;
180        // Find last file separator char.
181        if (Utilities.isPlatformWindows) {
182            for (int i = s.length(); i-- > 0;) {
183                char c = s.charAt(i);
184                if (c == '/' || c == '\\') {
185                    d = s.substring(0, i + 1);
186                    s = s.substring(i + 1);
187                    break;
188                }
189            }
190        } else {
191            for (int i = s.length(); i-- > 0;) {
192                if (s.charAt(i) == '/') {
193                    d = s.substring(0, i + 1);
194                    s = s.substring(i + 1);
195                    break;
196                }
197            }
198        }
199        if (d != null) {
200            if (s.equals("..")) {
201                d = d.concat(s);
202                s = "";
203            }
204            directory = parseDirectory(d);
205        }
206        if (s.startsWith(".")) {
207            name = new SimpleString(s);
208            return;
209        }
210        int index = s.lastIndexOf('.');
211        String n = null;
212        String t = null;
213        if (index > 0) {
214            n = s.substring(0, index);
215            t = s.substring(index + 1);
216        } else if (s.length() > 0)
217            n = s;
218        if (n != null) {
219            if (n.equals("*"))
220                name = Keyword.WILD;
221            else
222                name = new SimpleString(n);
223        }
224        if (t != null) {
225            if (t.equals("*"))
226                type = Keyword.WILD;
227            else
228                type = new SimpleString(t);
229        }
230    }
231
232    private static final LispObject parseDirectory(String d)
233        throws ConditionThrowable
234    {
235        if (d.equals("/") || (Utilities.isPlatformWindows && d.equals("\\")))
236            return new Cons(Keyword.ABSOLUTE);
237        LispObject result;
238        if (d.startsWith("/") || (Utilities.isPlatformWindows && d.startsWith("\\")))
239            result = new Cons(Keyword.ABSOLUTE);
240        else
241            result = new Cons(Keyword.RELATIVE);
242        StringTokenizer st = new StringTokenizer(d, "/\\");
243        while (st.hasMoreTokens()) {
244            String token = st.nextToken();
245            LispObject obj;
246            if (token.equals("*"))
247                obj = Keyword.WILD;
248            else if (token.equals("**"))
249                obj = Keyword.WILD_INFERIORS;
250            else if (token.equals("..")) {
251                if (result.car() instanceof AbstractString) {
252                    result = result.cdr();
253                    continue;
254                }
255                obj= Keyword.UP;
256            } else
257                obj = new SimpleString(token);
258            result = new Cons(obj, result);
259        }
260        return result.nreverse();
261    }
262
263    @Override
264    public LispObject getParts() throws ConditionThrowable
265    {
266        LispObject parts = NIL;
267        parts = parts.push(new Cons("HOST", host));
268        parts = parts.push(new Cons("DEVICE", device));
269        parts = parts.push(new Cons("DIRECTORY", directory));
270        parts = parts.push(new Cons("NAME", name));
271        parts = parts.push(new Cons("TYPE", type));
272        parts = parts.push(new Cons("VERSION", version));
273        return parts.nreverse();
274    }
275
276    @Override
277    public LispObject typeOf()
278    {
279        return Symbol.PATHNAME;
280    }
281
282    @Override
283    public LispObject classOf()
284    {
285        return BuiltInClass.PATHNAME;
286    }
287
288    @Override
289    public LispObject typep(LispObject type) throws ConditionThrowable
290    {
291        if (type == Symbol.PATHNAME)
292            return T;
293        if (type == BuiltInClass.PATHNAME)
294            return T;
295        return super.typep(type);
296    }
297
298    public final LispObject getDevice()
299    {
300        return device;
301    }
302
303    public String getNamestring() throws ConditionThrowable
304    {
305        if (namestring != null)
306            return namestring;
307        if (name == NIL && type != NIL) {
308            Debug.assertTrue(namestring == null);
309            return null;
310        }
311        if (directory instanceof AbstractString)
312            Debug.assertTrue(false);
313        FastStringBuffer sb = new FastStringBuffer();
314        // "If a pathname is converted to a namestring, the symbols NIL and
315        // :UNSPECIFIC cause the field to be treated as if it were empty. That
316        // is, both NIL and :UNSPECIFIC cause the component not to appear in
317        // the namestring." 19.2.2.2.3.1
318        if (host != NIL) {
319            Debug.assertTrue(host instanceof AbstractString);
320            if (! (this instanceof LogicalPathname))
321               sb.append("\\\\"); //UNC file support; if there's a host, it's a UNC path.
322            sb.append(host.getStringValue());
323            if (this instanceof LogicalPathname)
324              sb.append(':');
325            else
326              sb.append(File.separatorChar);
327        }
328        if (device == NIL) {
329        } else if (device == Keyword.UNSPECIFIC) {
330        } else if (device instanceof AbstractString) {
331            sb.append(device.getStringValue());
332            if (this instanceof LogicalPathname
333                || host == NIL)
334              sb.append(':'); // non-UNC paths
335        } else if (device instanceof Pathname) {
336            sb.append(((Pathname)device).getNamestring());
337            sb.append("!");
338        } else
339            Debug.assertTrue(false);
340        sb.append(getDirectoryNamestring());
341        if (name instanceof AbstractString) {
342            String n = name.getStringValue();
343            if (n.indexOf(File.separatorChar) >= 0) {
344                Debug.assertTrue(namestring == null);
345                return null;
346            }
347            sb.append(n);
348        } else if (name == Keyword.WILD)
349            sb.append('*');
350        if (type != NIL) {
351            sb.append('.');
352            if (type instanceof AbstractString) {
353                String t = type.getStringValue();
354                if (t.indexOf('.') >= 0) {
355                    Debug.assertTrue(namestring == null);
356                    return null;
357                }
358                sb.append(t);
359            } else if (type == Keyword.WILD)
360                sb.append('*');
361            else
362                Debug.assertTrue(false);
363        }
364        if (this instanceof LogicalPathname) {
365            if (version.integerp()) {
366                sb.append('.');
367                int base = Fixnum.getValue(Symbol.PRINT_BASE.symbolValue());
368                if (version instanceof Fixnum)
369                    sb.append(Integer.toString(((Fixnum)version).value, base).toUpperCase());
370                else if (version instanceof Bignum)
371                    sb.append(((Bignum)version).value.toString(base).toUpperCase());
372            } else if (version == Keyword.WILD) {
373                sb.append(".*");
374            } else if (version == Keyword.NEWEST) {
375                sb.append(".NEWEST");
376            }
377        }
378        return namestring = sb.toString();
379    }
380
381    protected String getDirectoryNamestring() throws ConditionThrowable
382    {
383        validateDirectory(true);
384        FastStringBuffer sb = new FastStringBuffer();
385        // "If a pathname is converted to a namestring, the symbols NIL and
386        // :UNSPECIFIC cause the field to be treated as if it were empty. That
387        // is, both NIL and :UNSPECIFIC cause the component not to appear in
388        // the namestring." 19.2.2.2.3.1
389        if (directory != NIL) {
390            final char separatorChar;
391            if (device instanceof Pathname)
392                separatorChar = '/'; // Jar file.
393            else
394                separatorChar = File.separatorChar;
395            LispObject temp = directory;
396            LispObject part = temp.car();
397            temp = temp.cdr();
398            if (part == Keyword.ABSOLUTE) {
399                sb.append(separatorChar);
400            } else if (part == Keyword.RELATIVE) {
401                if (temp == NIL) {
402                    // #p"./"
403                    sb.append('.');
404                    sb.append(separatorChar);
405                }
406                // else: Nothing to do.
407            } else {
408                error(new FileError("Unsupported directory component " +
409                                    part.writeToString() + ".",
410                                    this));
411            }
412            while (temp != NIL) {
413                part = temp.car();
414                if (part instanceof AbstractString)
415                    sb.append(part.getStringValue());
416                else if (part == Keyword.WILD)
417                    sb.append('*');
418                else if (part == Keyword.WILD_INFERIORS)
419                    sb.append("**");
420                else if (part == Keyword.UP)
421                    sb.append("..");
422                else
423                    error(new FileError("Unsupported directory component " + part.writeToString() + ".",
424                                        this));
425                sb.append(separatorChar);
426                temp = temp.cdr();
427            }
428        }
429        return sb.toString();
430    }
431
432    @Override
433    public boolean equal(LispObject obj) throws ConditionThrowable
434    {
435        if (this == obj)
436            return true;
437        if (obj instanceof Pathname) {
438            Pathname p = (Pathname) obj;
439            if (Utilities.isPlatformWindows) {
440                if (!host.equalp(p.host))
441                    return false;
442                if (!device.equalp(p.device))
443                    return false;
444                if (!directory.equalp(p.directory))
445                    return false;
446                if (!name.equalp(p.name))
447                    return false;
448                if (!type.equalp(p.type))
449                    return false;
450                // Ignore version component.
451                //if (!version.equalp(p.version))
452                //    return false;
453            } else {
454                // Unix.
455                if (!host.equal(p.host))
456                    return false;
457                if (!device.equal(p.device))
458                    return false;
459                if (!directory.equal(p.directory))
460                    return false;
461                if (!name.equal(p.name))
462                    return false;
463                if (!type.equal(p.type))
464                    return false;
465                // Ignore version component.
466                //if (!version.equal(p.version))
467                //    return false;
468            }
469            return true;
470        }
471        return false;
472    }
473
474    @Override
475    public boolean equalp(LispObject obj) throws ConditionThrowable
476    {
477        return equal(obj);
478    }
479
480    @Override
481    public int sxhash()
482    {
483        return ((host.sxhash() ^
484                 device.sxhash() ^
485                 directory.sxhash() ^
486                 name.sxhash() ^
487                 type.sxhash()) & 0x7fffffff);
488    }
489
490    @Override
491    public String writeToString() throws ConditionThrowable
492    {
493        try {
494            final LispThread thread = LispThread.currentThread();
495            boolean printReadably = (Symbol.PRINT_READABLY.symbolValue(thread) != NIL);
496            boolean printEscape = (Symbol.PRINT_ESCAPE.symbolValue(thread) != NIL);
497            boolean useNamestring;
498            String s = null;
499            try {
500                s = getNamestring();
501            }
502            catch (Throwable t) {}
503            if (s != null) {
504                useNamestring = true;
505                if (printReadably) {
506                    // We have a namestring. Check for pathname components that
507                    // can't be read from the namestring.
508                    if (host != NIL || version != NIL) {
509                        useNamestring = false;
510                    } else if (name instanceof AbstractString) {
511                        String n = name.getStringValue();
512                        if (n.equals(".") || n.equals(".."))
513                            useNamestring = false;
514                        else if (n.indexOf(File.separatorChar) >= 0)
515                            useNamestring = false;
516                    }
517                }
518            } else
519                useNamestring = false;
520            FastStringBuffer sb = new FastStringBuffer();
521            if (useNamestring) {
522                if (printReadably || printEscape)
523                    sb.append("#P\"");
524                final int limit = s.length();
525                for (int i = 0; i < limit; i++) {
526                    char c = s.charAt(i);
527                    if (printReadably || printEscape) {
528                        if (c == '\"' || c == '\\')
529                            sb.append('\\');
530                    }
531                    sb.append(c);
532                }
533                if (printReadably || printEscape)
534                    sb.append('"');
535            } else {
536                sb.append("#P(");
537                if (host != NIL) {
538                    sb.append(":HOST ");
539                    sb.append(host.writeToString());
540                    sb.append(' ');
541                }
542                if (device != NIL) {
543                    sb.append(":DEVICE ");
544                    sb.append(device.writeToString());
545                    sb.append(' ');
546                }
547                if (directory != NIL) {
548                    sb.append(":DIRECTORY ");
549                    sb.append(directory.writeToString());
550                    sb.append(" ");
551                }
552                if (name != NIL) {
553                    sb.append(":NAME ");
554                    sb.append(name.writeToString());
555                    sb.append(' ');
556                }
557                if (type != NIL) {
558                    sb.append(":TYPE ");
559                    sb.append(type.writeToString());
560                    sb.append(' ');
561                }
562                if (version != NIL) {
563                    sb.append(":VERSION ");
564                    sb.append(version.writeToString());
565                    sb.append(' ');
566                }
567                if (sb.charAt(sb.length() - 1) == ' ')
568                    sb.setLength(sb.length() - 1);
569                sb.append(')');
570            }
571            return sb.toString();
572        }
573        catch (ConditionThrowable t) {
574            return unreadableString("PATHNAME");
575        }
576    }
577
578    // A logical host is represented as the string that names it.
579    // (defvar *logical-pathname-translations* (make-hash-table :test 'equal))
580    public static EqualHashTable LOGICAL_PATHNAME_TRANSLATIONS =
581        new EqualHashTable(64, NIL, NIL);
582
583    private static final Symbol _LOGICAL_PATHNAME_TRANSLATIONS_ =
584        exportSpecial("*LOGICAL-PATHNAME-TRANSLATIONS*", PACKAGE_SYS,
585                      LOGICAL_PATHNAME_TRANSLATIONS);
586
587    public static Pathname parseNamestring(String s)
588        throws ConditionThrowable
589    {
590        return new Pathname(s);
591    }
592
593    public static Pathname parseNamestring(AbstractString namestring)
594        throws ConditionThrowable
595    {
596        // Check for a logical pathname host.
597        String s = namestring.getStringValue();
598        String h = getHostString(s);
599        if (h != null && LOGICAL_PATHNAME_TRANSLATIONS.get(new SimpleString(h)) != null) {
600            // A defined logical pathname host.
601            return new LogicalPathname(h, s.substring(s.indexOf(':') + 1));
602        }
603        return new Pathname(s);
604    }
605
606    public static Pathname parseNamestring(AbstractString namestring,
607                                           AbstractString host)
608        throws ConditionThrowable
609    {
610        // Look for a logical pathname host in the namestring.
611        String s = namestring.getStringValue();
612        String h = getHostString(s);
613        if (h != null) {
614            if (!h.equals(host.getStringValue())) {
615                error(new LispError("Host in " + s +
616                                    " does not match requested host " +
617                                    host.getStringValue()));
618                // Not reached.
619                return null;
620            }
621            // Remove host prefix from namestring.
622            s = s.substring(s.indexOf(':') + 1);
623        }
624        if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) != null) {
625            // A defined logical pathname host.
626            return new LogicalPathname(host.getStringValue(), s);
627        }
628        error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
629        // Not reached.
630        return null;
631    }
632
633    // "one or more uppercase letters, digits, and hyphens"
634    protected static String getHostString(String s)
635    {
636        int colon = s.indexOf(':');
637        if (colon >= 0)
638            return s.substring(0, colon).toUpperCase();
639        else
640            return null;
641    }
642
643    private static final void checkCaseArgument(LispObject arg)
644        throws ConditionThrowable
645    {
646        if (arg != Keyword.COMMON && arg != Keyword.LOCAL)
647            type_error(arg, list(Symbol.MEMBER, Keyword.COMMON,
648                                       Keyword.LOCAL));
649    }
650
651    // ### %pathname-host
652    private static final Primitive _PATHNAME_HOST =
653        new Primitive("%pathname-host", PACKAGE_SYS, false)
654    {
655        @Override
656        public LispObject execute(LispObject first, LispObject second)
657            throws ConditionThrowable
658        {
659            checkCaseArgument(second);
660            return coerceToPathname(first).host;
661        }
662    };
663
664    // ### %pathname-device
665    private static final Primitive _PATHNAME_DEVICE =
666        new Primitive("%pathname-device", PACKAGE_SYS, false)
667    {
668        @Override
669        public LispObject execute(LispObject first, LispObject second)
670            throws ConditionThrowable
671        {
672            checkCaseArgument(second);
673            return coerceToPathname(first).device;
674        }
675    };
676
677    // ### %pathname-directory
678    private static final Primitive _PATHNAME_DIRECTORY =
679        new Primitive("%pathname-directory", PACKAGE_SYS, false)
680    {
681        @Override
682        public LispObject execute(LispObject first, LispObject second)
683            throws ConditionThrowable
684        {
685            checkCaseArgument(second);
686            return coerceToPathname(first).directory;
687        }
688    };
689
690    // ### %pathname-name
691    private static final Primitive _PATHNAME_NAME =
692        new Primitive("%pathname-name", PACKAGE_SYS, false)
693    {
694        @Override
695        public LispObject execute(LispObject first, LispObject second)
696            throws ConditionThrowable
697        {
698            checkCaseArgument(second);
699            return coerceToPathname(first).name;
700        }
701    };
702
703    // ### %pathname-type
704    private static final Primitive _PATHNAME_TYPE =
705        new Primitive("%pathname-type", PACKAGE_SYS, false)
706    {
707        @Override
708        public LispObject execute(LispObject first, LispObject second)
709            throws ConditionThrowable
710        {
711            checkCaseArgument(second);
712            return coerceToPathname(first).type;
713        }
714    };
715
716    // ### pathname-version
717    private static final Primitive PATHNAME_VERSION =
718        new Primitive("pathname-version", "pathname")
719    {
720        @Override
721        public LispObject execute(LispObject arg) throws ConditionThrowable
722        {
723            return coerceToPathname(arg).version;
724        }
725    };
726
727    // ### namestring
728    // namestring pathname => namestring
729    private static final Primitive NAMESTRING =
730        new Primitive("namestring", "pathname")
731    {
732        @Override
733        public LispObject execute(LispObject arg) throws ConditionThrowable
734        {
735            Pathname pathname = coerceToPathname(arg);
736            String namestring = pathname.getNamestring();
737            if (namestring == null)
738                error(new SimpleError("Pathname has no namestring: " +
739                                      pathname.writeToString()));
740            return new SimpleString(namestring);
741        }
742    };
743
744    // ### directory-namestring
745    // directory-namestring pathname => namestring
746    private static final Primitive DIRECTORY_NAMESTRING =
747        new Primitive("directory-namestring", "pathname")
748    {
749        @Override
750        public LispObject execute(LispObject arg) throws ConditionThrowable
751        {
752            return new SimpleString(coerceToPathname(arg).getDirectoryNamestring());
753        }
754    };
755
756    // ### pathname pathspec => pathname
757    private static final Primitive PATHNAME =
758        new Primitive("pathname", "pathspec")
759    {
760        @Override
761        public LispObject execute(LispObject arg) throws ConditionThrowable
762        {
763            return coerceToPathname(arg);
764        }
765    };
766
767    // ### %parse-namestring string host default-pathname => pathname, position
768    private static final Primitive _PARSE_NAMESTRING =
769        new Primitive("%parse-namestring", PACKAGE_SYS, false,
770                      "namestring host default-pathname")
771    {
772        @Override
773        public LispObject execute(LispObject first, LispObject second,
774                                  LispObject third)
775            throws ConditionThrowable
776        {
777            final LispThread thread = LispThread.currentThread();
778            final AbstractString namestring = checkString(first);
779            // The HOST parameter must be a string or NIL.
780            if (second == NIL) {
781                // "If HOST is NIL, DEFAULT-PATHNAME is a logical pathname, and
782                // THING is a syntactically valid logical pathname namestring
783                // without an explicit host, then it is parsed as a logical
784                // pathname namestring on the host that is the host component
785                // of DEFAULT-PATHNAME."
786                third = coerceToPathname(third);
787                if (third instanceof LogicalPathname)
788                    second = ((LogicalPathname)third).host;
789                else
790                    return thread.setValues(parseNamestring(namestring),
791                                            namestring.LENGTH());
792            }
793            Debug.assertTrue(second != NIL);
794            final AbstractString host = checkString(second);
795            return thread.setValues(parseNamestring(namestring, host),
796                                    namestring.LENGTH());
797        }
798    };
799
800    // ### make-pathname
801    private static final Primitive MAKE_PATHNAME =
802        new Primitive("make-pathname",
803                      "&key host device directory name type version defaults case")
804    {
805        @Override
806        public LispObject execute(LispObject[] args)
807            throws ConditionThrowable
808        {
809            return _makePathname(args);
810        }
811    };
812
813    // Used by the #p reader.
814    public static final Pathname makePathname(LispObject args)
815        throws ConditionThrowable
816    {
817        return _makePathname(args.copyToArray());
818    }
819
820    private static final Pathname _makePathname(LispObject[] args)
821        throws ConditionThrowable
822    {
823        if (args.length % 2 != 0)
824            error(new ProgramError("Odd number of keyword arguments."));
825        LispObject host = NIL;
826        LispObject device = NIL;
827        LispObject directory = NIL;
828        LispObject name = NIL;
829        LispObject type = NIL;
830        LispObject version = NIL;
831        Pathname defaults = null;
832        boolean deviceSupplied = false;
833        boolean nameSupplied = false;
834        boolean typeSupplied = false;
835        for (int i = 0; i < args.length; i += 2) {
836            LispObject key = args[i];
837            LispObject value = args[i+1];
838            if (key == Keyword.HOST) {
839                host = value;
840            } else if (key == Keyword.DEVICE) {
841                device = value;
842                deviceSupplied = true;
843            } else if (key == Keyword.DIRECTORY) {
844                if (value instanceof AbstractString)
845                    directory = list(Keyword.ABSOLUTE, value);
846                else if (value == Keyword.WILD)
847                    directory = list(Keyword.ABSOLUTE, Keyword.WILD);
848                else
849                    directory = value;
850            } else if (key == Keyword.NAME) {
851                name = value;
852                nameSupplied = true;
853            } else if (key == Keyword.TYPE) {
854                type = value;
855                typeSupplied = true;
856            } else if (key == Keyword.VERSION) {
857                version = value;
858            } else if (key == Keyword.DEFAULTS) {
859                defaults = coerceToPathname(value);
860            } else if (key == Keyword.CASE) {
861                  // Ignored.
862            }
863        }
864        if (defaults != null) {
865            if (host == NIL)
866                host = defaults.host;
867            directory = mergeDirectories(directory, defaults.directory);
868            if (!deviceSupplied)
869                device = defaults.device;
870            if (!nameSupplied)
871                name = defaults.name;
872            if (!typeSupplied)
873                type = defaults.type;
874        }
875        final Pathname p;
876        final boolean logical;
877        if (host != NIL) {
878            if (host instanceof AbstractString)
879                host = LogicalPathname.canonicalizeStringComponent((AbstractString)host);
880            if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) == null) {
881                // Not a defined logical pathname host.
882                error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
883            }
884            p = new LogicalPathname();
885            logical = true;
886            p.host = host;
887            p.device = Keyword.UNSPECIFIC;
888        } else {
889            p = new Pathname();
890            logical = false;
891        }
892        if (device != NIL) {
893            if (logical) {
894                // "The device component of a logical pathname is always :UNSPECIFIC."
895                if (device != Keyword.UNSPECIFIC)
896                    error(new LispError("The device component of a logical pathname must be :UNSPECIFIC."));
897            } else
898                p.device = device;
899        }
900        if (directory != NIL) {
901            if (logical) {
902                if (directory.listp()) {
903                    LispObject d = NIL;
904                    while (directory != NIL) {
905                        LispObject component = directory.car();
906                        if (component instanceof AbstractString)
907                            d = d.push(LogicalPathname.canonicalizeStringComponent((AbstractString)component));
908                        else
909                            d = d.push(component);
910                        directory = directory.cdr();
911                    }
912                    p.directory = d.nreverse();
913                } else if (directory == Keyword.WILD || directory == Keyword.WILD_INFERIORS)
914                    p.directory = directory;
915                else
916                    error(new LispError("Invalid directory component for logical pathname: " + directory.writeToString()));
917            } else
918                p.directory = directory;
919        }
920        if (name != NIL) {
921            if (logical && name instanceof AbstractString)
922                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString)name);
923            else if (name instanceof AbstractString)
924                p.name = validateStringComponent((AbstractString)name);
925            else
926                p.name = name;
927        }
928        if (type != NIL) {
929            if (logical && type instanceof AbstractString)
930                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString)type);
931            else
932                p.type = type;
933        }
934        p.version = version;
935        return p;
936    }
937
938    private static final AbstractString validateStringComponent(AbstractString s)
939        throws ConditionThrowable
940    {
941        final int limit = s.length();
942        for (int i = 0; i < limit; i++) {
943            char c = s.charAt(i);
944            if (c == '/' || c == '\\' && Utilities.isPlatformWindows) {
945                error(new LispError("Invalid character #\\" + c +
946                                    " in pathname component \"" + s +
947                                    '"'));
948                // Not reached.
949                return null;
950            }
951        }
952        return s;
953    }
954
955    private final boolean validateDirectory(boolean signalError)
956        throws ConditionThrowable
957    {
958        LispObject temp = directory;
959        while (temp != NIL) {
960            LispObject first = temp.car();
961            temp = temp.cdr();
962            if (first == Keyword.ABSOLUTE || first == Keyword.WILD_INFERIORS) {
963                LispObject second = temp.car();
964                if (second == Keyword.UP || second == Keyword.BACK) {
965                    if (signalError) {
966                        FastStringBuffer sb = new FastStringBuffer();
967                        sb.append(first.writeToString());
968                        sb.append(" may not be followed immediately by ");
969                        sb.append(second.writeToString());
970                        sb.append('.');
971                        error(new FileError(sb.toString(), this));
972                    }
973                    return false;
974                }
975            }
976        }
977        return true;
978    }
979
980    // ### pathnamep
981    private static final Primitive PATHNAMEP =
982        new Primitive("pathnamep", "object")
983    {
984        @Override
985        public LispObject execute(LispObject arg) throws ConditionThrowable
986        {
987            return arg instanceof Pathname ? T : NIL;
988        }
989    };
990
991    // ### logical-pathname-p
992    private static final Primitive LOGICAL_PATHNAME_P =
993        new Primitive("logical-pathname-p", PACKAGE_SYS, true, "object")
994    {
995        @Override
996        public LispObject execute(LispObject arg) throws ConditionThrowable
997        {
998            return arg instanceof LogicalPathname ? T : NIL;
999        }
1000    };
1001
1002    // ### user-homedir-pathname &optional host => pathname
1003    private static final Primitive USER_HOMEDIR_PATHNAME =
1004        new Primitive("user-homedir-pathname", "&optional host")
1005    {
1006        @Override
1007        public LispObject execute(LispObject[] args) throws ConditionThrowable
1008        {
1009            switch (args.length) {
1010                case 0: {
1011                    String s = System.getProperty("user.home");
1012                    if (!s.endsWith(File.separator))
1013                        s = s.concat(File.separator);
1014                    return new Pathname(s);
1015                }
1016                case 1:
1017                    return NIL;
1018                default:
1019                    return error(new WrongNumberOfArgumentsException(this));
1020            }
1021        }
1022    };
1023
1024    // ### list-directory
1025    private static final Primitive LIST_DIRECTORY =
1026        new Primitive("list-directory", PACKAGE_SYS, true)
1027    {
1028        @Override
1029        public LispObject execute(LispObject arg) throws ConditionThrowable
1030        {
1031            Pathname pathname = coerceToPathname(arg);
1032            if (pathname instanceof LogicalPathname)
1033                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname)pathname);
1034            LispObject result = NIL;
1035            String s = pathname.getNamestring();
1036            if (s != null) {
1037                File f = new File(s);
1038                if (f.isDirectory()) {
1039                    try {
1040      File[] files = f.listFiles();
1041                        for (int i = files.length; i-- > 0;) {
1042                            File file = files[i];
1043                            Pathname p;
1044                            if (file.isDirectory())
1045                                p = Utilities.getDirectoryPathname(file);
1046                            else
1047                                p = new Pathname(file.getCanonicalPath());
1048                            result = new Cons(p, result);
1049                        }
1050                    }
1051                    catch (IOException e) {
1052                        return error(new FileError("Unable to list directory " + pathname.writeToString() + ".",
1053                                                   pathname));
1054                    }
1055                    catch (SecurityException e) {
1056                    }
1057                    catch (NullPointerException e) {
1058                    }
1059                }
1060            }
1061            return result;
1062        }
1063    };
1064
1065    public boolean isWild() throws ConditionThrowable
1066    {
1067        if (host == Keyword.WILD || host == Keyword.WILD_INFERIORS)
1068            return true;
1069        if (device == Keyword.WILD || device == Keyword.WILD_INFERIORS)
1070            return true;
1071        if (directory instanceof Cons) {
1072            if (memq(Keyword.WILD, directory))
1073                return true;
1074            if (memq(Keyword.WILD_INFERIORS, directory))
1075                return true;
1076        }
1077        if (name == Keyword.WILD || name == Keyword.WILD_INFERIORS)
1078            return true;
1079        if (type == Keyword.WILD || type == Keyword.WILD_INFERIORS)
1080            return true;
1081        if (version == Keyword.WILD || version == Keyword.WILD_INFERIORS)
1082            return true;
1083        return false;
1084    }
1085
1086    // ### %wild-pathname-p
1087    private static final Primitive _WILD_PATHNAME_P =
1088        new Primitive("%wild-pathname-p", PACKAGE_SYS, true)
1089    {
1090        @Override
1091        public LispObject execute(LispObject first, LispObject second)
1092            throws ConditionThrowable
1093        {
1094            Pathname pathname = coerceToPathname(first);
1095            if (second == NIL)
1096                return pathname.isWild() ? T : NIL;
1097            if (second == Keyword.DIRECTORY) {
1098                if (pathname.directory instanceof Cons) {
1099                    if (memq(Keyword.WILD, pathname.directory))
1100                        return T;
1101                    if (memq(Keyword.WILD_INFERIORS, pathname.directory))
1102                        return T;
1103                }
1104                return NIL;
1105            }
1106            LispObject value;
1107            if (second == Keyword.HOST)
1108                value = pathname.host;
1109            else if (second == Keyword.DEVICE)
1110                value = pathname.device;
1111            else if (second == Keyword.NAME)
1112                value = pathname.name;
1113            else if (second == Keyword.TYPE)
1114                value = pathname.type;
1115            else if (second == Keyword.VERSION)
1116                value = pathname.version;
1117            else
1118                return error(new ProgramError("Unrecognized keyword " +
1119                                              second.writeToString() + "."));
1120            if (value == Keyword.WILD || value == Keyword.WILD_INFERIORS)
1121                return T;
1122            else
1123                return NIL;
1124        }
1125    };
1126
1127    // ### merge-pathnames
1128    private static final Primitive MERGE_PATHNAMES =
1129        new Primitive("merge-pathnames",
1130                      "pathname &optional default-pathname default-version")
1131    {
1132        @Override
1133        public LispObject execute(LispObject arg) throws ConditionThrowable
1134        {
1135            Pathname pathname = coerceToPathname(arg);
1136            Pathname defaultPathname =
1137                coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
1138            LispObject defaultVersion = Keyword.NEWEST;
1139            return mergePathnames(pathname, defaultPathname, defaultVersion);
1140        }
1141        @Override
1142        public LispObject execute(LispObject first, LispObject second)
1143            throws ConditionThrowable
1144        {
1145            Pathname pathname = coerceToPathname(first);
1146            Pathname defaultPathname =
1147                coerceToPathname(second);
1148            LispObject defaultVersion = Keyword.NEWEST;
1149            return mergePathnames(pathname, defaultPathname, defaultVersion);
1150        }
1151        @Override
1152        public LispObject execute(LispObject first, LispObject second,
1153                                  LispObject third)
1154            throws ConditionThrowable
1155        {
1156            Pathname pathname = coerceToPathname(first);
1157            Pathname defaultPathname =
1158                coerceToPathname(second);
1159            LispObject defaultVersion = third;
1160            return mergePathnames(pathname, defaultPathname, defaultVersion);
1161        }
1162    };
1163
1164    public static final Pathname mergePathnames(Pathname pathname,
1165                                                Pathname defaultPathname,
1166                                                LispObject defaultVersion)
1167        throws ConditionThrowable
1168    {
1169        Pathname p;
1170        if (pathname instanceof LogicalPathname)
1171            p = new LogicalPathname();
1172        else {
1173            p = new Pathname();
1174            if (defaultPathname instanceof LogicalPathname)
1175                defaultPathname = LogicalPathname.translateLogicalPathname((LogicalPathname)defaultPathname);
1176        }
1177        if (pathname.host != NIL)
1178            p.host = pathname.host;
1179        else
1180            p.host = defaultPathname.host;
1181        if (pathname.device != NIL)
1182            p.device = pathname.device;
1183        else
1184            p.device = defaultPathname.device;
1185        p.directory =
1186            mergeDirectories(pathname.directory, defaultPathname.directory);
1187        if (pathname.name != NIL)
1188            p.name = pathname.name;
1189        else
1190            p.name = defaultPathname.name;
1191        if (pathname.type != NIL)
1192            p.type = pathname.type;
1193        else
1194            p.type = defaultPathname.type;
1195        if (pathname.version != NIL)
1196            p.version = pathname.version;
1197        else if (pathname.name instanceof AbstractString)
1198            p.version = defaultVersion;
1199        else if (defaultPathname.version != NIL)
1200            p.version = defaultPathname.version;
1201        else
1202            p.version = defaultVersion;
1203        if (p instanceof LogicalPathname) {
1204            // When we're returning a logical
1205            p.device = Keyword.UNSPECIFIC;
1206            if (p.directory.listp()) {
1207                LispObject original = p.directory;
1208                LispObject canonical = NIL;
1209                while (original != NIL) {
1210                    LispObject component = original.car();
1211                    if (component instanceof AbstractString)
1212                        component = LogicalPathname.canonicalizeStringComponent((AbstractString)component);
1213                    canonical = canonical.push(component);
1214                    original = original.cdr();
1215                }
1216                p.directory = canonical.nreverse();
1217            }
1218            if (p.name instanceof AbstractString)
1219                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString)p.name);
1220            if (p.type instanceof AbstractString)
1221                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString)p.type);
1222        }
1223        return p;
1224    }
1225
1226    private static final LispObject mergeDirectories(LispObject dir,
1227                                                     LispObject defaultDir)
1228        throws ConditionThrowable
1229    {
1230        if (dir == NIL)
1231            return defaultDir;
1232        if (dir.car() == Keyword.RELATIVE && defaultDir != NIL) {
1233            LispObject result = NIL;
1234            while (defaultDir != NIL) {
1235                result = new Cons(defaultDir.car(), result);
1236                defaultDir = defaultDir.cdr();
1237            }
1238            dir = dir.cdr(); // Skip :RELATIVE.
1239            while (dir != NIL) {
1240                result = new Cons(dir.car(), result);
1241                dir = dir.cdr();
1242            }
1243            LispObject[] array = result.copyToArray();
1244            for (int i = 0; i < array.length - 1; i++) {
1245                if (array[i] == Keyword.BACK) {
1246                    if (array[i+1] instanceof AbstractString || array[i+1] == Keyword.WILD) {
1247                        array[i] = null;
1248                        array[i+1] = null;
1249                    }
1250                }
1251            }
1252            result = NIL;
1253            for (int i = 0; i < array.length; i++) {
1254                if (array[i] != null)
1255                    result = new Cons(array[i], result);
1256            }
1257            return result;
1258        }
1259        return dir;
1260    }
1261
1262    public static final LispObject truename(LispObject arg,
1263                                            boolean errorIfDoesNotExist)
1264        throws ConditionThrowable
1265    {
1266        Pathname pathname = coerceToPathname(arg);
1267        if (pathname instanceof LogicalPathname)
1268            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname)pathname);
1269        if (pathname.isWild())
1270            return error(new FileError("Bad place for a wild pathname.",
1271                                       pathname));
1272        final Pathname defaultedPathname =
1273            mergePathnames(pathname,
1274                           coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1275                           NIL);
1276        final String namestring = defaultedPathname.getNamestring();
1277        if (namestring == null)
1278            return error(new FileError("Pathname has no namestring: " + defaultedPathname.writeToString(),
1279                                       defaultedPathname));
1280        final File file = new File(namestring);
1281        if (file.isDirectory())
1282            return Utilities.getDirectoryPathname(file);
1283        if (file.exists()) {
1284            try {
1285                return new Pathname(file.getCanonicalPath());
1286            }
1287            catch (IOException e) {
1288                return error(new LispError(e.getMessage()));
1289            }
1290        }
1291        if (errorIfDoesNotExist) {
1292            FastStringBuffer sb = new FastStringBuffer("The file ");
1293            sb.append(defaultedPathname.writeToString());
1294            sb.append(" does not exist.");
1295            return error(new FileError(sb.toString(), defaultedPathname));
1296        }
1297        return NIL;
1298    }
1299
1300    // ### mkdir
1301    private static final Primitive MKDIR =
1302        new Primitive("mkdir", PACKAGE_SYS, false)
1303    {
1304        @Override
1305        public LispObject execute(LispObject arg) throws ConditionThrowable
1306        {
1307            final Pathname pathname = coerceToPathname(arg);
1308            if (pathname.isWild())
1309                error(new FileError("Bad place for a wild pathname.", pathname));
1310            Pathname defaultedPathname =
1311                mergePathnames(pathname,
1312                               coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1313                               NIL);
1314            File file = Utilities.getFile(defaultedPathname);
1315            return file.mkdir() ? T : NIL;
1316        }
1317    };
1318
1319    // ### rename-file filespec new-name => defaulted-new-name, old-truename, new-truename
1320    public static final Primitive RENAME_FILE =
1321        new Primitive("rename-file", "filespec new-name")
1322    {
1323        @Override
1324        public LispObject execute(LispObject first, LispObject second)
1325            throws ConditionThrowable
1326        {
1327            final Pathname original = (Pathname) truename(first, true);
1328            final String originalNamestring = original.getNamestring();
1329            Pathname newName = coerceToPathname(second);
1330            if (newName.isWild())
1331                error(new FileError("Bad place for a wild pathname.", newName));
1332            newName = mergePathnames(newName, original, NIL);
1333            final String newNamestring;
1334            if (newName instanceof LogicalPathname)
1335                newNamestring = LogicalPathname.translateLogicalPathname((LogicalPathname)newName).getNamestring();
1336            else
1337                newNamestring = newName.getNamestring();
1338            if (originalNamestring != null && newNamestring != null) {
1339                final File source = new File(originalNamestring);
1340                final File destination = new File(newNamestring);
1341                if (Utilities.isPlatformWindows) {
1342                    if (destination.isFile())
1343                        destination.delete();
1344                }
1345                if (source.renameTo(destination))
1346                    // Success!
1347                    return LispThread.currentThread().setValues(newName, original,
1348                                                                truename(newName, true));
1349            }
1350            return error(new FileError("Unable to rename " +
1351                                       original.writeToString() +
1352                                       " to " + newName.writeToString() +
1353                                       "."));
1354        }
1355    };
1356
1357    // ### file-namestring pathname => namestring
1358    private static final Primitive FILE_NAMESTRING =
1359        new Primitive("file-namestring", "pathname")
1360    {
1361        @Override
1362        public LispObject execute(LispObject arg) throws ConditionThrowable
1363        {
1364            Pathname p = coerceToPathname(arg);
1365            FastStringBuffer sb = new FastStringBuffer();
1366            if (p.name instanceof AbstractString)
1367                sb.append(p.name.getStringValue());
1368            else if (p.name == Keyword.WILD)
1369                sb.append('*');
1370            else
1371                return NIL;
1372            if (p.type instanceof AbstractString) {
1373                sb.append('.');
1374                sb.append(p.type.getStringValue());
1375            } else if (p.type == Keyword.WILD)
1376                sb.append(".*");
1377            return new SimpleString(sb);
1378        }
1379    };
1380
1381    // ### host-namestring pathname => namestring
1382    private static final Primitive HOST_NAMESTRING =
1383        new Primitive("host-namestring", "pathname")
1384    {
1385        @Override
1386        public LispObject execute(LispObject arg) throws ConditionThrowable
1387        {
1388            return coerceToPathname(arg).host;
1389        }
1390    };
1391
1392    static {
1393        try {
1394            LispObject obj = Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue();
1395            Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(coerceToPathname(obj));
1396        }
1397        catch (Throwable t) {
1398            Debug.trace(t);
1399        }
1400    }
1401}
Note: See TracBrowser for help on using the repository browser.