1 /* 2 * LogicalPathname.java 3 * 4 * Copyright (C) 2004-2005 Peter Graves 5 * $Id$ 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, 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 34 package org.armedbear.lisp; 35 36 import static org.armedbear.lisp.Lisp.*; 37 38 import java.util.HashMap; 39 import java.util.StringTokenizer; 40 import java.text.MessageFormat; 41 42 public final class LogicalPathname extends Pathname 43 { 44 public static final String LOGICAL_PATHNAME_CHARS 45 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-;*."; 46 private static final HashMap map 47 = new HashMap(); 48 49 // A logical host is represented as the string that names it. 50 // (defvar *logical-pathname-translations* (make-hash-table :test 'equal)) 51 public static HashTable TRANSLATIONS 52 = HashTable.newEqualHashTable(LOGICAL_PATHNAME_CHARS.length(), NIL, NIL); 53 private static final Symbol _TRANSLATIONS_ 54 = exportSpecial("*LOGICAL-PATHNAME-TRANSLATIONS*", PACKAGE_SYS, TRANSLATIONS); 55 isValidLogicalPathname(String namestring)56 static public boolean isValidLogicalPathname(String namestring) { 57 if (!isValidURL(namestring)) { 58 String host = getHostString(namestring); 59 if (host != null 60 && LogicalPathname.TRANSLATIONS.get(new SimpleString(host)) != null) { 61 return true; 62 } 63 } 64 return false; 65 } 66 LogicalPathname()67 protected LogicalPathname() { 68 } 69 70 // Used in Pathname._makePathname to indicate type for namestring create()71 public static LogicalPathname create() { 72 return new LogicalPathname(); 73 } 74 create(LogicalPathname p)75 public static LogicalPathname create(LogicalPathname p) { 76 Pathname pathname = new Pathname(); 77 pathname.copyFrom(p); 78 LogicalPathname result = new LogicalPathname(); 79 Pathname.ncoerce(pathname, result); 80 return result; 81 } 82 83 84 create(String namestring)85 public static LogicalPathname create(String namestring) { 86 // parse host out then call create(host, rest); 87 LogicalPathname result = null; 88 if (LogicalPathname.isValidLogicalPathname(namestring)) { 89 String h = LogicalPathname.getHostString(namestring); 90 result 91 = LogicalPathname.create(h, namestring.substring(namestring.indexOf(':') + 1)); 92 return result; 93 } 94 error(new FileError("Failed to find a valid logical Pathname host in '" + namestring + "'", 95 NIL)); // ??? return NIL as we don't have a 96 // PATHNAME. Maybe signal a different 97 // condition? 98 return (LogicalPathname)UNREACHED; 99 } 100 create(String host, String rest)101 public static LogicalPathname create(String host, String rest) { 102 // This may be "too late" in the creation chain to be meaningful? 103 SimpleString h = new SimpleString(host); 104 if (LogicalPathname.TRANSLATIONS.get(h) == null) { 105 // Logical pathnames are only valid when it's host exists 106 String message = MessageFormat.format("'{0}' is not a defined logical host", host); 107 error(new SimpleError(message)); 108 } 109 LogicalPathname result = new LogicalPathname(); 110 final int limit = rest.length(); 111 for (int i = 0; i < limit; i++) { 112 char c = rest.charAt (i); 113 if (LOGICAL_PATHNAME_CHARS.indexOf(c) < 0) { 114 error(new ParseError("The character #\\" + c + " is not valid in a logical pathname.")); 115 116 } 117 } 118 119 result.setHost(h); 120 121 // "The device component of a logical pathname is always :UNSPECIFIC; 122 // no other component of a logical pathname can be :UNSPECIFIC." 123 result.setDevice(Keyword.UNSPECIFIC); 124 125 int semi = rest.lastIndexOf(';'); 126 if (semi >= 0) { 127 // Directory. 128 String d = rest.substring(0, semi + 1); 129 result.setDirectory(parseDirectory(d)); 130 rest = rest.substring(semi + 1); 131 } else { 132 // "If a relative-directory-marker precedes the directories, the 133 // directory component parsed is as relative; otherwise, the 134 // directory component is parsed as absolute." 135 result.setDirectory(new Cons(Keyword.ABSOLUTE)); 136 } 137 138 int dot = rest.indexOf('.'); 139 if (dot >= 0) { 140 String n = rest.substring(0, dot); 141 if (n.equals("*")) { 142 result.setName(Keyword.WILD); 143 } else { 144 result.setName(new SimpleString(n.toUpperCase())); 145 } 146 rest = rest.substring(dot + 1); 147 dot = rest.indexOf('.'); 148 if (dot >= 0) { 149 String t = rest.substring(0, dot); 150 if (t.equals("*")) { 151 result.setType(Keyword.WILD); 152 } else { 153 result.setType(new SimpleString(t.toUpperCase())); 154 } 155 // What's left is the version. 156 String v = rest.substring(dot + 1); 157 if (v.equals("*")) { 158 result.setVersion(Keyword.WILD); 159 } else if (v.equals("NEWEST") || v.equals("newest")) { 160 result.setVersion(Keyword.NEWEST); 161 } else { 162 result.setVersion(PACKAGE_CL.intern("PARSE-INTEGER").execute(new SimpleString(v))); 163 } 164 } else { 165 String t = rest; 166 if (t.equals("*")) { 167 result.setType(Keyword.WILD); 168 } else { 169 result.setType(new SimpleString(t.toUpperCase())); 170 } 171 } 172 } else { 173 String n = rest; 174 if (n.equals("*")) { 175 result.setName(Keyword.WILD); 176 } else if (n.length() > 0) { 177 result.setName(new SimpleString(n.toUpperCase())); 178 } 179 } 180 return result; 181 } 182 canonicalizeStringComponent(AbstractString s)183 public static final SimpleString canonicalizeStringComponent(AbstractString s) { 184 final int limit = s.length(); 185 for (int i = 0; i < limit; i++) { 186 char c = s.charAt(i); 187 if (LOGICAL_PATHNAME_CHARS.indexOf(c) < 0) { 188 error(new ParseError("Invalid character #\\" + c + 189 " in logical pathname component \"" + s + 190 '"')); 191 // Not reached. 192 return null; 193 } 194 } 195 return new SimpleString(s.getStringValue().toUpperCase()); 196 } 197 translateLogicalPathname(LogicalPathname pathname)198 public static Pathname translateLogicalPathname(LogicalPathname pathname) { 199 return (Pathname) Symbol.TRANSLATE_LOGICAL_PATHNAME.execute(pathname); 200 } 201 parseDirectory(String s)202 private static final LispObject parseDirectory(String s) { 203 LispObject result; 204 if (s.charAt(0) == ';') { 205 result = new Cons(Keyword.RELATIVE); 206 s = s.substring(1); 207 } else 208 result = new Cons(Keyword.ABSOLUTE); 209 StringTokenizer st = new StringTokenizer(s, ";"); 210 while (st.hasMoreTokens()) { 211 String token = st.nextToken(); 212 LispObject obj; 213 if (token.equals("*")) 214 obj = Keyword.WILD; 215 else if (token.equals("**")) 216 obj = Keyword.WILD_INFERIORS; 217 else if (token.equals("..")) { 218 if (result.car() instanceof AbstractString) { 219 result = result.cdr(); 220 continue; 221 } 222 obj= Keyword.UP; 223 } else 224 obj = new SimpleString(token.toUpperCase()); 225 result = new Cons(obj, result); 226 } 227 return result.nreverse(); 228 } 229 230 @Override typeOf()231 public LispObject typeOf() { 232 return Symbol.LOGICAL_PATHNAME; 233 } 234 235 @Override classOf()236 public LispObject classOf() { 237 return BuiltInClass.LOGICAL_PATHNAME; 238 } 239 240 @Override typep(LispObject type)241 public LispObject typep(LispObject type) { 242 if (type == Symbol.LOGICAL_PATHNAME) 243 return T; 244 if (type == BuiltInClass.LOGICAL_PATHNAME) 245 return T; 246 return super.typep(type); 247 } 248 249 @Override getDirectoryNamestring()250 protected String getDirectoryNamestring() { 251 StringBuilder sb = new StringBuilder(); 252 // "If a pathname is converted to a namestring, the symbols NIL and 253 // :UNSPECIFIC cause the field to be treated as if it were empty. That 254 // is, both NIL and :UNSPECIFIC cause the component not to appear in 255 // the namestring." 19.2.2.2.3.1 256 if (getDirectory() != NIL) { 257 LispObject temp = getDirectory(); 258 LispObject part = temp.car(); 259 if (part == Keyword.ABSOLUTE) { 260 } else if (part == Keyword.RELATIVE) 261 sb.append(';'); 262 else 263 error(new FileError("Unsupported directory component " + part.princToString() + ".", 264 this)); 265 temp = temp.cdr(); 266 while (temp != NIL) { 267 part = temp.car(); 268 if (part instanceof AbstractString) 269 sb.append(part.getStringValue()); 270 else if (part == Keyword.WILD) 271 sb.append('*'); 272 else if (part == Keyword.WILD_INFERIORS) 273 sb.append("**"); 274 else if (part == Keyword.UP) 275 sb.append(".."); 276 else 277 error(new FileError("Unsupported directory component " + part.princToString() + ".", 278 this)); 279 sb.append(';'); 280 temp = temp.cdr(); 281 } 282 } 283 return sb.toString(); 284 } 285 286 @Override printObject()287 public String printObject() { 288 final LispThread thread = LispThread.currentThread(); 289 boolean printReadably = (Symbol.PRINT_READABLY.symbolValue(thread) != NIL); 290 boolean printEscape = (Symbol.PRINT_ESCAPE.symbolValue(thread) != NIL); 291 StringBuilder sb = new StringBuilder(); 292 if (printReadably || printEscape) 293 sb.append("#P\""); 294 sb.append(getHost().getStringValue()); 295 sb.append(':'); 296 if (getDirectory() != NIL) 297 sb.append(getDirectoryNamestring()); 298 if (getName() != NIL) { 299 if (getName() == Keyword.WILD) 300 sb.append('*'); 301 else 302 sb.append(getName().getStringValue()); 303 } 304 if (getType() != NIL) { 305 sb.append('.'); 306 if (getType() == Keyword.WILD) 307 sb.append('*'); 308 else 309 sb.append(getType().getStringValue()); 310 } 311 if (getVersion().integerp()) { 312 sb.append('.'); 313 int base = Fixnum.getValue(Symbol.PRINT_BASE.symbolValue(thread)); 314 if (getVersion() instanceof Fixnum) 315 sb.append(Integer.toString(((Fixnum)getVersion()).value, base).toUpperCase()); 316 else if (getVersion() instanceof Bignum) 317 sb.append(((Bignum)getVersion()).value.toString(base).toUpperCase()); 318 } else if (getVersion() == Keyword.WILD) { 319 sb.append(".*"); 320 } else if (getVersion() == Keyword.NEWEST) { 321 sb.append(".NEWEST"); 322 } 323 if (printReadably || printEscape) 324 sb.append('"'); 325 return sb.toString(); 326 } 327 328 // ### canonicalize-logical-host host => canonical-host 329 private static final Primitive CANONICALIZE_LOGICAL_HOST = new canonicalize_logical_host(); 330 private static class canonicalize_logical_host extends Primitive { canonicalize_logical_host()331 canonicalize_logical_host() { 332 super("canonicalize-logical-host", PACKAGE_SYS, true, "host"); 333 } 334 @Override execute(LispObject arg)335 public LispObject execute(LispObject arg) 336 { 337 AbstractString s = checkString(arg); 338 if (s.length() == 0) { 339 // "The null string, "", is not a valid value for any 340 // component of a logical pathname." 19.3.2.2 341 return error(new LispError("Invalid logical host name: \"" + 342 s.getStringValue() + '"')); 343 } 344 return canonicalizeStringComponent(s); 345 } 346 } 347 348 // ### %make-logical-pathname namestring => logical-pathname 349 private static final Primitive _MAKE_LOGICAL_PATHNAME = new _make_logical_pathname(); 350 private static class _make_logical_pathname extends Primitive { _make_logical_pathname()351 _make_logical_pathname() { 352 super("%make-logical-pathname", PACKAGE_SYS, true, "namestring"); 353 } 354 @Override execute(LispObject arg)355 public LispObject execute(LispObject arg) 356 357 { 358 // Check for a logical pathname host. 359 String s = arg.getStringValue(); 360 String h = getHostString(s); 361 if (h != null) { 362 if (h.length() == 0) { 363 // "The null string, "", is not a valid value for any 364 // component of a logical pathname." 19.3.2.2 365 return error(new LispError("Invalid logical host name: \"" + 366 h + '"')); 367 } 368 if (LogicalPathname.TRANSLATIONS.get(new SimpleString(h)) != null) { 369 // A defined logical pathname host. 370 return LogicalPathname.create(h, s.substring(s.indexOf(':') + 1)); 371 } 372 } 373 return error(new TypeError("Logical namestring does not specify a host: \"" + s + '"')); 374 } 375 } 376 377 // "one or more uppercase letters, digits, and hyphens" getHostString(String s)378 protected static String getHostString(String s) { 379 int colon = s.indexOf(':'); 380 if (colon >= 0) { 381 return s.substring(0, colon).toUpperCase(); 382 } else { 383 return null; 384 } 385 } 386 getLastModified()387 public long getLastModified() { 388 Pathname p = translateLogicalPathname(this); 389 return p.getLastModified(); 390 } 391 } 392