1 6 21 22 package de.schlichtherle.io; 23 24 import de.schlichtherle.io.archive.spi.ArchiveEntry; 25 import de.schlichtherle.io.archive.spi.InputArchive; 26 27 import java.io.CharConversionException ; 28 import java.io.FileFilter ; 29 import java.io.FilenameFilter ; 30 import java.io.IOException ; 31 import java.util.Collections ; 32 import java.util.Comparator ; 33 import java.util.Enumeration ; 34 import java.util.HashMap ; 35 import java.util.LinkedList ; 36 import java.util.Map ; 37 import java.util.TreeMap ; 38 import javax.swing.Icon ; 39 40 52 final class ArchiveFileSystem implements FileConstants { 53 54 58 static final String ROOT = ENTRY_SEPARATOR; 59 60 61 private static final Comparator REVERSE_ENTRIES_COMPARATOR 62 = new Comparator () { 63 public int compare(Object s1, Object s2) { 64 return ((String ) s2).compareTo((String ) s1); } 69 }; 70 71 87 static String [] split(final String entryName, final String [] result) { 89 assert entryName != null; 90 assert result != null; 91 assert result.length >= 2; 92 93 int end = entryName.length(); 95 if (--end >= 0) 96 if (entryName.charAt(end) == ENTRY_SEPARATOR_CHAR) 97 end--; 98 99 int i = entryName.lastIndexOf(ENTRY_SEPARATOR_CHAR, end); 101 end++; 102 103 if (i != -1) { i++; 106 result[0] = entryName.substring(0, i); result[1] = entryName.substring(i, end); } else { if (end > 0) { result[0] = ROOT; 111 } else { 112 result[0] = null; } 114 result[1] = entryName.substring(0, end); } 116 117 return result; 118 } 119 120 123 private final GeneralArchiveController controller; 124 125 128 private final boolean readOnly; 130 138 private Map master; 139 140 private final ArchiveEntry root; 141 142 private long modCount; 143 144 148 private final String [] split = new String [2]; 149 150 161 ArchiveFileSystem(final GeneralArchiveController controller) 162 throws IOException { 163 this.controller = controller; 164 modCount = 1; 165 master = new CompoundMap(REVERSE_ENTRIES_COMPARATOR, 64); 166 root = createArchiveEntry(ROOT, null); 167 root.setTime(System.currentTimeMillis()); 168 master.put(ROOT, root); 169 readOnly = false; 170 controller.touch(); 171 } 172 173 198 ArchiveFileSystem( 199 final GeneralArchiveController controller, 200 final InputArchive archive, 201 final long rootTime, 202 final boolean readOnly) { 203 this.controller = controller; 204 205 final int iniCap = (int) (archive.getNumArchiveEntries() / 0.75f); 206 master = new CompoundMap(REVERSE_ENTRIES_COMPARATOR, iniCap); 207 208 try { 209 root = createArchiveEntry(ROOT, null); 211 root.setTime(rootTime); 213 master.put(ROOT, root); 214 215 Enumeration entries = archive.getArchiveEntries(); 216 while (entries.hasMoreElements()) { 217 final ArchiveEntry entry = (ArchiveEntry) entries.nextElement(); 218 final String name = entry.getName(); 219 if (!ROOT.equals(name) && !("." + ENTRY_SEPARATOR).equals(name)) { 221 entry.setMetaData(new ArchiveEntryMetaData(entry)); 222 master.put(name, entry); 223 } 224 } 225 226 entries = archive.getArchiveEntries(); 229 while (entries.hasMoreElements()) { 230 final ArchiveEntry entry = (ArchiveEntry) entries.nextElement(); 231 if (isLegalEntryName(entry.getName())) 232 fixParents(entry); 233 } 234 } catch (CharConversionException cannotHappen) { 235 throw new AssertionError (cannotHappen); 237 } 238 239 this.readOnly = readOnly; 241 if (readOnly) 242 master = Collections.unmodifiableMap(master); 243 244 assert modCount == 0; } 246 247 253 private static boolean isLegalEntryName(final String entryName) { 254 final int l = entryName.length(); 255 256 if (l <= 0) 257 return false; 259 switch (entryName.charAt(0)) { 260 case ENTRY_SEPARATOR_CHAR: 261 return false; 263 case '.': 264 if (l >= 2) { 265 switch (entryName.charAt(1)) { 266 case '.': 267 if (l >= 3) { 268 if (entryName.charAt(2) == ENTRY_SEPARATOR_CHAR) { 269 assert entryName.startsWith(".." + ENTRY_SEPARATOR); 270 return false; 271 } 272 } else { 274 assert "..".equals(entryName); 275 return false; 276 } 277 break; 278 279 case ENTRY_SEPARATOR_CHAR: 280 assert entryName.startsWith("." + ENTRY_SEPARATOR); 281 return false; 282 283 default: 284 } 286 } else { 287 assert ".".equals(entryName); 288 return false; 289 } 290 break; 291 292 default: 293 } 295 296 return true; 297 } 298 299 309 private void fixParents(final ArchiveEntry entry) 310 throws CharConversionException { 311 final String name = entry.getName(); 312 if (name.length() <= 0 || name.charAt(0) == ENTRY_SEPARATOR_CHAR) 315 return; assert isLegalEntryName(name); 317 318 final String split[] = split(name, this.split); 319 final String parentName = split[0]; 320 final String baseName = split[1]; 321 322 ArchiveEntry parent = (ArchiveEntry) master.get(parentName); 323 if (parent == null) { 324 parent = createArchiveEntry(parentName, null); 325 master.put(parentName, parent); 327 } 328 329 fixParents(parent); 330 parent.getMetaData().children.add(baseName); 331 } 332 333 337 boolean isReadOnly() { 338 return readOnly; 339 } 340 341 351 private void touch() throws IOException { 352 if (isReadOnly()) 353 throw new ArchiveReadOnlyException(); 354 355 if (modCount == 0) 357 controller.touch(); 358 modCount++; 359 } 360 361 365 boolean isTouched() { 366 assert controller.getFileSystem() == this; 367 return modCount != 0; 368 } 369 370 374 private void resetTouched() { 375 modCount = 0; 376 } 377 378 390 Enumeration getReversedEntries() { 391 assert controller.getFileSystem() == this; 392 return Collections.enumeration(master.values()); 394 } 395 396 400 private ArchiveEntry getRoot() { 401 return root; 402 } 403 404 408 ArchiveEntry get(String entryName) { 409 assert controller.getFileSystem() == this; 410 return (ArchiveEntry) master.get(entryName); 411 } 412 413 417 Delta beginCreateAndLink( 418 final String entryName, 419 final boolean createParents) 420 throws CharConversionException , ArchiveIllegalOperationException { 421 assert controller.getFileSystem() == this; 422 return new CreateAndLinkDelta(entryName, createParents, null); 423 } 424 425 479 Delta beginCreateAndLink( 480 final String entryName, 481 final boolean createParents, 482 final ArchiveEntry blueprint) 483 throws CharConversionException , ArchiveIllegalOperationException { 484 assert controller.getFileSystem() == this; 485 return new CreateAndLinkDelta(entryName, createParents, blueprint); 486 } 487 488 494 private class CreateAndLinkDelta extends AbstractDelta { 495 private final long time = System.currentTimeMillis(); 496 private final Element[] elements; 497 498 private CreateAndLinkDelta( 499 final String entryName, 500 final boolean createParents, 501 final ArchiveEntry blueprint) 502 throws ArchiveIllegalOperationException, CharConversionException { 503 assert entryName.length() > 0; 504 assert entryName.charAt(0) != ENTRY_SEPARATOR_CHAR; 505 506 if (isReadOnly()) 507 throw new ArchiveReadOnlyException(); 508 elements = createElements(entryName, createParents, blueprint, 1); 509 } 510 511 private Element[] createElements( 512 final String entryName, 513 final boolean createParents, 514 final ArchiveEntry blueprint, 515 final int level) 516 throws ArchiveIllegalOperationException, CharConversionException { 517 final String split[] 519 = split(entryName, ArchiveFileSystem.this.split); 520 final String parentName = split[0]; final String baseName = split[1]; 522 523 final Element[] elements; 524 525 final ArchiveEntry parent = (ArchiveEntry) master.get(parentName); 527 final ArchiveEntry entry; 528 if (parent != null) { 529 final ArchiveEntry oldEntry 530 = (ArchiveEntry) master.get(entryName); 531 ensureMayBeReplaced(entryName, oldEntry); 532 elements = new Element[level + 1]; 533 elements[0] = new Element(File.EMPTY, parent); 534 entry = createArchiveEntry(entryName, level == 1 ? blueprint : null); 535 elements[1] = new Element(baseName, entry); 536 } else if (createParents) { 537 elements = createElements( 538 parentName, createParents, blueprint, level + 1); 539 entry = createArchiveEntry(entryName, level == 1 ? blueprint : null); 540 elements[elements.length - level] 541 = new Element(baseName, entry); 542 } else { 543 throw new ArchiveIllegalOperationException( 544 entryName, "Missing parent directory!"); 545 } 546 if (blueprint != null && level == 1) { 547 entry.setTime(blueprint.getTime()); 551 } else { 552 entry.setTime(time); 553 } 554 555 return elements; 556 } 557 558 private void ensureMayBeReplaced( 559 final String entryName, 560 final ArchiveEntry oldEntry) 561 throws ArchiveIllegalOperationException { 562 final int end = entryName.length() - 1; 563 if (entryName.charAt(end) == ENTRY_SEPARATOR_CHAR) { if (oldEntry != null) 565 throw new ArchiveIllegalOperationException(entryName, 566 "Directories cannot be replaced!"); 567 if (master.get(entryName.substring(0, end)) != null) 568 throw new ArchiveIllegalOperationException(entryName, 569 "Directories cannot replace files!"); 570 } else { if (master.get(entryName + ENTRY_SEPARATOR) != null) 572 throw new ArchiveIllegalOperationException(entryName, 573 "Files cannot replace directories!"); 574 } 575 } 576 577 578 public void commit() throws IOException { 579 assert controller.getFileSystem() == ArchiveFileSystem.this; 580 touch(); 581 ArchiveEntry parent = elements[0].entry; 582 for (int i = 1, l = elements.length; i < l ; i++) { 583 final Element element = elements[i]; 584 final String baseName = element.baseName; 585 final ArchiveEntry entry = element.entry; 586 if (parent.getMetaData().children.add(baseName) 587 && parent.getTime() >= 0) { 588 parent.setTime(System.currentTimeMillis()); } 590 master.put(entry.getName(), entry); 591 parent = entry; 592 } 593 } 594 595 public ArchiveEntry getEntry() { 596 assert controller.getFileSystem() == ArchiveFileSystem.this; 597 return elements[elements.length - 1].entry; 598 } 599 } 601 private static abstract class AbstractDelta implements Delta { 602 603 protected static class Element { 604 protected final String baseName; 605 protected final ArchiveEntry entry; 606 607 protected Element(String baseName, ArchiveEntry entry) { 609 this.baseName = baseName; assert entry != null; 611 this.entry = entry; 612 } 613 } 614 } 616 626 interface Delta { 627 630 ArchiveEntry getEntry(); 631 632 639 void commit() throws IOException ; 640 } 641 642 663 private ArchiveEntry createArchiveEntry(String name, ArchiveEntry blueprint) 664 throws CharConversionException { 665 ArchiveEntry entry = controller.createArchiveEntry(name, blueprint); 666 entry.setMetaData(new ArchiveEntryMetaData(entry)); 667 return entry; 668 } 669 670 684 private void unlink(final String entryName) 685 throws IOException { 686 assert entryName.length() > 0; 687 assert entryName.charAt(0) != ENTRY_SEPARATOR_CHAR; 688 689 try { 690 final ArchiveEntry entry = (ArchiveEntry) master.remove(entryName); 691 if (entry == null) 692 throw new ArchiveIllegalOperationException(entryName, 693 "Entry does not exist!"); 694 if (entry == root 695 || entry.isDirectory() && entry.getMetaData().children.size() != 0) { 696 master.put(entryName, entry); throw new ArchiveIllegalOperationException(entryName, 698 "Directory is not empty!"); 699 } 700 final String split[] = split(entryName, this.split); 701 final String parentName = split[0]; 702 final ArchiveEntry parent = (ArchiveEntry) master.get(parentName); 703 assert parent != null : "The parent directory of \"" + entryName 704 + "\" is missing - archive file system is corrupted!"; 705 final boolean ok = parent.getMetaData().children.remove(split[1]); 706 assert ok : "The parent directory of \"" + entryName 707 + "\" does not contain this entry - archive file system is corrupted!"; 708 touch(); 709 parent.setTime(System.currentTimeMillis()); 710 } 711 catch (UnsupportedOperationException unmodifiableMap) { 712 throw new ArchiveReadOnlyException(); 713 } 714 } 715 716 720 732 private static class ArchiveIllegalOperationException extends IOException { 733 734 private final String entryName; 735 736 private ArchiveIllegalOperationException(String message) { 737 super(message); 738 this.entryName = null; 739 } 740 741 private ArchiveIllegalOperationException(String entryName, String message) { 742 super(message); 743 this.entryName = entryName; 744 } 745 746 public String getMessage() { 747 if (entryName != null) 750 return entryName + " (" + super.getMessage() + ")"; 751 else 752 return super.getMessage(); 753 } 754 } 755 756 760 private static class ArchiveReadOnlyException extends ArchiveIllegalOperationException { 761 private ArchiveReadOnlyException() { 762 super("This archive is read-only!"); 763 } 764 } 765 766 770 boolean exists(final String entryName) { 771 return get(entryName) != null || get(entryName + ENTRY_SEPARATOR) != null; 772 } 773 774 boolean isFile(final String entryName) { 775 return get(entryName) != null; 776 } 777 778 boolean isDirectory(final String entryName) { 779 return get(entryName + ENTRY_SEPARATOR) != null; 780 } 781 782 Icon getOpenIcon(final String entryName) { 783 assert !EMPTY.equals(entryName); 784 785 ArchiveEntry entry = get(entryName); 786 if (entry == null) 787 entry = get(entryName + ENTRY_SEPARATOR_CHAR); 788 return entry != null ? entry.getOpenIcon() : null; 789 } 790 791 Icon getClosedIcon(final String entryName) { 792 assert !EMPTY.equals(entryName); 793 794 ArchiveEntry entry = get(entryName); 795 if (entry == null) 796 entry = get(entryName + ENTRY_SEPARATOR_CHAR); 797 return entry != null ? entry.getClosedIcon() : null; 798 } 799 800 boolean canWrite(final String entryName) { 801 return !isReadOnly() && exists(entryName); 802 } 803 804 boolean setReadOnly(final String entryName) { 805 return isReadOnly() && exists(entryName); 806 } 807 808 long length(final String entryName) { 809 final ArchiveEntry entry = get(entryName); 810 if (entry != null) { 811 final long length = entry.getSize(); 820 return length != -1 ? length : 0; 821 } 822 return 0; 824 } 825 826 long lastModified(final String entryName) { 827 ArchiveEntry entry = get(entryName); 828 if (entry == null) 829 entry = get(entryName + ENTRY_SEPARATOR); 830 if (entry != null) { 831 final long time = entry.getTime(); 838 return time >= 0 ? time : 0; 839 } 840 return 0; 842 } 843 844 boolean setLastModified(final String entryName, final long time) 845 throws IOException { 846 if (time < 0) 847 throw new IllegalArgumentException (entryName + 848 ": Negative entry modification time!"); 849 850 if (isReadOnly()) 851 return false; 852 853 ArchiveEntry entry = get(entryName); 854 if (entry == null) { 855 entry = get(entryName + ENTRY_SEPARATOR); 856 if (entry == null) { 857 return false; 859 } 860 } 861 862 touch(); 864 entry.setTime(time); 865 866 return true; 867 } 868 869 String [] list(final String entryName) { 870 final ArchiveEntry entry = get(entryName + ENTRY_SEPARATOR); 872 if (entry != null) 873 return entry.getMetaData().list(); 874 else 875 return null; } 877 878 String [] list( 879 final String entryName, 880 final FilenameFilter filenameFilter, 881 final File dir) { 882 final ArchiveEntry entry = get(entryName + ENTRY_SEPARATOR); 884 if (entry != null) 885 if (filenameFilter != null) 886 return entry.getMetaData().list(filenameFilter, dir); 887 else 888 return entry.getMetaData().list(); else 890 return null; } 892 893 File[] listFiles( 894 final String entryName, 895 final FilenameFilter filenameFilter, 896 final File dir, 897 final FileFactory factory) { final ArchiveEntry entry = get(entryName + ENTRY_SEPARATOR); 900 if (entry != null) 901 return entry.getMetaData().listFiles(filenameFilter, dir, factory); 902 else 903 return null; } 905 906 File[] listFiles( 907 final String entryName, 908 final FileFilter fileFilter, 909 final File dir, 910 final FileFactory factory) { final ArchiveEntry entry = get(entryName + ENTRY_SEPARATOR); 913 if (entry != null) 914 return entry.getMetaData().listFiles(fileFilter, dir, factory); 915 else 916 return null; } 918 919 void mkdir(final String entryName, final boolean createParents) 920 throws IOException { 921 beginCreateAndLink(entryName + ENTRY_SEPARATOR, createParents).commit(); 922 } 923 924 void delete(final String entryName) 925 throws IOException { 926 if (get(entryName) != null) { 927 unlink(entryName); 928 return; 929 } 930 final String dirEntryName = entryName + ENTRY_SEPARATOR; 931 if (get(dirEntryName) != null) { 932 unlink(dirEntryName); 933 return; 934 } 935 throw new IOException (entryName + " (archive entry does not exist)"); 936 } 937 938 942 949 private static class CompoundMap extends HashMap { 950 private static final LinkedList actions = new LinkedList (); 951 private static final Updater mapper = new Updater(); 952 static { 953 mapper.start(); 954 } 955 private static boolean done; 956 957 private final TreeMap tree; 958 959 private CompoundMap(Comparator comparator, int initialCapacity) { 960 super(initialCapacity); 961 tree = new TreeMap (comparator); 962 int priority = Thread.currentThread().getPriority(); 964 if (mapper.getPriority() < priority) 965 mapper.setPriority(priority); 966 } 967 968 public Object remove(Object key) { 969 synchronized (actions) { 970 actions.addLast(new Action(key)); 971 done = false; 972 actions.notifyAll(); 973 } 974 return super.remove(key); 975 } 976 977 public Object put(Object key, Object value) { 978 synchronized (actions) { 979 actions.addLast(new Action(key, value)); 980 done = false; 981 actions.notifyAll(); 982 } 983 return super.put(key, value); 984 } 985 986 public java.util.Collection values() { 987 synchronized (actions) { 988 while (!done) { 989 try { 990 actions.wait(); 991 } catch (InterruptedException ignored) { 992 } 993 } 994 return tree.values(); 995 } 996 } 997 998 private class Action implements Runnable { 999 private final boolean put; 1000 private final Object key; 1001 private Object value; 1002 1003 private Action(Object key, Object value) { 1004 put = true; 1005 this.key = key; 1006 this.value = value; 1007 } 1008 1009 private Action(Object key) { 1010 put = false; 1011 this.key = key; 1012 } 1013 1014 public void run() { 1015 if (put) 1016 tree.put(key, value); 1017 else 1018 tree.remove(key); 1019 } 1020 } 1021 1022 private static class Updater extends Thread { 1023 private Updater() { 1024 super("TrueZIP CompoundMap Updater"); 1025 setDaemon(true); 1026 } 1027 1028 public void run() { 1029 while (true) { 1030 final Action action; 1031 synchronized (actions) { 1032 done = actions.isEmpty(); 1033 if (done) { 1034 actions.notifyAll(); 1037 try { 1038 actions.wait(); 1039 } catch (InterruptedException ignored) { 1040 } 1041 continue; } 1043 action = (Action) actions.removeFirst(); 1044 } 1045 action.run(); 1046 } 1047 } 1048 } 1049 } } 1051 | Popular Tags |