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

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

Rename ConditionThrowable? to ControlTransfer? and remove

try/catch blocks which don't have anything to do with
non-local transfer of control.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 49.3 KB
Line 
1/*
2 * Pathname.java
3 *
4 * Copyright (C) 2003-2007 Peter Graves
5 * $Id: Pathname.java 12255 2009-11-06 22:36:32Z 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)
62    {
63        init(s);
64    }
65
66    public Pathname(URL url)
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)
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
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()
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)
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()
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()
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)
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)
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()
492    {
493        final LispThread thread = LispThread.currentThread();
494        boolean printReadably = (Symbol.PRINT_READABLY.symbolValue(thread) != NIL);
495        boolean printEscape = (Symbol.PRINT_ESCAPE.symbolValue(thread) != NIL);
496        boolean useNamestring;
497        String s = null;
498        try {
499            s = getNamestring();
500        }
501        // ### FIXME exception
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
574    // A logical host is represented as the string that names it.
575    // (defvar *logical-pathname-translations* (make-hash-table :test 'equal))
576    public static EqualHashTable LOGICAL_PATHNAME_TRANSLATIONS =
577        new EqualHashTable(64, NIL, NIL);
578
579    private static final Symbol _LOGICAL_PATHNAME_TRANSLATIONS_ =
580        exportSpecial("*LOGICAL-PATHNAME-TRANSLATIONS*", PACKAGE_SYS,
581                      LOGICAL_PATHNAME_TRANSLATIONS);
582
583    public static Pathname parseNamestring(String s)
584
585    {
586        return new Pathname(s);
587    }
588
589    public static Pathname parseNamestring(AbstractString namestring)
590
591    {
592        // Check for a logical pathname host.
593        String s = namestring.getStringValue();
594        String h = getHostString(s);
595        if (h != null && LOGICAL_PATHNAME_TRANSLATIONS.get(new SimpleString(h)) != null) {
596            // A defined logical pathname host.
597            return new LogicalPathname(h, s.substring(s.indexOf(':') + 1));
598        }
599        return new Pathname(s);
600    }
601
602    public static Pathname parseNamestring(AbstractString namestring,
603                                           AbstractString host)
604
605    {
606        // Look for a logical pathname host in the namestring.
607        String s = namestring.getStringValue();
608        String h = getHostString(s);
609        if (h != null) {
610            if (!h.equals(host.getStringValue())) {
611                error(new LispError("Host in " + s +
612                                    " does not match requested host " +
613                                    host.getStringValue()));
614                // Not reached.
615                return null;
616            }
617            // Remove host prefix from namestring.
618            s = s.substring(s.indexOf(':') + 1);
619        }
620        if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) != null) {
621            // A defined logical pathname host.
622            return new LogicalPathname(host.getStringValue(), s);
623        }
624        error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
625        // Not reached.
626        return null;
627    }
628
629    // "one or more uppercase letters, digits, and hyphens"
630    protected static String getHostString(String s)
631    {
632        int colon = s.indexOf(':');
633        if (colon >= 0)
634            return s.substring(0, colon).toUpperCase();
635        else
636            return null;
637    }
638
639    private static final void checkCaseArgument(LispObject arg)
640
641    {
642        if (arg != Keyword.COMMON && arg != Keyword.LOCAL)
643            type_error(arg, list(Symbol.MEMBER, Keyword.COMMON,
644                                       Keyword.LOCAL));
645    }
646
647    // ### %pathname-host
648    private static final Primitive _PATHNAME_HOST =
649        new Primitive("%pathname-host", PACKAGE_SYS, false)
650    {
651        @Override
652        public LispObject execute(LispObject first, LispObject second)
653
654        {
655            checkCaseArgument(second);
656            return coerceToPathname(first).host;
657        }
658    };
659
660    // ### %pathname-device
661    private static final Primitive _PATHNAME_DEVICE =
662        new Primitive("%pathname-device", PACKAGE_SYS, false)
663    {
664        @Override
665        public LispObject execute(LispObject first, LispObject second)
666
667        {
668            checkCaseArgument(second);
669            return coerceToPathname(first).device;
670        }
671    };
672
673    // ### %pathname-directory
674    private static final Primitive _PATHNAME_DIRECTORY =
675        new Primitive("%pathname-directory", PACKAGE_SYS, false)
676    {
677        @Override
678        public LispObject execute(LispObject first, LispObject second)
679
680        {
681            checkCaseArgument(second);
682            return coerceToPathname(first).directory;
683        }
684    };
685
686    // ### %pathname-name
687    private static final Primitive _PATHNAME_NAME =
688        new Primitive("%pathname-name", PACKAGE_SYS, false)
689    {
690        @Override
691        public LispObject execute(LispObject first, LispObject second)
692
693        {
694            checkCaseArgument(second);
695            return coerceToPathname(first).name;
696        }
697    };
698
699    // ### %pathname-type
700    private static final Primitive _PATHNAME_TYPE =
701        new Primitive("%pathname-type", PACKAGE_SYS, false)
702    {
703        @Override
704        public LispObject execute(LispObject first, LispObject second)
705
706        {
707            checkCaseArgument(second);
708            return coerceToPathname(first).type;
709        }
710    };
711
712    // ### pathname-version
713    private static final Primitive PATHNAME_VERSION =
714        new Primitive("pathname-version", "pathname")
715    {
716        @Override
717        public LispObject execute(LispObject arg)
718        {
719            return coerceToPathname(arg).version;
720        }
721    };
722
723    // ### namestring
724    // namestring pathname => namestring
725    private static final Primitive NAMESTRING =
726        new Primitive("namestring", "pathname")
727    {
728        @Override
729        public LispObject execute(LispObject arg)
730        {
731            Pathname pathname = coerceToPathname(arg);
732            String namestring = pathname.getNamestring();
733            if (namestring == null)
734                error(new SimpleError("Pathname has no namestring: " +
735                                      pathname.writeToString()));
736            return new SimpleString(namestring);
737        }
738    };
739
740    // ### directory-namestring
741    // directory-namestring pathname => namestring
742    private static final Primitive DIRECTORY_NAMESTRING =
743        new Primitive("directory-namestring", "pathname")
744    {
745        @Override
746        public LispObject execute(LispObject arg)
747        {
748            return new SimpleString(coerceToPathname(arg).getDirectoryNamestring());
749        }
750    };
751
752    // ### pathname pathspec => pathname
753    private static final Primitive PATHNAME =
754        new Primitive("pathname", "pathspec")
755    {
756        @Override
757        public LispObject execute(LispObject arg)
758        {
759            return coerceToPathname(arg);
760        }
761    };
762
763    // ### %parse-namestring string host default-pathname => pathname, position
764    private static final Primitive _PARSE_NAMESTRING =
765        new Primitive("%parse-namestring", PACKAGE_SYS, false,
766                      "namestring host default-pathname")
767    {
768        @Override
769        public LispObject execute(LispObject first, LispObject second,
770                                  LispObject third)
771
772        {
773            final LispThread thread = LispThread.currentThread();
774            final AbstractString namestring = checkString(first);
775            // The HOST parameter must be a string or NIL.
776            if (second == NIL) {
777                // "If HOST is NIL, DEFAULT-PATHNAME is a logical pathname, and
778                // THING is a syntactically valid logical pathname namestring
779                // without an explicit host, then it is parsed as a logical
780                // pathname namestring on the host that is the host component
781                // of DEFAULT-PATHNAME."
782                third = coerceToPathname(third);
783                if (third instanceof LogicalPathname)
784                    second = ((LogicalPathname)third).host;
785                else
786                    return thread.setValues(parseNamestring(namestring),
787                                            namestring.LENGTH());
788            }
789            Debug.assertTrue(second != NIL);
790            final AbstractString host = checkString(second);
791            return thread.setValues(parseNamestring(namestring, host),
792                                    namestring.LENGTH());
793        }
794    };
795
796    // ### make-pathname
797    private static final Primitive MAKE_PATHNAME =
798        new Primitive("make-pathname",
799                      "&key host device directory name type version defaults case")
800    {
801        @Override
802        public LispObject execute(LispObject[] args)
803
804        {
805            return _makePathname(args);
806        }
807    };
808
809    // Used by the #p reader.
810    public static final Pathname makePathname(LispObject args)
811
812    {
813        return _makePathname(args.copyToArray());
814    }
815
816    private static final Pathname _makePathname(LispObject[] args)
817
818    {
819        if (args.length % 2 != 0)
820            error(new ProgramError("Odd number of keyword arguments."));
821        LispObject host = NIL;
822        LispObject device = NIL;
823        LispObject directory = NIL;
824        LispObject name = NIL;
825        LispObject type = NIL;
826        LispObject version = NIL;
827        Pathname defaults = null;
828        boolean deviceSupplied = false;
829        boolean nameSupplied = false;
830        boolean typeSupplied = false;
831        for (int i = 0; i < args.length; i += 2) {
832            LispObject key = args[i];
833            LispObject value = args[i+1];
834            if (key == Keyword.HOST) {
835                host = value;
836            } else if (key == Keyword.DEVICE) {
837                device = value;
838                deviceSupplied = true;
839            } else if (key == Keyword.DIRECTORY) {
840                if (value instanceof AbstractString)
841                    directory = list(Keyword.ABSOLUTE, value);
842                else if (value == Keyword.WILD)
843                    directory = list(Keyword.ABSOLUTE, Keyword.WILD);
844                else
845                    directory = value;
846            } else if (key == Keyword.NAME) {
847                name = value;
848                nameSupplied = true;
849            } else if (key == Keyword.TYPE) {
850                type = value;
851                typeSupplied = true;
852            } else if (key == Keyword.VERSION) {
853                version = value;
854            } else if (key == Keyword.DEFAULTS) {
855                defaults = coerceToPathname(value);
856            } else if (key == Keyword.CASE) {
857                  // Ignored.
858            }
859        }
860        if (defaults != null) {
861            if (host == NIL)
862                host = defaults.host;
863            directory = mergeDirectories(directory, defaults.directory);
864            if (!deviceSupplied)
865                device = defaults.device;
866            if (!nameSupplied)
867                name = defaults.name;
868            if (!typeSupplied)
869                type = defaults.type;
870        }
871        final Pathname p;
872        final boolean logical;
873        if (host != NIL) {
874            if (host instanceof AbstractString)
875                host = LogicalPathname.canonicalizeStringComponent((AbstractString)host);
876            if (LOGICAL_PATHNAME_TRANSLATIONS.get(host) == null) {
877                // Not a defined logical pathname host.
878                error(new LispError(host.writeToString() + " is not defined as a logical pathname host."));
879            }
880            p = new LogicalPathname();
881            logical = true;
882            p.host = host;
883            p.device = Keyword.UNSPECIFIC;
884        } else {
885            p = new Pathname();
886            logical = false;
887        }
888        if (device != NIL) {
889            if (logical) {
890                // "The device component of a logical pathname is always :UNSPECIFIC."
891                if (device != Keyword.UNSPECIFIC)
892                    error(new LispError("The device component of a logical pathname must be :UNSPECIFIC."));
893            } else
894                p.device = device;
895        }
896        if (directory != NIL) {
897            if (logical) {
898                if (directory.listp()) {
899                    LispObject d = NIL;
900                    while (directory != NIL) {
901                        LispObject component = directory.car();
902                        if (component instanceof AbstractString)
903                            d = d.push(LogicalPathname.canonicalizeStringComponent((AbstractString)component));
904                        else
905                            d = d.push(component);
906                        directory = directory.cdr();
907                    }
908                    p.directory = d.nreverse();
909                } else if (directory == Keyword.WILD || directory == Keyword.WILD_INFERIORS)
910                    p.directory = directory;
911                else
912                    error(new LispError("Invalid directory component for logical pathname: " + directory.writeToString()));
913            } else
914                p.directory = directory;
915        }
916        if (name != NIL) {
917            if (logical && name instanceof AbstractString)
918                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString)name);
919            else if (name instanceof AbstractString)
920                p.name = validateStringComponent((AbstractString)name);
921            else
922                p.name = name;
923        }
924        if (type != NIL) {
925            if (logical && type instanceof AbstractString)
926                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString)type);
927            else
928                p.type = type;
929        }
930        p.version = version;
931        return p;
932    }
933
934    private static final AbstractString validateStringComponent(AbstractString s)
935
936    {
937        final int limit = s.length();
938        for (int i = 0; i < limit; i++) {
939            char c = s.charAt(i);
940            if (c == '/' || c == '\\' && Utilities.isPlatformWindows) {
941                error(new LispError("Invalid character #\\" + c +
942                                    " in pathname component \"" + s +
943                                    '"'));
944                // Not reached.
945                return null;
946            }
947        }
948        return s;
949    }
950
951    private final boolean validateDirectory(boolean signalError)
952
953    {
954        LispObject temp = directory;
955        while (temp != NIL) {
956            LispObject first = temp.car();
957            temp = temp.cdr();
958            if (first == Keyword.ABSOLUTE || first == Keyword.WILD_INFERIORS) {
959                LispObject second = temp.car();
960                if (second == Keyword.UP || second == Keyword.BACK) {
961                    if (signalError) {
962                        FastStringBuffer sb = new FastStringBuffer();
963                        sb.append(first.writeToString());
964                        sb.append(" may not be followed immediately by ");
965                        sb.append(second.writeToString());
966                        sb.append('.');
967                        error(new FileError(sb.toString(), this));
968                    }
969                    return false;
970                }
971            }
972        }
973        return true;
974    }
975
976    // ### pathnamep
977    private static final Primitive PATHNAMEP =
978        new Primitive("pathnamep", "object")
979    {
980        @Override
981        public LispObject execute(LispObject arg)
982        {
983            return arg instanceof Pathname ? T : NIL;
984        }
985    };
986
987    // ### logical-pathname-p
988    private static final Primitive LOGICAL_PATHNAME_P =
989        new Primitive("logical-pathname-p", PACKAGE_SYS, true, "object")
990    {
991        @Override
992        public LispObject execute(LispObject arg)
993        {
994            return arg instanceof LogicalPathname ? T : NIL;
995        }
996    };
997
998    // ### user-homedir-pathname &optional host => pathname
999    private static final Primitive USER_HOMEDIR_PATHNAME =
1000        new Primitive("user-homedir-pathname", "&optional host")
1001    {
1002        @Override
1003        public LispObject execute(LispObject[] args)
1004        {
1005            switch (args.length) {
1006                case 0: {
1007                    String s = System.getProperty("user.home");
1008                    if (!s.endsWith(File.separator))
1009                        s = s.concat(File.separator);
1010                    return new Pathname(s);
1011                }
1012                case 1:
1013                    return NIL;
1014                default:
1015                    return error(new WrongNumberOfArgumentsException(this));
1016            }
1017        }
1018    };
1019
1020    // ### list-directory
1021    private static final Primitive LIST_DIRECTORY =
1022        new Primitive("list-directory", PACKAGE_SYS, true)
1023    {
1024        @Override
1025        public LispObject execute(LispObject arg)
1026        {
1027            Pathname pathname = coerceToPathname(arg);
1028            if (pathname instanceof LogicalPathname)
1029                pathname = LogicalPathname.translateLogicalPathname((LogicalPathname)pathname);
1030            LispObject result = NIL;
1031            String s = pathname.getNamestring();
1032            if (s != null) {
1033                File f = new File(s);
1034                if (f.isDirectory()) {
1035                    try {
1036      File[] files = f.listFiles();
1037                        for (int i = files.length; i-- > 0;) {
1038                            File file = files[i];
1039                            Pathname p;
1040                            if (file.isDirectory())
1041                                p = Utilities.getDirectoryPathname(file);
1042                            else
1043                                p = new Pathname(file.getCanonicalPath());
1044                            result = new Cons(p, result);
1045                        }
1046                    }
1047                    catch (IOException e) {
1048                        return error(new FileError("Unable to list directory " + pathname.writeToString() + ".",
1049                                                   pathname));
1050                    }
1051                    catch (SecurityException e) {
1052                    }
1053                    catch (NullPointerException e) {
1054                    }
1055                }
1056            }
1057            return result;
1058        }
1059    };
1060
1061    public boolean isWild()
1062    {
1063        if (host == Keyword.WILD || host == Keyword.WILD_INFERIORS)
1064            return true;
1065        if (device == Keyword.WILD || device == Keyword.WILD_INFERIORS)
1066            return true;
1067        if (directory instanceof Cons) {
1068            if (memq(Keyword.WILD, directory))
1069                return true;
1070            if (memq(Keyword.WILD_INFERIORS, directory))
1071                return true;
1072        }
1073        if (name == Keyword.WILD || name == Keyword.WILD_INFERIORS)
1074            return true;
1075        if (type == Keyword.WILD || type == Keyword.WILD_INFERIORS)
1076            return true;
1077        if (version == Keyword.WILD || version == Keyword.WILD_INFERIORS)
1078            return true;
1079        return false;
1080    }
1081
1082    // ### %wild-pathname-p
1083    private static final Primitive _WILD_PATHNAME_P =
1084        new Primitive("%wild-pathname-p", PACKAGE_SYS, true)
1085    {
1086        @Override
1087        public LispObject execute(LispObject first, LispObject second)
1088
1089        {
1090            Pathname pathname = coerceToPathname(first);
1091            if (second == NIL)
1092                return pathname.isWild() ? T : NIL;
1093            if (second == Keyword.DIRECTORY) {
1094                if (pathname.directory instanceof Cons) {
1095                    if (memq(Keyword.WILD, pathname.directory))
1096                        return T;
1097                    if (memq(Keyword.WILD_INFERIORS, pathname.directory))
1098                        return T;
1099                }
1100                return NIL;
1101            }
1102            LispObject value;
1103            if (second == Keyword.HOST)
1104                value = pathname.host;
1105            else if (second == Keyword.DEVICE)
1106                value = pathname.device;
1107            else if (second == Keyword.NAME)
1108                value = pathname.name;
1109            else if (second == Keyword.TYPE)
1110                value = pathname.type;
1111            else if (second == Keyword.VERSION)
1112                value = pathname.version;
1113            else
1114                return error(new ProgramError("Unrecognized keyword " +
1115                                              second.writeToString() + "."));
1116            if (value == Keyword.WILD || value == Keyword.WILD_INFERIORS)
1117                return T;
1118            else
1119                return NIL;
1120        }
1121    };
1122
1123    // ### merge-pathnames
1124    private static final Primitive MERGE_PATHNAMES =
1125        new Primitive("merge-pathnames",
1126                      "pathname &optional default-pathname default-version")
1127    {
1128        @Override
1129        public LispObject execute(LispObject arg)
1130        {
1131            Pathname pathname = coerceToPathname(arg);
1132            Pathname defaultPathname =
1133                coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue());
1134            LispObject defaultVersion = Keyword.NEWEST;
1135            return mergePathnames(pathname, defaultPathname, defaultVersion);
1136        }
1137        @Override
1138        public LispObject execute(LispObject first, LispObject second)
1139
1140        {
1141            Pathname pathname = coerceToPathname(first);
1142            Pathname defaultPathname =
1143                coerceToPathname(second);
1144            LispObject defaultVersion = Keyword.NEWEST;
1145            return mergePathnames(pathname, defaultPathname, defaultVersion);
1146        }
1147        @Override
1148        public LispObject execute(LispObject first, LispObject second,
1149                                  LispObject third)
1150
1151        {
1152            Pathname pathname = coerceToPathname(first);
1153            Pathname defaultPathname =
1154                coerceToPathname(second);
1155            LispObject defaultVersion = third;
1156            return mergePathnames(pathname, defaultPathname, defaultVersion);
1157        }
1158    };
1159
1160    public static final Pathname mergePathnames(Pathname pathname,
1161                                                Pathname defaultPathname,
1162                                                LispObject defaultVersion)
1163
1164    {
1165        Pathname p;
1166        if (pathname instanceof LogicalPathname)
1167            p = new LogicalPathname();
1168        else {
1169            p = new Pathname();
1170            if (defaultPathname instanceof LogicalPathname)
1171                defaultPathname = LogicalPathname.translateLogicalPathname((LogicalPathname)defaultPathname);
1172        }
1173        if (pathname.host != NIL)
1174            p.host = pathname.host;
1175        else
1176            p.host = defaultPathname.host;
1177        if (pathname.device != NIL)
1178            p.device = pathname.device;
1179        else
1180            p.device = defaultPathname.device;
1181        p.directory =
1182            mergeDirectories(pathname.directory, defaultPathname.directory);
1183        if (pathname.name != NIL)
1184            p.name = pathname.name;
1185        else
1186            p.name = defaultPathname.name;
1187        if (pathname.type != NIL)
1188            p.type = pathname.type;
1189        else
1190            p.type = defaultPathname.type;
1191        if (pathname.version != NIL)
1192            p.version = pathname.version;
1193        else if (pathname.name instanceof AbstractString)
1194            p.version = defaultVersion;
1195        else if (defaultPathname.version != NIL)
1196            p.version = defaultPathname.version;
1197        else
1198            p.version = defaultVersion;
1199        if (p instanceof LogicalPathname) {
1200            // When we're returning a logical
1201            p.device = Keyword.UNSPECIFIC;
1202            if (p.directory.listp()) {
1203                LispObject original = p.directory;
1204                LispObject canonical = NIL;
1205                while (original != NIL) {
1206                    LispObject component = original.car();
1207                    if (component instanceof AbstractString)
1208                        component = LogicalPathname.canonicalizeStringComponent((AbstractString)component);
1209                    canonical = canonical.push(component);
1210                    original = original.cdr();
1211                }
1212                p.directory = canonical.nreverse();
1213            }
1214            if (p.name instanceof AbstractString)
1215                p.name = LogicalPathname.canonicalizeStringComponent((AbstractString)p.name);
1216            if (p.type instanceof AbstractString)
1217                p.type = LogicalPathname.canonicalizeStringComponent((AbstractString)p.type);
1218        }
1219        return p;
1220    }
1221
1222    private static final LispObject mergeDirectories(LispObject dir,
1223                                                     LispObject defaultDir)
1224
1225    {
1226        if (dir == NIL)
1227            return defaultDir;
1228        if (dir.car() == Keyword.RELATIVE && defaultDir != NIL) {
1229            LispObject result = NIL;
1230            while (defaultDir != NIL) {
1231                result = new Cons(defaultDir.car(), result);
1232                defaultDir = defaultDir.cdr();
1233            }
1234            dir = dir.cdr(); // Skip :RELATIVE.
1235            while (dir != NIL) {
1236                result = new Cons(dir.car(), result);
1237                dir = dir.cdr();
1238            }
1239            LispObject[] array = result.copyToArray();
1240            for (int i = 0; i < array.length - 1; i++) {
1241                if (array[i] == Keyword.BACK) {
1242                    if (array[i+1] instanceof AbstractString || array[i+1] == Keyword.WILD) {
1243                        array[i] = null;
1244                        array[i+1] = null;
1245                    }
1246                }
1247            }
1248            result = NIL;
1249            for (int i = 0; i < array.length; i++) {
1250                if (array[i] != null)
1251                    result = new Cons(array[i], result);
1252            }
1253            return result;
1254        }
1255        return dir;
1256    }
1257
1258    public static final LispObject truename(LispObject arg,
1259                                            boolean errorIfDoesNotExist)
1260
1261    {
1262        Pathname pathname = coerceToPathname(arg);
1263        if (pathname instanceof LogicalPathname)
1264            pathname = LogicalPathname.translateLogicalPathname((LogicalPathname)pathname);
1265        if (pathname.isWild())
1266            return error(new FileError("Bad place for a wild pathname.",
1267                                       pathname));
1268        final Pathname defaultedPathname =
1269            mergePathnames(pathname,
1270                           coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1271                           NIL);
1272        final String namestring = defaultedPathname.getNamestring();
1273        if (namestring == null)
1274            return error(new FileError("Pathname has no namestring: " + defaultedPathname.writeToString(),
1275                                       defaultedPathname));
1276        final File file = new File(namestring);
1277        if (file.isDirectory())
1278            return Utilities.getDirectoryPathname(file);
1279        if (file.exists()) {
1280            try {
1281                return new Pathname(file.getCanonicalPath());
1282            }
1283            catch (IOException e) {
1284                return error(new LispError(e.getMessage()));
1285            }
1286        }
1287        if (errorIfDoesNotExist) {
1288            FastStringBuffer sb = new FastStringBuffer("The file ");
1289            sb.append(defaultedPathname.writeToString());
1290            sb.append(" does not exist.");
1291            return error(new FileError(sb.toString(), defaultedPathname));
1292        }
1293        return NIL;
1294    }
1295
1296    // ### mkdir
1297    private static final Primitive MKDIR =
1298        new Primitive("mkdir", PACKAGE_SYS, false)
1299    {
1300        @Override
1301        public LispObject execute(LispObject arg)
1302        {
1303            final Pathname pathname = coerceToPathname(arg);
1304            if (pathname.isWild())
1305                error(new FileError("Bad place for a wild pathname.", pathname));
1306            Pathname defaultedPathname =
1307                mergePathnames(pathname,
1308                               coerceToPathname(Symbol.DEFAULT_PATHNAME_DEFAULTS.symbolValue()),
1309                               NIL);
1310            File file = Utilities.getFile(defaultedPathname);
1311            return file.mkdir() ? T : NIL;
1312        }
1313    };
1314
1315    // ### rename-file filespec new-name => defaulted-new-name, old-truename, new-truename
1316    public static final Primitive RENAME_FILE =
1317        new Primitive("rename-file", "filespec new-name")
1318    {
1319        @Override
1320        public LispObject execute(LispObject first, LispObject second)
1321
1322        {
1323            final Pathname original = (Pathname) truename(first, true);
1324            final String originalNamestring = original.getNamestring();
1325            Pathname newName = coerceToPathname(second);
1326            if (newName.isWild())
1327                error(new FileError("Bad place for a wild pathname.", newName));
1328            newName = mergePathnames(newName, original, NIL);
1329            final String newNamestring;
1330            if (newName instanceof LogicalPathname)
1331                newNamestring = LogicalPathname.translateLogicalPathname((LogicalPathname)newName).getNamestring();
1332            else
1333                newNamestring = newName.getNamestring();
1334            if (originalNamestring != null && newNamestring != null) {
1335                final File source = new File(originalNamestring);
1336                final File destination = new File(newNamestring);
1337                if (Utilities.isPlatformWindows) {
1338                    if (destination.isFile())
1339                        destination.delete();
1340                }
1341                if (source.renameTo(destination))
1342                    // Success!
1343                    return LispThread.currentThread().setValues(newName, original,
1344                                                                truename(newName, true));
1345            }
1346            return error(new FileError("Unable to rename " +
1347                                       original.writeToString() +
1348                                       " to " + newName.writeToString() +
1349                                       "."));
1350        }
1351    };
1352
1353    // ### file-namestring pathname => namestring
1354    private static final Primitive FILE_NAMESTRING =
1355        new Primitive("file-namestring", "pathname")
1356    {
1357        @Override
1358        public LispObject execute(LispObject arg)
1359        {
1360            Pathname p = coerceToPathname(arg);
1361            FastStringBuffer sb = new FastStringBuffer();
1362            if (p.name instanceof AbstractString)
1363                sb.append(p.name.getStringValue());
1364            else if (p.name == Keyword.WILD)
1365                sb.append('*');
1366            else
1367                return NIL;
1368            if (p.type instanceof AbstractString) {
1369                sb.append('.');
1370                sb.append(p.type.getStringValue());
1371            } else if (p.type == Keyword.WILD)
1372                sb.append(".*");
1373            return new SimpleString(sb);
1374        }
1375    };
1376
1377    // ### host-namestring pathname => namestring
1378    private static final Primitive HOST_NAMESTRING =
1379        new Primitive("host-namestring", "pathname")
1380    {
1381        @Override
1382        public LispObject execute(LispObject arg)
1383        {
1384            return coerceToPathname(arg).host;
1385        }
1386    };
1387
1388    static {
1389        try {
1390            LispObject obj = Symbol.DEFAULT_PATHNAME_DEFAULTS.getSymbolValue();
1391            Symbol.DEFAULT_PATHNAME_DEFAULTS.setSymbolValue(coerceToPathname(obj));
1392        }
1393        catch (Throwable t) {
1394            Debug.trace(t);
1395        }
1396    }
1397}
Note: See TracBrowser for help on using the repository browser.