1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package java.io; 19 20 import java.net.URI; 21 import java.net.URISyntaxException; 22 import java.net.URL; 23 import java.security.AccessController; 24 import java.security.SecureRandom; 25 import java.util.ArrayList; 26 import java.util.List; 27 28 import org.apache.harmony.luni.internal.io.FileCanonPathCache; 29 import org.apache.harmony.luni.util.DeleteOnExit; 30 import org.apache.harmony.luni.util.Msg; 31 import org.apache.harmony.luni.util.PriviAction; 32 import org.apache.harmony.luni.util.Util; 33 34 /** 35 * An "abstract" representation of a file system entity identified by a 36 * pathname. The pathname may be absolute (relative to the root directory 37 * of the file system) or relative to the current directory in which the program 38 * is running. 39 * <p> 40 * This class provides methods for querying/changing information about the file 41 * as well as directory listing capabilities if the file represents a directory. 42 * <p> 43 * When manipulating file paths, the static fields of this class may be used to 44 * determine the platform specific separators. 45 * 46 * @see java.io.Serializable 47 * @see java.lang.Comparable 48 */ 49 public class File implements Serializable, Comparable<File> { 50 51 private static final long serialVersionUID = 301077366599181567L; 52 53 private static final String EMPTY_STRING = ""; //$NON-NLS-1$ 54 55 private String path; 56 57 transient byte[] properPath; 58 59 /** 60 * The system dependent file separator character. 61 */ 62 public static final char separatorChar; 63 64 /** 65 * The system dependent file separator string. The initial value of this 66 * field is the system property "file.separator". 67 */ 68 public static final String separator; 69 70 /** 71 * The system dependent path separator character. 72 */ 73 public static final char pathSeparatorChar; 74 75 /** 76 * The system dependent path separator string. The initial value of this 77 * field is the system property "path.separator". 78 */ 79 public static final String pathSeparator; 80 81 /* Temp file counter */ 82 private static int counter; 83 84 private static boolean caseSensitive; 85 oneTimeInitialization()86 private static native void oneTimeInitialization(); 87 88 static { oneTimeInitialization()89 oneTimeInitialization(); 90 91 // The default protection domain grants access to these properties 92 // BEGIN android-changed 93 // We're on linux so the filesystem is case sensitive and the separator is /. 94 separatorChar = System.getProperty("file.separator", "/").charAt(0); //$NON-NLS-1$ //$NON-NLS-2$ 95 pathSeparatorChar = System.getProperty("path.separator", ";").charAt(0); //$NON-NLS-1$//$NON-NLS-2$ 96 separator = new String(new char[] { separatorChar }, 0, 1); 97 pathSeparator = new String(new char[] { pathSeparatorChar }, 0, 1); 98 caseSensitive = true; 99 // END android-changed 100 } 101 102 /** 103 * Constructs a new file using the specified directory and name. 104 * 105 * @param dir 106 * the directory where the file is stored. 107 * @param name 108 * the file's name. 109 * @throws NullPointerException 110 * if {@code name} is {@code null}. 111 */ File(File dir, String name)112 public File(File dir, String name) { 113 if (name == null) { 114 throw new NullPointerException(); 115 } 116 if (dir == null) { 117 this.path = fixSlashes(name); 118 } else { 119 this.path = calculatePath(dir.getPath(), name); 120 } 121 } 122 123 /** 124 * Constructs a new file using the specified path. 125 * 126 * @param path 127 * the path to be used for the file. 128 */ File(String path)129 public File(String path) { 130 // path == null check & NullPointerException thrown by fixSlashes 131 this.path = fixSlashes(path); 132 } 133 134 /** 135 * Constructs a new File using the specified directory path and file name, 136 * placing a path separator between the two. 137 * 138 * @param dirPath 139 * the path to the directory where the file is stored. 140 * @param name 141 * the file's name. 142 * @throws NullPointerException 143 * if {@code name} is {@code null}. 144 */ File(String dirPath, String name)145 public File(String dirPath, String name) { 146 if (name == null) { 147 throw new NullPointerException(); 148 } 149 if (dirPath == null) { 150 this.path = fixSlashes(name); 151 } else { 152 this.path = calculatePath(dirPath, name); 153 } 154 } 155 156 /** 157 * Constructs a new File using the path of the specified URI. {@code uri} 158 * needs to be an absolute and hierarchical Unified Resource Identifier with 159 * file scheme and non-empty path component, but with undefined authority, 160 * query or fragment components. 161 * 162 * @param uri 163 * the Unified Resource Identifier that is used to construct this 164 * file. 165 * @throws IllegalArgumentException 166 * if {@code uri} does not comply with the conditions above. 167 * @see #toURI 168 * @see java.net.URI 169 */ File(URI uri)170 public File(URI uri) { 171 // check pre-conditions 172 checkURI(uri); 173 this.path = fixSlashes(uri.getPath()); 174 } 175 calculatePath(String dirPath, String name)176 private String calculatePath(String dirPath, String name) { 177 dirPath = fixSlashes(dirPath); 178 if (!name.equals(EMPTY_STRING) || dirPath.equals(EMPTY_STRING)) { 179 // Remove all the proceeding separator chars from name 180 name = fixSlashes(name); 181 182 int separatorIndex = 0; 183 while ((separatorIndex < name.length()) 184 && (name.charAt(separatorIndex) == separatorChar)) { 185 separatorIndex++; 186 } 187 if (separatorIndex > 0) { 188 name = name.substring(separatorIndex, name.length()); 189 } 190 191 // Ensure there is a separator char between dirPath and name 192 if (dirPath.length() > 0 193 && (dirPath.charAt(dirPath.length() - 1) == separatorChar)) { 194 return dirPath + name; 195 } 196 return dirPath + separatorChar + name; 197 } 198 199 return dirPath; 200 } 201 202 @SuppressWarnings("nls") checkURI(URI uri)203 private void checkURI(URI uri) { 204 if (!uri.isAbsolute()) { 205 throw new IllegalArgumentException(Msg.getString("K031a", uri)); 206 } else if (!uri.getRawSchemeSpecificPart().startsWith("/")) { 207 throw new IllegalArgumentException(Msg.getString("K031b", uri)); 208 } 209 210 String temp = uri.getScheme(); 211 if (temp == null || !temp.equals("file")) { 212 throw new IllegalArgumentException(Msg.getString("K031c", uri)); 213 } 214 215 temp = uri.getRawPath(); 216 if (temp == null || temp.length() == 0) { 217 throw new IllegalArgumentException(Msg.getString("K031d", uri)); 218 } 219 220 if (uri.getRawAuthority() != null) { 221 throw new IllegalArgumentException(Msg.getString("K031e", 222 new String[] { "authority", uri.toString() })); 223 } 224 225 if (uri.getRawQuery() != null) { 226 throw new IllegalArgumentException(Msg.getString("K031e", 227 new String[] { "query", uri.toString() })); 228 } 229 230 if (uri.getRawFragment() != null) { 231 throw new IllegalArgumentException(Msg.getString("K031e", 232 new String[] { "fragment", uri.toString() })); 233 } 234 } 235 rootsImpl()236 private static native byte[][] rootsImpl(); 237 isCaseSensitiveImpl()238 private static native boolean isCaseSensitiveImpl(); 239 240 /** 241 * Lists the file system roots. The Java platform may support zero or more 242 * file systems, each with its own platform-dependent root. Further, the 243 * canonical pathname of any file on the system will always begin with one 244 * of the returned file system roots. 245 * 246 * @return the array of file system roots. 247 */ listRoots()248 public static File[] listRoots() { 249 byte[][] rootsList = rootsImpl(); 250 if (rootsList == null) { 251 return new File[0]; 252 } 253 File result[] = new File[rootsList.length]; 254 for (int i = 0; i < rootsList.length; i++) { 255 result[i] = new File(Util.toString(rootsList[i])); 256 } 257 return result; 258 } 259 260 /** 261 * The purpose of this method is to take a path and fix the slashes up. This 262 * includes changing them all to the current platforms fileSeparator and 263 * removing duplicates. 264 */ fixSlashes(String origPath)265 private String fixSlashes(String origPath) { 266 int uncIndex = 1; 267 int length = origPath.length(), newLength = 0; 268 if (separatorChar == '/') { 269 uncIndex = 0; 270 } else if (length > 2 && origPath.charAt(1) == ':') { 271 uncIndex = 2; 272 } 273 274 boolean foundSlash = false; 275 char newPath[] = origPath.toCharArray(); 276 for (int i = 0; i < length; i++) { 277 char pathChar = newPath[i]; 278 if ((separatorChar == '\\' && pathChar == '\\') 279 || pathChar == '/') { 280 /* UNC Name requires 2 leading slashes */ 281 if ((foundSlash && i == uncIndex) || !foundSlash) { 282 newPath[newLength++] = separatorChar; 283 foundSlash = true; 284 } 285 } else { 286 // check for leading slashes before a drive 287 if (pathChar == ':' 288 && uncIndex > 0 289 && (newLength == 2 || (newLength == 3 && newPath[1] == separatorChar)) 290 && newPath[0] == separatorChar) { 291 newPath[0] = newPath[newLength - 1]; 292 newLength = 1; 293 // allow trailing slash after drive letter 294 uncIndex = 2; 295 } 296 newPath[newLength++] = pathChar; 297 foundSlash = false; 298 } 299 } 300 // remove trailing slash 301 if (foundSlash 302 && (newLength > (uncIndex + 1) || (newLength == 2 && newPath[0] != separatorChar))) { 303 newLength--; 304 } 305 306 return new String(newPath, 0, newLength); 307 } 308 309 /** 310 * Indicates whether the current context is allowed to read from this file. 311 * 312 * @return {@code true} if this file can be read, {@code false} otherwise. 313 * @throws SecurityException 314 * if a {@code SecurityManager} is installed and it denies the 315 * read request. 316 */ canRead()317 public boolean canRead() { 318 if (path.length() == 0) { 319 return false; 320 } 321 SecurityManager security = System.getSecurityManager(); 322 if (security != null) { 323 security.checkRead(path); 324 } 325 byte[] pp = properPath(true); 326 // BEGIN android-changed 327 return exists() && isReadableImpl(pp); 328 // END android-changed 329 } 330 331 /** 332 * Indicates whether the current context is allowed to write to this file. 333 * 334 * @return {@code true} if this file can be written, {@code false} 335 * otherwise. 336 * @throws SecurityException 337 * if a {@code SecurityManager} is installed and it denies the 338 * write request. 339 */ canWrite()340 public boolean canWrite() { 341 SecurityManager security = System.getSecurityManager(); 342 if (security != null) { 343 security.checkWrite(path); 344 } 345 346 // Cannot use exists() since that does an unwanted read-check. 347 boolean exists = false; 348 if (path.length() > 0) { 349 exists = existsImpl(properPath(true)); 350 } 351 // BEGIN android-changed 352 return exists && isWriteableImpl(properPath(true)); 353 // END android-changed 354 } 355 356 /** 357 * Returns the relative sort ordering of the paths for this file and the 358 * file {@code another}. The ordering is platform dependent. 359 * 360 * @param another 361 * a file to compare this file to 362 * @return an int determined by comparing the two paths. Possible values are 363 * described in the Comparable interface. 364 * @see Comparable 365 */ compareTo(File another)366 public int compareTo(File another) { 367 if (caseSensitive) { 368 return this.getPath().compareTo(another.getPath()); 369 } 370 return this.getPath().compareToIgnoreCase(another.getPath()); 371 } 372 373 /** 374 * Deletes this file. Directories must be empty before they will be deleted. 375 * 376 * @return {@code true} if this file was deleted, {@code false} otherwise. 377 * @throws SecurityException 378 * if a {@code SecurityManager} is installed and it denies the 379 * request. 380 * @see java.lang.SecurityManager#checkDelete 381 */ delete()382 public boolean delete() { 383 SecurityManager security = System.getSecurityManager(); 384 if (security != null) { 385 security.checkDelete(path); 386 } 387 byte[] propPath = properPath(true); 388 if ((path.length() != 0) && isDirectoryImpl(propPath)) { 389 return deleteDirImpl(propPath); 390 } 391 return deleteFileImpl(propPath); 392 } 393 deleteDirImpl(byte[] filePath)394 private native boolean deleteDirImpl(byte[] filePath); 395 deleteFileImpl(byte[] filePath)396 private native boolean deleteFileImpl(byte[] filePath); 397 398 /** 399 * Schedules this file to be automatically deleted once the virtual machine 400 * terminates. This will only happen when the virtual machine terminates 401 * normally as described by the Java Language Specification section 12.9. 402 * 403 * @throws SecurityException 404 * if a {@code SecurityManager} is installed and it denies the 405 * request. 406 */ deleteOnExit()407 public void deleteOnExit() { 408 SecurityManager security = System.getSecurityManager(); 409 if (security != null) { 410 security.checkDelete(path); 411 } 412 // BEGIN android-changed 413 DeleteOnExit.getInstance().addFile(getAbsoluteName()); 414 // END android-changed 415 } 416 417 /** 418 * Compares {@code obj} to this file and returns {@code true} if they 419 * represent the <em>same</em> object using a path specific comparison. 420 * 421 * @param obj 422 * the object to compare this file with. 423 * @return {@code true} if {@code obj} is the same as this object, 424 * {@code false} otherwise. 425 */ 426 @Override equals(Object obj)427 public boolean equals(Object obj) { 428 if (!(obj instanceof File)) { 429 return false; 430 } 431 if (!caseSensitive) { 432 return path.equalsIgnoreCase(((File) obj).getPath()); 433 } 434 return path.equals(((File) obj).getPath()); 435 } 436 437 /** 438 * Returns a boolean indicating whether this file can be found on the 439 * underlying file system. 440 * 441 * @return {@code true} if this file exists, {@code false} otherwise. 442 * @throws SecurityException 443 * if a {@code SecurityManager} is installed and it denies read 444 * access to this file. 445 * @see #getPath 446 * @see java.lang.SecurityManager#checkRead(FileDescriptor) 447 */ exists()448 public boolean exists() { 449 if (path.length() == 0) { 450 return false; 451 } 452 SecurityManager security = System.getSecurityManager(); 453 if (security != null) { 454 security.checkRead(path); 455 } 456 return existsImpl(properPath(true)); 457 } 458 existsImpl(byte[] filePath)459 private native boolean existsImpl(byte[] filePath); 460 461 /** 462 * Returns the absolute path of this file. 463 * 464 * @return the absolute file path. 465 * @see java.lang.SecurityManager#checkPropertyAccess 466 */ getAbsolutePath()467 public String getAbsolutePath() { 468 byte[] absolute = properPath(false); 469 return Util.toUTF8String(absolute); 470 } 471 472 /** 473 * Returns a new file constructed using the absolute path of this file. 474 * 475 * @return a new file from this file's absolute path. 476 * @see java.lang.SecurityManager#checkPropertyAccess 477 */ getAbsoluteFile()478 public File getAbsoluteFile() { 479 return new File(this.getAbsolutePath()); 480 } 481 482 /** 483 * Returns the absolute path of this file with all references resolved. An 484 * <em>absolute</em> path is one that begins at the root of the file 485 * system. The canonical path is one in which all references have been 486 * resolved. For the cases of '..' and '.', where the file system supports 487 * parent and working directory respectively, these are removed and replaced 488 * with a direct directory reference. If the file does not exist, 489 * getCanonicalPath() may not resolve any references and simply returns an 490 * absolute path name or throws an IOException. 491 * 492 * @return the canonical path of this file. 493 * @throws IOException 494 * if an I/O error occurs. 495 * @see java.lang.SecurityManager#checkPropertyAccess 496 */ getCanonicalPath()497 public String getCanonicalPath() throws IOException { 498 byte[] result = properPath(false); 499 String absPath = Util.toUTF8String(result); 500 String canonPath = FileCanonPathCache.get(absPath); 501 502 if (canonPath != null) { 503 return canonPath; 504 } 505 if(separatorChar == '/') { 506 // resolve the full path first 507 result = resolveLink(result, result.length, false); 508 // resolve the parent directories 509 result = resolve(result); 510 } 511 int numSeparators = 1; 512 for (int i = 0; i < result.length; i++) { 513 if (result[i] == separatorChar) { 514 numSeparators++; 515 } 516 } 517 int sepLocations[] = new int[numSeparators]; 518 int rootLoc = 0; 519 if (separatorChar != '/') { 520 if (result[0] == '\\') { 521 rootLoc = (result.length > 1 && result[1] == '\\') ? 1 : 0; 522 } else { 523 rootLoc = 2; // skip drive i.e. c: 524 } 525 } 526 byte newResult[] = new byte[result.length + 1]; 527 int newLength = 0, lastSlash = 0, foundDots = 0; 528 sepLocations[lastSlash] = rootLoc; 529 for (int i = 0; i <= result.length; i++) { 530 if (i < rootLoc) { 531 newResult[newLength++] = result[i]; 532 } else { 533 if (i == result.length || result[i] == separatorChar) { 534 if (i == result.length && foundDots == 0) { 535 break; 536 } 537 if (foundDots == 1) { 538 /* Don't write anything, just reset and continue */ 539 foundDots = 0; 540 continue; 541 } 542 if (foundDots > 1) { 543 /* Go back N levels */ 544 lastSlash = lastSlash > (foundDots - 1) ? lastSlash 545 - (foundDots - 1) : 0; 546 newLength = sepLocations[lastSlash] + 1; 547 foundDots = 0; 548 continue; 549 } 550 sepLocations[++lastSlash] = newLength; 551 newResult[newLength++] = (byte) separatorChar; 552 continue; 553 } 554 if (result[i] == '.') { 555 foundDots++; 556 continue; 557 } 558 /* Found some dots within text, write them out */ 559 if (foundDots > 0) { 560 for (int j = 0; j < foundDots; j++) { 561 newResult[newLength++] = (byte) '.'; 562 } 563 } 564 newResult[newLength++] = result[i]; 565 foundDots = 0; 566 } 567 } 568 // remove trailing slash 569 if (newLength > (rootLoc + 1) 570 && newResult[newLength - 1] == separatorChar) { 571 newLength--; 572 } 573 newResult[newLength] = 0; 574 newResult = getCanonImpl(newResult); 575 newLength = newResult.length; 576 canonPath = Util.toUTF8String(newResult, 0, newLength); 577 FileCanonPathCache.put(absPath, canonPath); 578 return canonPath; 579 } 580 581 /* 582 * Resolve symbolic links in the parent directories. 583 */ resolve(byte[] newResult)584 private byte[] resolve(byte[] newResult) throws IOException { 585 int last = 1, nextSize, linkSize; 586 byte[] linkPath = newResult, bytes; 587 boolean done, inPlace; 588 for (int i = 1; i <= newResult.length; i++) { 589 if (i == newResult.length || newResult[i] == separatorChar) { 590 done = i >= newResult.length - 1; 591 // if there is only one segment, do nothing 592 if (done && linkPath.length == 1) { 593 return newResult; 594 } 595 inPlace = false; 596 if (linkPath == newResult) { 597 bytes = newResult; 598 // if there are no symbolic links, terminate the C string 599 // instead of copying 600 if (!done) { 601 inPlace = true; 602 newResult[i] = '\0'; 603 } 604 } else { 605 nextSize = i - last + 1; 606 linkSize = linkPath.length; 607 if (linkPath[linkSize - 1] == separatorChar) { 608 linkSize--; 609 } 610 bytes = new byte[linkSize + nextSize]; 611 System.arraycopy(linkPath, 0, bytes, 0, linkSize); 612 System.arraycopy(newResult, last - 1, bytes, linkSize, 613 nextSize); 614 // the full path has already been resolved 615 } 616 if (done) { 617 return bytes; 618 } 619 linkPath = resolveLink(bytes, inPlace ? i : bytes.length, true); 620 if (inPlace) { 621 newResult[i] = '/'; 622 } 623 last = i + 1; 624 } 625 } 626 throw new InternalError(); 627 } 628 629 /* 630 * Resolve a symbolic link. While the path resolves to an existing path, 631 * keep resolving. If an absolute link is found, resolve the parent 632 * directories if resolveAbsolute is true. 633 */ resolveLink(byte[] pathBytes, int length, boolean resolveAbsolute)634 private byte[] resolveLink(byte[] pathBytes, int length, 635 boolean resolveAbsolute) throws IOException { 636 boolean restart = false; 637 byte[] linkBytes, temp; 638 do { 639 linkBytes = getLinkImpl(pathBytes); 640 if (linkBytes == pathBytes) { 641 break; 642 } 643 if (linkBytes[0] == separatorChar) { 644 // link to an absolute path, if resolving absolute paths, 645 // resolve the parent dirs again 646 restart = resolveAbsolute; 647 pathBytes = linkBytes; 648 } else { 649 int last = length - 1; 650 while (pathBytes[last] != separatorChar) { 651 last--; 652 } 653 last++; 654 temp = new byte[last + linkBytes.length]; 655 System.arraycopy(pathBytes, 0, temp, 0, last); 656 System.arraycopy(linkBytes, 0, temp, last, linkBytes.length); 657 pathBytes = temp; 658 } 659 length = pathBytes.length; 660 } while (existsImpl(pathBytes)); 661 // resolve the parent directories 662 if (restart) { 663 return resolve(pathBytes); 664 } 665 return pathBytes; 666 } 667 668 /** 669 * Returns a new file created using the canonical path of this file. 670 * Equivalent to {@code new File(this.getCanonicalPath())}. 671 * 672 * @return the new file constructed from this file's canonical path. 673 * @throws IOException 674 * if an I/O error occurs. 675 * @see java.lang.SecurityManager#checkPropertyAccess 676 */ getCanonicalFile()677 public File getCanonicalFile() throws IOException { 678 return new File(getCanonicalPath()); 679 } 680 getCanonImpl(byte[] filePath)681 private native byte[] getCanonImpl(byte[] filePath); 682 683 /** 684 * Returns the name of the file or directory represented by this file. 685 * 686 * @return this file's name or an empty string if there is no name part in 687 * the file's path. 688 */ getName()689 public String getName() { 690 int separatorIndex = path.lastIndexOf(separator); 691 return (separatorIndex < 0) ? path : path.substring(separatorIndex + 1, 692 path.length()); 693 } 694 695 /** 696 * Returns the pathname of the parent of this file. This is the path up to 697 * but not including the last name. {@code null} is returned if there is no 698 * parent. 699 * 700 * @return this file's parent pathname or {@code null}. 701 */ getParent()702 public String getParent() { 703 int length = path.length(), firstInPath = 0; 704 if (separatorChar == '\\' && length > 2 && path.charAt(1) == ':') { 705 firstInPath = 2; 706 } 707 int index = path.lastIndexOf(separatorChar); 708 if (index == -1 && firstInPath > 0) { 709 index = 2; 710 } 711 if (index == -1 || path.charAt(length - 1) == separatorChar) { 712 return null; 713 } 714 if (path.indexOf(separatorChar) == index 715 && path.charAt(firstInPath) == separatorChar) { 716 return path.substring(0, index + 1); 717 } 718 return path.substring(0, index); 719 } 720 721 /** 722 * Returns a new file made from the pathname of the parent of this file. 723 * This is the path up to but not including the last name. {@code null} is 724 * returned when there is no parent. 725 * 726 * @return a new file representing this file's parent or {@code null}. 727 */ getParentFile()728 public File getParentFile() { 729 String tempParent = getParent(); 730 if (tempParent == null) { 731 return null; 732 } 733 return new File(tempParent); 734 } 735 736 /** 737 * Returns the path of this file. 738 * 739 * @return this file's path. 740 */ getPath()741 public String getPath() { 742 return path; 743 } 744 745 /** 746 * Returns an integer hash code for the receiver. Any two objects for which 747 * {@code equals} returns {@code true} must return the same hash code. 748 * 749 * @return this files's hash value. 750 * @see #equals 751 */ 752 @Override hashCode()753 public int hashCode() { 754 if (caseSensitive) { 755 return path.hashCode() ^ 1234321; 756 } 757 return path.toLowerCase().hashCode() ^ 1234321; 758 } 759 760 /** 761 * Indicates if this file's pathname is absolute. Whether a pathname is 762 * absolute is platform specific. On UNIX, absolute paths must start with 763 * the character '/'; on Windows it is absolute if either it starts with 764 * '\', '/', '\\' (to represent a file server), or a letter followed by a 765 * colon. 766 * 767 * @return {@code true} if this file's pathname is absolute, {@code false} 768 * otherwise. 769 * @see #getPath 770 */ isAbsolute()771 public boolean isAbsolute() { 772 // BEGIN android-changed 773 // Removing platform independent code because we're always on linux. 774 return path.length() > 0 && path.charAt(0) == separatorChar; 775 // END android-changed 776 } 777 778 // BEGIN android-removed 779 // private native boolean isAbsoluteImpl(byte[] filePath); 780 // END android-removed 781 782 /** 783 * Indicates if this file represents a <em>directory</em> on the 784 * underlying file system. 785 * 786 * @return {@code true} if this file is a directory, {@code false} 787 * otherwise. 788 * @throws SecurityException 789 * if a {@code SecurityManager} is installed and it denies read 790 * access to this file. 791 */ isDirectory()792 public boolean isDirectory() { 793 if (path.length() == 0) { 794 return false; 795 } 796 SecurityManager security = System.getSecurityManager(); 797 if (security != null) { 798 security.checkRead(path); 799 } 800 return isDirectoryImpl(properPath(true)); 801 } 802 isDirectoryImpl(byte[] filePath)803 private native boolean isDirectoryImpl(byte[] filePath); 804 805 /** 806 * Indicates if this file represents a <em>file</em> on the underlying 807 * file system. 808 * 809 * @return {@code true} if this file is a file, {@code false} otherwise. 810 * @throws SecurityException 811 * if a {@code SecurityManager} is installed and it denies read 812 * access to this file. 813 */ isFile()814 public boolean isFile() { 815 if (path.length() == 0) { 816 return false; 817 } 818 SecurityManager security = System.getSecurityManager(); 819 if (security != null) { 820 security.checkRead(path); 821 } 822 return isFileImpl(properPath(true)); 823 } 824 isFileImpl(byte[] filePath)825 private native boolean isFileImpl(byte[] filePath); 826 827 /** 828 * Returns whether or not this file is a hidden file as defined by the 829 * operating system. The notion of "hidden" is system-dependent. For Unix 830 * systems a file is considered hidden if its name starts with a ".". For 831 * Windows systems there is an explicit flag in the file system for this 832 * purpose. 833 * 834 * @return {@code true} if the file is hidden, {@code false} otherwise. 835 * @throws SecurityException 836 * if a {@code SecurityManager} is installed and it denies read 837 * access to this file. 838 */ isHidden()839 public boolean isHidden() { 840 if (path.length() == 0) { 841 return false; 842 } 843 SecurityManager security = System.getSecurityManager(); 844 if (security != null) { 845 security.checkRead(path); 846 } 847 return isHiddenImpl(properPath(true)); 848 } 849 isHiddenImpl(byte[] filePath)850 private native boolean isHiddenImpl(byte[] filePath); 851 852 // BEGIN android-changed isReadableImpl(byte[] filePath)853 private native boolean isReadableImpl(byte[] filePath); 854 isWriteableImpl(byte[] filePath)855 private native boolean isWriteableImpl(byte[] filePath); 856 // END android-changed 857 getLinkImpl(byte[] filePath)858 private native byte[] getLinkImpl(byte[] filePath); 859 860 /** 861 * Returns the time when this file was last modified, measured in 862 * milliseconds since January 1st, 1970, midnight. 863 * 864 * @return the time when this file was last modified. 865 * @throws SecurityException 866 * if a {@code SecurityManager} is installed and it denies read 867 * access to this file. 868 */ lastModified()869 public long lastModified() { 870 SecurityManager security = System.getSecurityManager(); 871 if (security != null) { 872 security.checkRead(path); 873 } 874 long result = lastModifiedImpl(properPath(true)); 875 /* Temporary code to handle both return cases until natives fixed */ 876 if (result == -1 || result == 0) { 877 return 0; 878 } 879 return result; 880 } 881 lastModifiedImpl(byte[] filePath)882 private native long lastModifiedImpl(byte[] filePath); 883 884 /** 885 * Sets the time this file was last modified, measured in milliseconds since 886 * January 1st, 1970, midnight. 887 * 888 * @param time 889 * the last modification time for this file. 890 * @return {@code true} if the operation is successful, {@code false} 891 * otherwise. 892 * @throws IllegalArgumentException 893 * if {@code time < 0}. 894 * @throws SecurityException 895 * if a {@code SecurityManager} is installed and it denies write 896 * access to this file. 897 */ setLastModified(long time)898 public boolean setLastModified(long time) { 899 if (time < 0) { 900 throw new IllegalArgumentException(Msg.getString("K006a")); //$NON-NLS-1$ 901 } 902 SecurityManager security = System.getSecurityManager(); 903 if (security != null) { 904 security.checkWrite(path); 905 } 906 return (setLastModifiedImpl(properPath(true), time)); 907 } 908 setLastModifiedImpl(byte[] path, long time)909 private native boolean setLastModifiedImpl(byte[] path, long time); 910 911 /** 912 * Marks this file or directory to be read-only as defined by the operating 913 * system. 914 * 915 * @return {@code true} if the operation is successful, {@code false} 916 * otherwise. 917 * @throws SecurityException 918 * if a {@code SecurityManager} is installed and it denies write 919 * access to this file. 920 */ setReadOnly()921 public boolean setReadOnly() { 922 SecurityManager security = System.getSecurityManager(); 923 if (security != null) { 924 security.checkWrite(path); 925 } 926 return (setReadOnlyImpl(properPath(true))); 927 } 928 setReadOnlyImpl(byte[] path)929 private native boolean setReadOnlyImpl(byte[] path); 930 931 /** 932 * Returns the length of this file in bytes. 933 * 934 * @return the number of bytes in this file. 935 * @throws SecurityException 936 * if a {@code SecurityManager} is installed and it denies read 937 * access to this file. 938 */ length()939 public long length() { 940 SecurityManager security = System.getSecurityManager(); 941 if (security != null) { 942 security.checkRead(path); 943 } 944 return lengthImpl(properPath(true)); 945 } 946 lengthImpl(byte[] filePath)947 private native long lengthImpl(byte[] filePath); 948 949 /** 950 * Returns an array of strings with the file names in the directory 951 * represented by this file. The result is {@code null} if this file is not 952 * a directory. 953 * <p> 954 * The entries {@code .} and {@code ..} representing the current and parent 955 * directory are not returned as part of the list. 956 * 957 * @return an array of strings with file names or {@code null}. 958 * @throws SecurityException 959 * if a {@code SecurityManager} is installed and it denies read 960 * access to this file. 961 * @see #isDirectory 962 * @see java.lang.SecurityManager#checkRead(FileDescriptor) 963 */ list()964 public java.lang.String[] list() { 965 SecurityManager security = System.getSecurityManager(); 966 if (security != null) { 967 security.checkRead(path); 968 } 969 970 if (path.length() == 0) { 971 return null; 972 } 973 974 byte[] bs = properPath(true); 975 if (!isDirectoryImpl(bs) || !existsImpl(bs) || !isReadableImpl(bs)) { 976 return null; 977 } 978 979 byte[][] implList = listImpl(bs); 980 if (implList == null) { 981 // empty list 982 return new String[0]; 983 } 984 String result[] = new String[implList.length]; 985 for (int index = 0; index < implList.length; index++) { 986 result[index] = Util.toUTF8String(implList[index]); 987 } 988 return result; 989 } 990 991 /** 992 * Returns an array of files contained in the directory represented by this 993 * file. The result is {@code null} if this file is not a directory. The 994 * paths of the files in the array are absolute if the path of this file is 995 * absolute, they are relative otherwise. 996 * 997 * @return an array of files or {@code null}. 998 * @throws SecurityException 999 * if a {@code SecurityManager} is installed and it denies read 1000 * access to this file. 1001 * @see #list 1002 * @see #isDirectory 1003 */ listFiles()1004 public File[] listFiles() { 1005 String[] tempNames = list(); 1006 if (tempNames == null) { 1007 return null; 1008 } 1009 int resultLength = tempNames.length; 1010 File results[] = new File[resultLength]; 1011 for (int i = 0; i < resultLength; i++) { 1012 results[i] = new File(this, tempNames[i]); 1013 } 1014 return results; 1015 } 1016 1017 /** 1018 * Gets a list of the files in the directory represented by this file. This 1019 * list is then filtered through a FilenameFilter and files with matching 1020 * names are returned as an array of files. Returns {@code null} if this 1021 * file is not a directory. If {@code filter} is {@code null} then all 1022 * filenames match. 1023 * <p> 1024 * The entries {@code .} and {@code ..} representing the current and parent 1025 * directories are not returned as part of the list. 1026 * 1027 * @param filter 1028 * the filter to match names against, may be {@code null}. 1029 * @return an array of files or {@code null}. 1030 * @throws SecurityException 1031 * if a {@code SecurityManager} is installed and it denies read 1032 * access to this file. 1033 * @see #list(FilenameFilter filter) 1034 * @see #getPath 1035 * @see #isDirectory 1036 * @see java.lang.SecurityManager#checkRead(FileDescriptor) 1037 */ listFiles(FilenameFilter filter)1038 public File[] listFiles(FilenameFilter filter) { 1039 String[] tempNames = list(filter); 1040 if (tempNames == null) { 1041 return null; 1042 } 1043 int resultLength = tempNames.length; 1044 File results[] = new File[resultLength]; 1045 for (int i = 0; i < resultLength; i++) { 1046 results[i] = new File(this, tempNames[i]); 1047 } 1048 return results; 1049 } 1050 1051 /** 1052 * Gets a list of the files in the directory represented by this file. This 1053 * list is then filtered through a FileFilter and matching files are 1054 * returned as an array of files. Returns {@code null} if this file is not a 1055 * directory. If {@code filter} is {@code null} then all files match. 1056 * <p> 1057 * The entries {@code .} and {@code ..} representing the current and parent 1058 * directories are not returned as part of the list. 1059 * 1060 * @param filter 1061 * the filter to match names against, may be {@code null}. 1062 * @return an array of files or {@code null}. 1063 * @throws SecurityException 1064 * if a {@code SecurityManager} is installed and it denies read 1065 * access to this file. 1066 * @see #getPath 1067 * @see #isDirectory 1068 * @see java.lang.SecurityManager#checkRead(FileDescriptor) 1069 */ listFiles(FileFilter filter)1070 public File[] listFiles(FileFilter filter) { 1071 SecurityManager security = System.getSecurityManager(); 1072 if (security != null) { 1073 security.checkRead(path); 1074 } 1075 1076 if (path.length() == 0) { 1077 return null; 1078 } 1079 1080 byte[] bs = properPath(true); 1081 if (!isDirectoryImpl(bs) || !existsImpl(bs) || !isReadableImpl(bs)) { 1082 return null; 1083 } 1084 1085 byte[][] implList = listImpl(bs); 1086 if (implList == null) { 1087 return new File[0]; 1088 } 1089 List<File> tempResult = new ArrayList<File>(); 1090 for (int index = 0; index < implList.length; index++) { 1091 String aName = Util.toString(implList[index]); 1092 File aFile = new File(this, aName); 1093 if (filter == null || filter.accept(aFile)) { 1094 tempResult.add(aFile); 1095 } 1096 } 1097 return tempResult.toArray(new File[tempResult.size()]); 1098 } 1099 1100 /** 1101 * Gets a list of the files in the directory represented by this file. This 1102 * list is then filtered through a FilenameFilter and the names of files 1103 * with matching names are returned as an array of strings. Returns 1104 * {@code null} if this file is not a directory. If {@code filter} is 1105 * {@code null} then all filenames match. 1106 * <p> 1107 * The entries {@code .} and {@code ..} representing the current and parent 1108 * directories are not returned as part of the list. 1109 * 1110 * @param filter 1111 * the filter to match names against, may be {@code null}. 1112 * @return an array of files or {@code null}. 1113 * @throws SecurityException 1114 * if a {@code SecurityManager} is installed and it denies read 1115 * access to this file. 1116 * @see #getPath 1117 * @see #isDirectory 1118 * @see java.lang.SecurityManager#checkRead(FileDescriptor) 1119 */ list(FilenameFilter filter)1120 public java.lang.String[] list(FilenameFilter filter) { 1121 SecurityManager security = System.getSecurityManager(); 1122 if (security != null) { 1123 security.checkRead(path); 1124 } 1125 1126 if (path.length() == 0) { 1127 return null; 1128 } 1129 1130 byte[] bs = properPath(true); 1131 if (!isDirectoryImpl(bs) || !existsImpl(bs) || !isReadableImpl(bs)) { 1132 return null; 1133 } 1134 1135 byte[][] implList = listImpl(bs); 1136 if (implList == null) { 1137 // empty list 1138 return new String[0]; 1139 } 1140 List<String> tempResult = new ArrayList<String>(); 1141 for (int index = 0; index < implList.length; index++) { 1142 String aName = Util.toString(implList[index]); 1143 if (filter == null || filter.accept(this, aName)) { 1144 tempResult.add(aName); 1145 } 1146 } 1147 1148 return tempResult.toArray(new String[tempResult.size()]); 1149 } 1150 listImpl(byte[] path)1151 private synchronized static native byte[][] listImpl(byte[] path); 1152 1153 /** 1154 * Creates the directory named by the trailing filename of this file. Does 1155 * not create the complete path required to create this directory. 1156 * 1157 * @return {@code true} if the directory has been created, {@code false} 1158 * otherwise. 1159 * @throws SecurityException 1160 * if a {@code SecurityManager} is installed and it denies write 1161 * access for this file. 1162 * @see #mkdirs 1163 */ mkdir()1164 public boolean mkdir() { 1165 SecurityManager security = System.getSecurityManager(); 1166 if (security != null) { 1167 security.checkWrite(path); 1168 } 1169 return mkdirImpl(properPath(true)); 1170 } 1171 mkdirImpl(byte[] filePath)1172 private native boolean mkdirImpl(byte[] filePath); 1173 1174 /** 1175 * Creates the directory named by the trailing filename of this file, 1176 * including the complete directory path required to create this directory. 1177 * 1178 * @return {@code true} if the necessary directories have been created, 1179 * {@code false} if the target directory already exists or one of 1180 * the directories can not be created. 1181 * @throws SecurityException 1182 * if a {@code SecurityManager} is installed and it denies write 1183 * access for this file. 1184 * @see #mkdir 1185 */ mkdirs()1186 public boolean mkdirs() { 1187 /* If the terminal directory already exists, answer false */ 1188 if (exists()) { 1189 return false; 1190 } 1191 1192 /* If the receiver can be created, answer true */ 1193 if (mkdir()) { 1194 return true; 1195 } 1196 1197 String parentDir = getParent(); 1198 /* If there is no parent and we were not created, answer false */ 1199 if (parentDir == null) { 1200 return false; 1201 } 1202 1203 /* Otherwise, try to create a parent directory and then this directory */ 1204 return (new File(parentDir).mkdirs() && mkdir()); 1205 } 1206 1207 /** 1208 * Creates a new, empty file on the file system according to the path 1209 * information stored in this file. 1210 * 1211 * @return {@code true} if the file has been created, {@code false} if it 1212 * already exists. 1213 * @throws IOException 1214 * if an I/O error occurs or the directory does not exist where 1215 * the file should have been created. 1216 * @throws SecurityException 1217 * if a {@code SecurityManager} is installed and it denies write 1218 * access for this file. 1219 */ createNewFile()1220 public boolean createNewFile() throws IOException { 1221 SecurityManager security = System.getSecurityManager(); 1222 if (security != null) { 1223 security.checkWrite(path); 1224 } 1225 if (0 == path.length()) { 1226 throw new IOException(Msg.getString("KA012")); //$NON-NLS-1$ 1227 } 1228 int result = newFileImpl(properPath(true)); 1229 switch (result) { 1230 case 0: 1231 return true; 1232 case 1: 1233 return false; 1234 // BEGIN android-changed 1235 default: { 1236 // Try to provide a reasonable explanation. 1237 String msg = null; 1238 try { 1239 File parent = getAbsoluteFile().getParentFile(); 1240 if (parent == null) { 1241 /* 1242 * This shouldn't happen, unless the caller 1243 * tried to create "/". We just use the 1244 * generic message for this case. 1245 */ 1246 } else if (! parent.exists()) { 1247 msg = "Parent directory of file does not exist"; 1248 } else if (! parent.isDirectory()) { 1249 msg = "Parent of file is not a directory"; 1250 } else if (! parent.canWrite()) { 1251 msg = "Parent directory of file is not writable"; 1252 } 1253 } catch (RuntimeException ex) { 1254 /* 1255 * Ignore the exception, and just fall through to 1256 * use a generic message. 1257 */ 1258 } 1259 1260 if (msg == null) { 1261 msg = "Cannot create"; 1262 } 1263 throw new IOException(msg + ": " + path); //$NON-NLS-1$ 1264 } 1265 // END android-changed 1266 } 1267 } 1268 newFileImpl(byte[] filePath)1269 private native int newFileImpl(byte[] filePath); 1270 1271 /** 1272 * Creates an empty temporary file using the given prefix and suffix as part 1273 * of the file name. If suffix is {@code null}, {@code .tmp} is used. This 1274 * method is a convenience method that calls 1275 * {@link #createTempFile(String, String, File)} with the third argument 1276 * being {@code null}. 1277 * 1278 * @param prefix 1279 * the prefix to the temp file name. 1280 * @param suffix 1281 * the suffix to the temp file name. 1282 * @return the temporary file. 1283 * @throws IOException 1284 * if an error occurs when writing the file. 1285 */ createTempFile(String prefix, String suffix)1286 public static File createTempFile(String prefix, String suffix) 1287 throws IOException { 1288 return createTempFile(prefix, suffix, null); 1289 } 1290 1291 /** 1292 * Creates an empty temporary file in the given directory using the given 1293 * prefix and suffix as part of the file name. 1294 * 1295 * @param prefix 1296 * the prefix to the temp file name. 1297 * @param suffix 1298 * the suffix to the temp file name. 1299 * @param directory 1300 * the location to which the temp file is to be written, or 1301 * {@code null} for the default location for temporary files, 1302 * which is taken from the "java.io.tmpdir" system property. It 1303 * may be necessary to set this property to an existing, writable 1304 * directory for this method to work properly. 1305 * @return the temporary file. 1306 * @throws IllegalArgumentException 1307 * if the length of {@code prefix} is less than 3. 1308 * @throws IOException 1309 * if an error occurs when writing the file. 1310 */ 1311 @SuppressWarnings("nls") createTempFile(String prefix, String suffix, File directory)1312 public static File createTempFile(String prefix, String suffix, 1313 File directory) throws IOException { 1314 // Force a prefix null check first 1315 if (prefix.length() < 3) { 1316 throw new IllegalArgumentException(Msg.getString("K006b")); 1317 } 1318 String newSuffix = suffix == null ? ".tmp" : suffix; 1319 File tmpDirFile; 1320 if (directory == null) { 1321 String tmpDir = AccessController.doPrivileged( 1322 new PriviAction<String>("java.io.tmpdir", ".")); 1323 tmpDirFile = new File(tmpDir); 1324 } else { 1325 tmpDirFile = directory; 1326 } 1327 File result; 1328 do { 1329 result = genTempFile(prefix, newSuffix, tmpDirFile); 1330 } while (!result.createNewFile()); 1331 return result; 1332 } 1333 genTempFile(String prefix, String suffix, File directory)1334 private static File genTempFile(String prefix, String suffix, File directory) { 1335 if (counter == 0) { 1336 int newInt = new SecureRandom().nextInt(); 1337 counter = ((newInt / 65535) & 0xFFFF) + 0x2710; 1338 } 1339 StringBuilder newName = new StringBuilder(); 1340 newName.append(prefix); 1341 newName.append(counter++); 1342 newName.append(suffix); 1343 return new File(directory, newName.toString()); 1344 } 1345 1346 // BEGIN android-changed 1347 // Removing platform independent code because we're always on linux. 1348 /** 1349 * Returns a string representing the proper path for this file. If this file 1350 * path is absolute, the user.dir property is not prepended, otherwise it 1351 * is. 1352 * 1353 * @param internal 1354 * is user.dir internal. 1355 * @return the proper path. 1356 */ properPath(boolean internal)1357 byte[] properPath(boolean internal) { 1358 if (properPath != null) { 1359 return properPath; 1360 } 1361 if(path.length() > 0 && path.charAt(0) == separatorChar) { 1362 return properPath = Util.getBytes(path); 1363 } 1364 // Check security by getting user.dir when the path is not absolute 1365 String userdir; 1366 if (internal) { 1367 userdir = AccessController.doPrivileged(new PriviAction<String>( 1368 "user.dir")); //$NON-NLS-1$ 1369 } else { 1370 userdir = System.getProperty("user.dir"); //$NON-NLS-1$ 1371 } 1372 if (path.length() == 0) { 1373 return properPath = Util.getUTF8Bytes(userdir); 1374 } 1375 int length = userdir.length(); 1376 if (length > 0 && userdir.charAt(length - 1) == separatorChar) { 1377 return properPath = Util.getBytes(userdir + path); 1378 } 1379 return properPath = Util.getBytes(userdir + separator + path); 1380 } 1381 // END android-changed 1382 1383 // BEGIN android-removed 1384 // private static native byte[] properPathImpl(byte[] path); 1385 // END android-removed 1386 1387 /** 1388 * Renames this file to the name represented by the {@code dest} file. This 1389 * works for both normal files and directories. 1390 * 1391 * @param dest 1392 * the file containing the new name. 1393 * @return {@code true} if the File was renamed, {@code false} otherwise. 1394 * @throws SecurityException 1395 * if a {@code SecurityManager} is installed and it denies write 1396 * access for this file or the {@code dest} file. 1397 */ renameTo(java.io.File dest)1398 public boolean renameTo(java.io.File dest) { 1399 SecurityManager security = System.getSecurityManager(); 1400 if (security != null) { 1401 security.checkWrite(path); 1402 security.checkWrite(dest.path); 1403 } 1404 return renameToImpl(properPath(true), dest.properPath(true)); 1405 } 1406 renameToImpl(byte[] pathExist, byte[] pathNew)1407 private native boolean renameToImpl(byte[] pathExist, byte[] pathNew); 1408 1409 /** 1410 * Returns a string containing a concise, human-readable description of this 1411 * file. 1412 * 1413 * @return a printable representation of this file. 1414 */ 1415 @Override toString()1416 public String toString() { 1417 return path; 1418 } 1419 1420 /** 1421 * Returns a Uniform Resource Identifier for this file. The URI is system 1422 * dependent and may not be transferable between different operating / file 1423 * systems. 1424 * 1425 * @return an URI for this file. 1426 */ 1427 @SuppressWarnings("nls") toURI()1428 public URI toURI() { 1429 String name = getAbsoluteName(); 1430 try { 1431 if (!name.startsWith("/")) { 1432 // start with sep. 1433 return new URI("file", null, new StringBuilder( 1434 name.length() + 1).append('/').append(name).toString(), 1435 null, null); 1436 } else if (name.startsWith("//")) { 1437 return new URI("file", "", name, null); // UNC path 1438 } 1439 return new URI("file", null, name, null, null); 1440 } catch (URISyntaxException e) { 1441 // this should never happen 1442 return null; 1443 } 1444 } 1445 1446 /** 1447 * Returns a Uniform Resource Locator for this file. The URL is system 1448 * dependent and may not be transferable between different operating / file 1449 * systems. 1450 * 1451 * @return a URL for this file. 1452 * @throws java.net.MalformedURLException 1453 * if the path cannot be transformed into a URL. 1454 */ 1455 @SuppressWarnings("nls") toURL()1456 public URL toURL() throws java.net.MalformedURLException { 1457 String name = getAbsoluteName(); 1458 if (!name.startsWith("/")) { 1459 // start with sep. 1460 return new URL( 1461 "file", EMPTY_STRING, -1, new StringBuilder(name.length() + 1) //$NON-NLS-1$ 1462 .append('/').append(name).toString(), null); 1463 } else if (name.startsWith("//")) { 1464 return new URL("file:" + name); // UNC path 1465 } 1466 return new URL("file", EMPTY_STRING, -1, name, null); 1467 } 1468 getAbsoluteName()1469 private String getAbsoluteName() { 1470 File f = getAbsoluteFile(); 1471 String name = f.getPath(); 1472 1473 if (f.isDirectory() && name.charAt(name.length() - 1) != separatorChar) { 1474 // Directories must end with a slash 1475 name = new StringBuilder(name.length() + 1).append(name) 1476 .append('/').toString(); 1477 } 1478 if (separatorChar != '/') { // Must convert slashes. 1479 name = name.replace(separatorChar, '/'); 1480 } 1481 return name; 1482 } 1483 writeObject(ObjectOutputStream stream)1484 private void writeObject(ObjectOutputStream stream) throws IOException { 1485 stream.defaultWriteObject(); 1486 stream.writeChar(separatorChar); 1487 1488 } 1489 readObject(ObjectInputStream stream)1490 private void readObject(ObjectInputStream stream) throws IOException, 1491 ClassNotFoundException { 1492 stream.defaultReadObject(); 1493 char inSeparator = stream.readChar(); 1494 path = path.replace(inSeparator, separatorChar); 1495 } 1496 } 1497