1 16 package com.google.gwt.dev.jdt; 17 18 import com.google.gwt.core.ext.TreeLogger; 19 import com.google.gwt.core.ext.UnableToCompleteException; 20 import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider; 21 import com.google.gwt.core.ext.typeinfo.HasMetaData; 22 import com.google.gwt.core.ext.typeinfo.JClassType; 23 import com.google.gwt.core.ext.typeinfo.JType; 24 import com.google.gwt.core.ext.typeinfo.TypeOracle; 25 import com.google.gwt.dev.shell.JavaScriptHost; 26 import com.google.gwt.dev.shell.ShellGWT; 27 import com.google.gwt.dev.shell.ShellJavaScriptHost; 28 import com.google.gwt.dev.util.Util; 29 30 import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration; 31 import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration; 32 import org.eclipse.jdt.internal.compiler.ast.Javadoc; 33 import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration; 34 import org.eclipse.jdt.internal.compiler.env.ICompilationUnit; 35 import org.eclipse.jdt.internal.compiler.impl.ReferenceContext; 36 import org.eclipse.jdt.internal.compiler.lookup.ClassScope; 37 import org.eclipse.jdt.internal.compiler.lookup.MethodScope; 38 import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding; 39 40 import java.io.File ; 41 import java.io.FileInputStream ; 42 import java.io.FileOutputStream ; 43 import java.io.IOException ; 44 import java.io.ObjectInputStream ; 45 import java.io.ObjectOutputStream ; 46 import java.util.AbstractMap ; 47 import java.util.HashMap ; 48 import java.util.HashSet ; 49 import java.util.IdentityHashMap ; 50 import java.util.Iterator ; 51 import java.util.Map ; 52 import java.util.Set ; 53 import java.util.TreeSet ; 54 55 60 public class CacheManager { 61 62 65 static class Mapper { 66 private final Map map = new IdentityHashMap (); 67 68 public JClassType get(SourceTypeBinding binding) { 69 JClassType type = (JClassType) map.get(binding); 70 return type; 71 } 72 73 public void put(SourceTypeBinding binding, JClassType type) { 74 boolean firstPut = (null == map.put(binding, type)); 75 assert (firstPut); 76 } 77 78 public void reset() { 79 map.clear(); 80 } 81 } 82 83 86 private static class Dependencies { 87 private Map map = new HashMap (); 88 89 96 private void add(String dependerFilename, String dependeeFilename) { 97 if (!map.containsKey(dependeeFilename)) { 98 map.put(dependeeFilename, new HashSet ()); 99 } 100 101 get(dependeeFilename).add(dependerFilename); 102 } 103 104 110 private Set get(String filename) { 111 return (Set ) map.get(filename); 112 } 113 114 private void remove(String filename) { 115 map.remove(filename); 116 } 117 118 private Set transitiveClosure(final String filename) { 119 String current = filename; 120 TreeSet queue = new TreeSet (); 121 Set finished = new HashSet (); 122 queue.add(filename); 123 while (true) { 124 finished.add(current); 125 Set children = get(current); 126 if (children != null) { 127 for (Iterator iter = children.iterator(); iter.hasNext();) { 128 String child = (String ) iter.next(); 129 if (!finished.contains(child)) { 130 queue.add(child); 131 } 132 } 133 } 134 if (queue.size() == 0) { 135 return finished; 136 } else { 137 current = (String ) queue.first(); 138 queue.remove(current); 139 } 140 } 141 } 142 } 143 144 155 private final class DependencyVisitor extends TypeRefVisitor { 156 private final Dependencies dependencies; 157 158 private DependencyVisitor(Dependencies dependencies) { 159 this.dependencies = dependencies; 160 } 161 162 public void endVisit(FieldDeclaration fieldDeclaration, 163 final MethodScope scope) { 164 extractDependenciesFromTypeArgs(fieldDeclaration.javadoc, 165 scope.referenceContext(), true); 166 } 167 168 public void endVisit(MethodDeclaration methodDeclaration, ClassScope scope) { 169 extractDependenciesFromTypeArgs(methodDeclaration.javadoc, 170 scope.referenceContext(), false); 171 } 172 173 protected void onTypeRef(SourceTypeBinding referencedType, 174 CompilationUnitDeclaration unitOfReferrer) { 175 String dependeeFilename = String.valueOf(referencedType.getFileName()); 180 String dependerFilename = String.valueOf(unitOfReferrer.getFileName()); 181 182 dependencies.add(dependerFilename, dependeeFilename); 183 } 184 185 private String combine(String [] strings, int startIndex) { 186 StringBuffer sb = new StringBuffer (); 187 for (int i = startIndex; i < strings.length; i++) { 188 String s = strings[i]; 189 sb.append(s); 190 } 191 return sb.toString(); 192 } 193 194 203 private void extractDependenciesFromTypeArgs(Javadoc javadoc, 204 final ReferenceContext scope, final boolean isField) { 205 if (javadoc == null) { 206 return; 207 } 208 final char[] source = scope.compilationResult().compilationUnit.getContents(); 209 210 TypeOracleBuilder.parseMetaDataTags(source, new HasMetaData() { 211 public void addMetaData(String tagName, String [] values) { 212 assert (values != null); 213 214 if (!TypeOracle.TAG_TYPEARGS.equals(tagName)) { 215 return; 217 } 218 219 if (values.length == 0) { 220 return; 221 } 222 223 Set typeNames = new HashSet (); 224 225 229 int startIndex = 1; 230 if (values[0].trim().startsWith("<")) { 231 startIndex = 0; 232 } 233 234 extractTypeNamesFromTypeArg(combine(values, startIndex), typeNames); 235 236 Iterator it = typeNames.iterator(); 237 while (it.hasNext()) { 238 String typeName = (String ) it.next(); 239 240 try { 241 ICompilationUnit compilationUnit = astCompiler.getCompilationUnitForType( 242 TreeLogger.NULL, typeName); 243 244 String dependeeFilename = String.valueOf(compilationUnit.getFileName()); 245 String dependerFilename = String.valueOf(scope.compilationResult().compilationUnit.getFileName()); 246 247 dependencies.add(dependerFilename, dependeeFilename); 248 249 } catch (UnableToCompleteException e) { 250 } 252 } 253 } 254 255 public String [][] getMetaData(String tagName) { 256 return null; 257 } 258 259 public String [] getMetaDataTags() { 260 return null; 261 } 262 }, javadoc); 263 } 264 265 272 private void extractTypeNamesFromTypeArg(String typeArg, Set typeNames) { 273 typeArg.replaceAll("\\\\s", ""); 275 276 String [] typeArgs = typeArg.split("[\\[\\]<>,]"); 278 279 for (int i = 0; i < typeArgs.length; ++i) { 280 if (typeArgs[i].length() > 0) { 281 typeNames.add(typeArgs[i]); 282 } 283 } 284 } 285 } 286 287 291 private static class DiskCache extends AbstractMap { 292 293 private class FileEntry implements Map.Entry { 294 295 private File file; 296 297 private FileEntry(File file) { 298 this.file = file; 299 } 300 301 private FileEntry(Object name) { 302 this(new File (directory, possiblyAddTmpExtension(name))); 303 } 304 305 private FileEntry(Object name, Object o) { 306 this(new File (directory, possiblyAddTmpExtension(name))); 307 setValue(o); 308 } 309 310 public Object getKey() { 311 return possiblyRemoveTmpExtension(file.getName()); 312 } 313 314 public Object getValue() { 315 if (!file.exists()) { 316 return null; 317 } 318 try { 319 FileInputStream fis = new FileInputStream (file); 320 ObjectInputStream ois = new ObjectInputStream (fis); 321 Object out = ois.readObject(); 322 ois.close(); 323 fis.close(); 324 return out; 325 } catch (IOException e) { 326 return null; 327 } catch (ClassNotFoundException e) { 329 return null; 330 } 333 } 334 335 public void remove() { 336 file.delete(); 337 } 338 339 public Object setValue(Object value) { 340 Object o = getValue(); 341 FileOutputStream fos; 342 try { 343 fos = new FileOutputStream (file); 344 ObjectOutputStream oos = new ObjectOutputStream (fos); 345 oos.writeObject(value); 346 oos.close(); 347 fos.close(); 348 } catch (IOException e) { 349 markCacheDirectoryUnusable(); 350 } 351 return o; 352 } 353 354 private long lastModified() { 355 return file.lastModified(); 356 } 357 } 358 359 private final Map cache = new HashMap (); 360 361 private File directory; 364 365 public DiskCache(File dirName) { 366 if (dirName != null) { 367 directory = dirName; 368 possiblyCreateCacheDirectory(); 369 } else { 370 directory = null; 371 } 372 } 373 374 public void clear() { 375 cache.clear(); 376 if (directory != null) { 377 for (Iterator iter = keySet().iterator(); iter.hasNext();) { 378 iter.remove(); 379 } 380 } 381 } 382 383 public Set entrySet() { 384 Set out = new HashSet () { 385 public boolean remove(Object o) { 386 boolean removed = (DiskCache.this.remove(((Entry) o).getKey())) != null; 387 super.remove(o); 388 return removed; 389 } 390 }; 391 out.addAll(cache.entrySet()); 392 if (directory != null) { 394 possiblyCreateCacheDirectory(); 395 File [] entries = directory.listFiles(); 397 for (int i = 0; i < entries.length; i++) { 398 if (!cache.containsKey(new FileEntry(entries[i]).getKey())) { 399 out.add(new FileEntry(entries[i])); 400 } 401 } 402 } 403 return out; 404 } 405 406 public Object get(Object key) { 407 if (cache.containsKey(key)) { 408 return cache.get(key); 409 } 410 Object value = null; 411 if (directory != null) { 412 value = new FileEntry(key).getValue(); 413 cache.put(key, value); 414 } 415 return value; 416 } 417 418 public Set keySet() { 419 Set out = new HashSet () { 420 public boolean remove(Object o) { 421 boolean removed = (DiskCache.this.remove(o)) != null; 422 super.remove(o); 423 return removed; 424 } 425 }; 426 out.addAll(cache.keySet()); 427 if (directory != null) { 429 possiblyCreateCacheDirectory(); 430 File [] entries = directory.listFiles(); 432 for (int i = 0; i < entries.length; i++) { 433 out.add(new FileEntry(entries[i].getName()).getKey()); 434 } 435 } 436 return out; 437 } 438 439 public Object put(Object key, Object value) { 440 return put(key, value, true); 441 } 442 443 public Object remove(Object key) { 444 Object out = get(key); 445 if (directory != null) { 447 possiblyCreateCacheDirectory(); 448 FileEntry e = new FileEntry(key); 449 e.remove(); 450 } 451 cache.remove(key); 452 return out; 453 } 454 455 private long lastModified(Object key) { 456 if (directory == null) { 457 return 0; 460 } 461 return new FileEntry(key).lastModified(); 462 } 463 464 468 private void markCacheDirectoryUnusable() { 469 System.err.println("The directory " + directory.getAbsolutePath() 470 + " is not usable as a cache directory"); 471 directory = null; 472 } 473 474 478 private void possiblyCreateCacheDirectory() { 479 directory.mkdirs(); 480 if (!(directory.exists() && directory.canWrite())) { 481 markCacheDirectoryUnusable(); 482 } 483 } 484 485 private Object put(Object key, Object value, boolean persist) { 486 Object out = get(key); 487 488 cache.remove(key.toString()); 490 491 if (persist && directory != null) { 493 new FileEntry(key, value); 495 } 496 cache.put(key, value); 497 return out; 498 } 499 } 500 501 505 public static final Class [] BOOTSTRAP_CLASSES = new Class [] { 506 JavaScriptHost.class, ShellJavaScriptHost.class, ShellGWT.class}; 507 508 512 private static final Set TRANSIENT_CLASS_NAMES; 513 514 static { 515 TRANSIENT_CLASS_NAMES = new HashSet (BOOTSTRAP_CLASSES.length + 3); 516 for (int i = 0; i < BOOTSTRAP_CLASSES.length; i++) { 517 TRANSIENT_CLASS_NAMES.add(BOOTSTRAP_CLASSES[i].getName()); 518 } 519 } 520 521 private static String possiblyAddTmpExtension(Object className) { 524 String fileName = className.toString(); 525 if (fileName.indexOf("-") == -1) { 526 int hashCode = fileName.hashCode(); 527 String hashCodeStr = Integer.toHexString(hashCode); 528 while (hashCodeStr.length() < 8) { 529 hashCodeStr = '0' + hashCodeStr; 530 } 531 fileName = fileName + "-" + hashCodeStr + ".tmp"; 532 } 533 return fileName; 534 } 535 536 private static String possiblyRemoveTmpExtension(Object fileName) { 539 String className = fileName.toString(); 540 if (className.indexOf("-") != -1) { 541 className = className.split("-")[0]; 542 } 543 return className; 544 } 545 546 private final Set addedCups = new HashSet (); 547 548 private final AstCompiler astCompiler; 549 550 private final DiskCache byteCodeCache; 551 552 private final File cacheDir; 553 554 private final Set changedFiles; 555 556 private final Map cudsByFileName; 557 558 private final Map cupsByLocation = new HashMap (); 559 560 private boolean firstTime = true; 561 562 569 private final Set generatedCupLocations = new HashSet (); 570 571 private final Mapper identityMapper = new Mapper(); 572 573 private final Set invalidatedTypes = new HashSet (); 574 575 private final TypeOracle oracle; 576 577 private final Map timesByLocation = new HashMap (); 578 579 private boolean typeOracleBuilderFirstTime = true; 580 581 private final Map unitsByCup = new HashMap (); 582 583 589 public CacheManager() { 590 this(null, null); 591 } 592 593 599 public CacheManager(String cacheDir, TypeOracle oracle) { 600 if (oracle == null) { 601 this.oracle = new TypeOracle(); 602 } else { 603 this.oracle = oracle; 604 } 605 changedFiles = new HashSet (); 606 cudsByFileName = new HashMap (); 607 if (cacheDir != null) { 608 this.cacheDir = new File (cacheDir); 609 this.cacheDir.mkdirs(); 610 byteCodeCache = new DiskCache(new File (cacheDir, "bytecode")); 611 } else { 612 this.cacheDir = null; 613 byteCodeCache = new DiskCache(null); 614 } 615 SourceOracleOnTypeOracle sooto = new SourceOracleOnTypeOracle(this.oracle); 616 astCompiler = new AstCompiler(sooto); 617 } 618 619 625 public CacheManager(TypeOracle typeOracle) { 626 this(null, typeOracle); 627 } 628 629 634 public void addGeneratedCup(CompilationUnitProvider generatedCup) { 635 assert (generatedCup != null); 636 637 generatedCupLocations.add(generatedCup.getLocation()); 638 } 639 640 644 public TypeOracle getTypeOracle() { 645 return oracle; 646 } 647 648 653 public void invalidateVolatileFiles() { 654 for (Iterator iter = addedCups.iterator(); iter.hasNext();) { 655 CompilationUnitProvider cup = (CompilationUnitProvider) iter.next(); 656 if (isGeneratedCup(cup)) { 657 iter.remove(); 658 } 659 } 660 } 661 662 670 boolean acceptIntoCache(TreeLogger logger, String binaryTypeName, 671 ByteCode byteCode) { 672 synchronized (byteCodeCache) { 673 if (getByteCode(logger, binaryTypeName) == null) { 674 byteCodeCache.put(binaryTypeName, byteCode, (!byteCode.isTransient())); 675 logger.log(TreeLogger.SPAM, "Cached bytecode for " + binaryTypeName, 676 null); 677 return true; 678 } else { 679 logger.log(TreeLogger.SPAM, "Bytecode not re-cached for " 680 + binaryTypeName, null); 681 return false; 682 } 683 } 684 } 685 686 693 void addCompilationUnit(CompilationUnitProvider cup) 694 throws UnableToCompleteException { 695 Long lastModified = new Long (cup.getLastModified()); 696 if (isCupUnchanged(cup, lastModified)) { 697 return; 698 } 699 CompilationUnitProvider oldCup = getCup(cup); 700 if (oldCup != null) { 701 addedCups.remove(oldCup); 702 markCupChanged(cup); 703 } 704 timesByLocation.put(cup.getLocation(), lastModified); 705 cupsByLocation.put(cup.getLocation(), cup); 706 addedCups.add(cup); 707 } 708 709 714 void addDependentsToChangedFiles() { 715 final Dependencies dependencies = new Dependencies(); 716 717 DependencyVisitor trv = new DependencyVisitor(dependencies); 718 719 for (Iterator iter = cudsByFileName.values().iterator(); iter.hasNext();) { 722 CompilationUnitDeclaration cud = (CompilationUnitDeclaration) iter.next(); 723 cud.traverse(trv, cud.scope); 724 } 725 726 Set toTraverse = new HashSet (changedFiles); 727 for (Iterator iter = toTraverse.iterator(); iter.hasNext();) { 728 String fileName = (String ) iter.next(); 729 changedFiles.addAll(dependencies.transitiveClosure(fileName)); 730 } 731 } 732 733 ICompilationUnit findUnitForCup(CompilationUnitProvider cup) { 734 if (!unitsByCup.containsKey(cup.getLocation())) { 735 unitsByCup.put(cup.getLocation(), new ICompilationUnitAdapter(cup)); 736 } 737 return (ICompilationUnit) unitsByCup.get(cup.getLocation()); 738 } 739 740 Set getAddedCups() { 741 return addedCups; 742 } 743 744 AstCompiler getAstCompiler() { 745 return astCompiler; 746 } 747 748 752 ByteCode getByteCode(TreeLogger logger, String binaryTypeName) { 753 synchronized (byteCodeCache) { 754 ByteCode byteCode = (ByteCode) byteCodeCache.get(binaryTypeName); 755 if ((byteCode != null) 758 && byteCode.getSystemIdentifier() != null 759 && (!(byteCode.getSystemIdentifier().equals(ByteCode.getCurrentSystemIdentifier())))) { 760 byteCodeCache.remove(binaryTypeName); 761 byteCode = null; 762 } 763 if (byteCode != null) { 764 return byteCode; 767 } else { 768 return null; 771 } 772 } 773 } 774 775 Set getChangedFiles() { 776 return changedFiles; 777 } 778 779 Map getCudsByFileName() { 780 return cudsByFileName; 781 } 782 783 CompilationUnitProvider getCup(CompilationUnitProvider cup) { 784 return (CompilationUnitProvider) getCupsByLocation().get(cup.getLocation()); 785 } 786 787 Object getCupLastUpdateTime(CompilationUnitProvider cup) { 788 return getTimesByLocation().get(cup.getLocation()); 789 } 790 791 Map getCupsByLocation() { 792 return cupsByLocation; 793 } 794 795 Mapper getIdentityMapper() { 796 return identityMapper; 797 } 798 799 Map getTimesByLocation() { 800 return timesByLocation; 801 } 802 803 JType getTypeForBinding(SourceTypeBinding sourceTypeBinding) { 804 return identityMapper.get(sourceTypeBinding); 805 } 806 807 814 void invalidateOnRefresh(TypeOracle typeOracle) { 815 if (!isTypeOracleBuilderFirstTime()) { 821 changedFiles.addAll(generatedCupLocations); 822 addDependentsToChangedFiles(); 823 824 for (Iterator iter = changedFiles.iterator(); iter.hasNext();) { 825 String location = (String ) iter.next(); 826 CompilationUnitProvider cup = (CompilationUnitProvider) getCupsByLocation().get( 827 location); 828 unitsByCup.remove(location); 829 Util.invokeInaccessableMethod(TypeOracle.class, 830 "invalidateTypesInCompilationUnit", 831 new Class [] {CompilationUnitProvider.class}, typeOracle, 832 new Object [] {cup}); 833 } 834 astCompiler.invalidateChangedFiles(changedFiles, invalidatedTypes); 835 } else { 836 becomeTypeOracleNotFirstTime(); 837 } 838 } 839 840 844 boolean isCupUnchanged(CompilationUnitProvider cup, Long lastModified) { 845 Long oldTime = (Long ) getCupLastUpdateTime(cup); 846 if (oldTime != null) { 847 if (oldTime.longValue() >= lastModified.longValue() 848 && (!cup.isTransient())) { 849 return true; 850 } 851 } 852 return false; 853 } 854 855 861 void markCupChanged(CompilationUnitProvider cup) { 862 changedFiles.add(String.valueOf(cup.getLocation())); 863 } 864 865 boolean removeFromCache(TreeLogger logger, String binaryTypeName) { 866 synchronized (byteCodeCache) { 867 if (getByteCode(logger, binaryTypeName) == null) { 868 logger.log(TreeLogger.SPAM, "Bytecode for " + binaryTypeName 869 + " was not cached, so not removing", null); 870 return false; 871 } else { 872 byteCodeCache.remove(binaryTypeName); 873 logger.log(TreeLogger.SPAM, "Bytecode not re-cached for " 874 + binaryTypeName, null); 875 return false; 876 } 877 } 878 } 879 880 886 void removeStaleByteCode(TreeLogger logger, AbstractCompiler compiler) { 887 if (cacheDir == null) { 888 byteCodeCache.clear(); 889 return; 890 } 891 if (isFirstTime()) { 892 Set classNames = byteCodeCache.keySet(); 893 for (Iterator iter = classNames.iterator(); iter.hasNext();) { 894 Object className = iter.next(); 895 ByteCode byteCode = ((ByteCode) (byteCodeCache.get(className))); 896 if (byteCode == null) { 897 iter.remove(); 898 continue; 899 } 900 String qname = byteCode.getBinaryTypeName(); 901 if (TRANSIENT_CLASS_NAMES.contains(qname)) { 902 continue; 903 } 904 if (byteCode != null) { 905 String location = byteCode.getLocation(); 906 if (byteCode.isTransient()) { 907 continue; 912 } 913 String fileName = Util.findFileName(location); 914 CompilationUnitDeclaration compilationUnitDeclaration = ((CompilationUnitDeclaration) cudsByFileName.get(location)); 915 if (compilationUnitDeclaration == null) { 916 changedFiles.add(location); 917 continue; 918 } 919 long srcLastModified = Long.MAX_VALUE; 920 File srcLocation = new File (fileName); 921 if (srcLocation.exists()) { 922 srcLastModified = srcLocation.lastModified(); 923 } 924 long byteCodeLastModified = byteCodeCache.lastModified(className); 925 if (srcLastModified >= byteCodeLastModified) { 926 changedFiles.add(location); 927 } 928 } 929 } 930 addDependentsToChangedFiles(); 931 } 932 becomeNotFirstTime(); 933 invalidateChangedFiles(logger, compiler); 934 } 935 936 void setTypeForBinding(SourceTypeBinding binding, JClassType type) { 937 identityMapper.put(binding, type); 938 } 939 940 private void becomeNotFirstTime() { 941 firstTime = false; 942 } 943 944 private void becomeTypeOracleNotFirstTime() { 945 typeOracleBuilderFirstTime = false; 946 } 947 948 957 private void invalidateChangedFiles(TreeLogger logger, 958 AbstractCompiler compiler) { 959 Set invalidTypes = new HashSet (); 960 if (logger.isLoggable(TreeLogger.TRACE)) { 961 TreeLogger branch = logger.branch(TreeLogger.TRACE, 962 "The following compilation units have changed since " 963 + "the last compilation to bytecode", null); 964 for (Iterator iter = changedFiles.iterator(); iter.hasNext();) { 965 String filename = (String ) iter.next(); 966 branch.log(TreeLogger.TRACE, filename, null); 967 } 968 } 969 for (Iterator iter = byteCodeCache.keySet().iterator(); iter.hasNext();) { 970 Object key = iter.next(); 971 ByteCode byteCode = ((ByteCode) (byteCodeCache.get(key))); 972 if (byteCode != null) { 973 String location = byteCode.getLocation(); 974 if (changedFiles.contains(location)) { 975 String binaryTypeName = byteCode.getBinaryTypeName(); 976 invalidTypes.add(binaryTypeName); 977 removeFromCache(logger, binaryTypeName); 978 } 979 } 980 } 981 compiler.invalidateUnitsInFiles(changedFiles, invalidTypes); 982 changedFiles.clear(); 983 } 984 985 private boolean isFirstTime() { 986 return firstTime; 987 } 988 989 private boolean isGeneratedCup(CompilationUnitProvider cup) { 990 return generatedCupLocations.contains(cup.getLocation()); 991 } 992 993 private boolean isTypeOracleBuilderFirstTime() { 994 return typeOracleBuilderFirstTime; 995 } 996 } 997 | Popular Tags |