0001: /*
0002: * Copyright 2007 Google Inc.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
0005: * use this file except in compliance with the License. You may obtain a copy of
0006: * the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
0012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
0013: * License for the specific language governing permissions and limitations under
0014: * the License.
0015: */
0016: package com.google.gwt.dev.jdt;
0017:
0018: import com.google.gwt.core.ext.TreeLogger;
0019: import com.google.gwt.core.ext.UnableToCompleteException;
0020: import com.google.gwt.core.ext.typeinfo.CompilationUnitProvider;
0021: import com.google.gwt.core.ext.typeinfo.HasMetaData;
0022: import com.google.gwt.core.ext.typeinfo.JClassType;
0023: import com.google.gwt.core.ext.typeinfo.JType;
0024: import com.google.gwt.core.ext.typeinfo.TypeOracle;
0025: import com.google.gwt.dev.shell.JavaScriptHost;
0026: import com.google.gwt.dev.shell.ShellGWT;
0027: import com.google.gwt.dev.shell.ShellJavaScriptHost;
0028: import com.google.gwt.dev.util.Util;
0029:
0030: import org.eclipse.jdt.internal.compiler.ast.CompilationUnitDeclaration;
0031: import org.eclipse.jdt.internal.compiler.ast.FieldDeclaration;
0032: import org.eclipse.jdt.internal.compiler.ast.Javadoc;
0033: import org.eclipse.jdt.internal.compiler.ast.MethodDeclaration;
0034: import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
0035: import org.eclipse.jdt.internal.compiler.impl.ReferenceContext;
0036: import org.eclipse.jdt.internal.compiler.lookup.ClassScope;
0037: import org.eclipse.jdt.internal.compiler.lookup.MethodScope;
0038: import org.eclipse.jdt.internal.compiler.lookup.ReferenceBinding;
0039: import org.eclipse.jdt.internal.compiler.lookup.SourceTypeBinding;
0040:
0041: import java.io.File;
0042: import java.io.FileInputStream;
0043: import java.io.FileOutputStream;
0044: import java.io.IOException;
0045: import java.io.ObjectInputStream;
0046: import java.io.ObjectOutputStream;
0047: import java.util.AbstractMap;
0048: import java.util.HashMap;
0049: import java.util.HashSet;
0050: import java.util.IdentityHashMap;
0051: import java.util.Iterator;
0052: import java.util.Map;
0053: import java.util.Set;
0054: import java.util.TreeSet;
0055:
0056: /**
0057: * CacheManager manages all the caching used to speed up hosted mode startup and
0058: * refresh, and manages the invalidations required to ensure that changes are
0059: * reflected correctly on reload.
0060: */
0061: public class CacheManager {
0062:
0063: /**
0064: * Maps SourceTypeBindings to their associated types.
0065: */
0066: static class Mapper {
0067: private final Map<ReferenceBinding, JClassType> map = new IdentityHashMap<ReferenceBinding, JClassType>();
0068:
0069: public JClassType get(ReferenceBinding referenceBinding) {
0070: JClassType type = map.get(referenceBinding);
0071: return type;
0072: }
0073:
0074: public void put(ReferenceBinding binding, JClassType type) {
0075: boolean firstPut = (null == map.put(binding, type));
0076: assert (firstPut);
0077: }
0078:
0079: public void reset() {
0080: map.clear();
0081: }
0082: }
0083:
0084: /**
0085: * This class is a very simple multi-valued map.
0086: */
0087: private static class Dependencies {
0088: private Map<String, HashSet<String>> map = new HashMap<String, HashSet<String>>();
0089:
0090: /**
0091: * This method adds <code>item</code> to the list stored under
0092: * <code>key</code>.
0093: *
0094: * @param key the key used to access the list
0095: * @param item the item to be added to the list
0096: */
0097: private void add(String dependerFilename,
0098: String dependeeFilename) {
0099: if (!map.containsKey(dependeeFilename)) {
0100: map.put(dependeeFilename, new HashSet<String>());
0101: }
0102:
0103: get(dependeeFilename).add(dependerFilename);
0104: }
0105:
0106: /**
0107: * This method gets the list stored under <code>key</code>.
0108: *
0109: * @param key the key used to access the list.
0110: * @return the list stored under <code>key</code>
0111: */
0112: private Set<String> get(String filename) {
0113: return map.get(filename);
0114: }
0115:
0116: private Set<String> transitiveClosure(final String filename) {
0117: String current = filename;
0118: TreeSet<String> queue = new TreeSet<String>();
0119: Set<String> finished = new HashSet<String>();
0120: queue.add(filename);
0121: while (true) {
0122: finished.add(current);
0123: Set<String> children = get(current);
0124: if (children != null) {
0125: for (Iterator<String> iter = children.iterator(); iter
0126: .hasNext();) {
0127: String child = iter.next();
0128: if (!finished.contains(child)) {
0129: queue.add(child);
0130: }
0131: }
0132: }
0133: if (queue.size() == 0) {
0134: return finished;
0135: } else {
0136: current = queue.first();
0137: queue.remove(current);
0138: }
0139: }
0140: }
0141: }
0142:
0143: /**
0144: * Visit all of the CUDs and extract dependencies. This visitor handles
0145: * explicit TypeRefs via the onTypeRef method AND it also deals with the
0146: * gwt.typeArgs annotation.
0147: *
0148: * <ol>
0149: * <li>Extract the list of type names from the gwt.typeArgs annotation</li>
0150: * <li>For each type name, locate the CUD that defines it</li>
0151: * <li>Add the referenced CUD as a dependency</li>
0152: * </ol>
0153: */
0154: private final class DependencyVisitor extends TypeRefVisitor {
0155: private final Dependencies dependencies;
0156:
0157: private DependencyVisitor(Dependencies dependencies) {
0158: this .dependencies = dependencies;
0159: }
0160:
0161: @Override
0162: public void endVisit(FieldDeclaration fieldDeclaration,
0163: final MethodScope scope) {
0164: extractDependenciesFromTypeArgs(fieldDeclaration.javadoc,
0165: scope.referenceContext());
0166: }
0167:
0168: @Override
0169: public void endVisit(MethodDeclaration methodDeclaration,
0170: ClassScope scope) {
0171: extractDependenciesFromTypeArgs(methodDeclaration.javadoc,
0172: scope.referenceContext());
0173: }
0174:
0175: @Override
0176: protected void onTypeRef(SourceTypeBinding referencedType,
0177: CompilationUnitDeclaration unitOfReferrer) {
0178: // If the referenced type belongs to a compilation unit that
0179: // was changed, then the unit in which it
0180: // is referenced must also be treated as changed.
0181: //
0182: String dependeeFilename = String.valueOf(referencedType
0183: .getFileName());
0184: String dependerFilename = String.valueOf(unitOfReferrer
0185: .getFileName());
0186:
0187: dependencies.add(dependerFilename, dependeeFilename);
0188: }
0189:
0190: private String combine(String[] strings, int startIndex) {
0191: StringBuffer sb = new StringBuffer();
0192: for (int i = startIndex; i < strings.length; i++) {
0193: String s = strings[i];
0194: sb.append(s);
0195: }
0196: return sb.toString();
0197: }
0198:
0199: /**
0200: * Extracts additional dependencies based on the gwt.typeArgs annotation.
0201: * This is not detected by JDT so we need to do it here. We do not perform
0202: * as strict a parse as the TypeOracle would do.
0203: *
0204: * @param javadoc javadoc text
0205: * @param scope scope that contains the definition
0206: * @param isField true if the javadoc is associated with a field
0207: */
0208: private void extractDependenciesFromTypeArgs(Javadoc javadoc,
0209: final ReferenceContext scope) {
0210: if (javadoc == null) {
0211: return;
0212: }
0213: final char[] source = scope.compilationResult().compilationUnit
0214: .getContents();
0215:
0216: TypeOracleBuilder.parseMetaDataTags(source,
0217: new HasMetaData() {
0218: public void addMetaData(String tagName,
0219: String[] values) {
0220: assert (values != null);
0221:
0222: if (!TypeOracle.TAG_TYPEARGS
0223: .equals(tagName)) {
0224: // don't care about non gwt.typeArgs
0225: return;
0226: }
0227:
0228: if (values.length == 0) {
0229: return;
0230: }
0231:
0232: Set<String> typeNames = new HashSet<String>();
0233:
0234: /*
0235: * if the first element starts with a "<" then we assume that no
0236: * parameter name was specified
0237: */
0238: int startIndex = 1;
0239: if (values[0].trim().startsWith("<")) {
0240: startIndex = 0;
0241: }
0242:
0243: extractTypeNamesFromTypeArg(combine(values,
0244: startIndex), typeNames);
0245:
0246: Iterator<String> it = typeNames.iterator();
0247: while (it.hasNext()) {
0248: String typeName = it.next();
0249:
0250: try {
0251: ICompilationUnit compilationUnit = astCompiler
0252: .getCompilationUnitForType(
0253: TreeLogger.NULL,
0254: typeName);
0255:
0256: String dependeeFilename = String
0257: .valueOf(compilationUnit
0258: .getFileName());
0259: String dependerFilename = String
0260: .valueOf(scope
0261: .compilationResult().compilationUnit
0262: .getFileName());
0263:
0264: dependencies.add(dependerFilename,
0265: dependeeFilename);
0266:
0267: } catch (UnableToCompleteException e) {
0268: // Purposely ignored
0269: }
0270: }
0271: }
0272:
0273: public String[][] getMetaData(String tagName) {
0274: return null;
0275: }
0276:
0277: public String[] getMetaDataTags() {
0278: return null;
0279: }
0280: }, javadoc);
0281: }
0282:
0283: /**
0284: * Extracts the type names referenced from a gwt.typeArgs annotation and
0285: * adds them to the set of type names.
0286: *
0287: * @param typeArg a string containing the type args as the user entered them
0288: * @param typeNames the set of type names referenced in the typeArgs string
0289: */
0290: private void extractTypeNamesFromTypeArg(String typeArg,
0291: Set<String> typeNames) {
0292: // Remove all whitespace
0293: typeArg = typeArg.replaceAll("\\\\s", "");
0294:
0295: // Remove anything that is not a raw type name
0296: String[] typeArgs = typeArg.split("[\\[\\]<>,]");
0297:
0298: for (int i = 0; i < typeArgs.length; ++i) {
0299: if (typeArgs[i].length() > 0) {
0300: typeNames.add(typeArgs[i]);
0301: }
0302: }
0303: }
0304: }
0305:
0306: /**
0307: * Caches information using a directory, with an in memory cache to prevent
0308: * unneeded disk access.
0309: */
0310: private static class DiskCache extends AbstractMap<String, Object> {
0311:
0312: private class FileEntry implements Map.Entry<String, Object> {
0313:
0314: private File file;
0315:
0316: private FileEntry(File file) {
0317: this .file = file;
0318: }
0319:
0320: private FileEntry(String className) {
0321: this (new File(directory,
0322: possiblyAddTmpExtension(className)));
0323: }
0324:
0325: private FileEntry(String className, Object o) {
0326: this (new File(directory,
0327: possiblyAddTmpExtension(className)));
0328: setValue(o);
0329: }
0330:
0331: public String getKey() {
0332: return possiblyRemoveTmpExtension(file.getName());
0333: }
0334:
0335: public Object getValue() {
0336: if (!file.exists()) {
0337: return null;
0338: }
0339: try {
0340: FileInputStream fis = new FileInputStream(file);
0341: ObjectInputStream ois = new ObjectInputStream(fis);
0342: Object out = ois.readObject();
0343: ois.close();
0344: fis.close();
0345: return out;
0346: } catch (IOException e) {
0347: return null;
0348: // If we can't read the file, we can't get the item from the cache.
0349: } catch (ClassNotFoundException e) {
0350: return null;
0351: // The class does not match because the serialUID is not correct
0352: // so we don't want this item anyway.
0353: }
0354: }
0355:
0356: public void remove() {
0357: file.delete();
0358: }
0359:
0360: public Object setValue(Object value) {
0361: Object o = getValue();
0362: FileOutputStream fos;
0363: try {
0364: fos = new FileOutputStream(file);
0365: ObjectOutputStream oos = new ObjectOutputStream(fos);
0366: oos.writeObject(value);
0367: oos.close();
0368: fos.close();
0369: } catch (IOException e) {
0370: markCacheDirectoryUnusable();
0371: }
0372: return o;
0373: }
0374:
0375: private long lastModified() {
0376: return file.lastModified();
0377: }
0378: }
0379:
0380: private final Map<String, Object> cache = new HashMap<String, Object>();
0381:
0382: // May be set to null after the fact if the cache directory becomes
0383: // unusable.
0384: private File directory;
0385:
0386: public DiskCache(File dirName) {
0387: if (dirName != null) {
0388: directory = dirName;
0389: possiblyCreateCacheDirectory();
0390: } else {
0391: directory = null;
0392: }
0393: }
0394:
0395: @Override
0396: public void clear() {
0397: cache.clear();
0398: if (directory != null) {
0399: for (Iterator<String> iter = keySet().iterator(); iter
0400: .hasNext();) {
0401: iter.remove();
0402: }
0403: }
0404: }
0405:
0406: @Override
0407: public Set<Entry<String, Object>> entrySet() {
0408: Set<Entry<String, Object>> out = new HashSet<Entry<String, Object>>() {
0409: @Override
0410: public boolean remove(Object o) {
0411: Entry<String, Object> entry = (Entry<String, Object>) o;
0412: boolean removed = (DiskCache.this .remove(entry
0413: .getKey())) != null;
0414: super .remove(o);
0415: return removed;
0416: }
0417: };
0418: out.addAll(cache.entrySet());
0419: // No directory means no persistence.
0420: if (directory != null) {
0421: possiblyCreateCacheDirectory();
0422: // Add files not yet loaded into this cache.
0423: File[] entries = directory.listFiles();
0424: for (int i = 0; i < entries.length; i++) {
0425: if (!cache.containsKey(new FileEntry(entries[i])
0426: .getKey())) {
0427: out.add(new FileEntry(entries[i]));
0428: }
0429: }
0430: }
0431: return out;
0432: }
0433:
0434: public Object get(String key) {
0435: if (cache.containsKey(key)) {
0436: return cache.get(key);
0437: }
0438: Object value = null;
0439: if (directory != null) {
0440: value = new FileEntry(key).getValue();
0441: cache.put(key, value);
0442: }
0443: return value;
0444: }
0445:
0446: @Override
0447: public Set<String> keySet() {
0448: Set<String> out = new HashSet<String>() {
0449: @Override
0450: public boolean remove(Object o) {
0451: boolean removed = (DiskCache.this .remove(o)) != null;
0452: super .remove(o);
0453: return removed;
0454: }
0455: };
0456: out.addAll(cache.keySet());
0457: // No directory means no persistence.
0458: if (directory != null) {
0459: possiblyCreateCacheDirectory();
0460: // Add files not yet loaded into this cache.
0461: File[] entries = directory.listFiles();
0462: for (int i = 0; i < entries.length; i++) {
0463: out.add(new FileEntry(entries[i].getName())
0464: .getKey());
0465: }
0466: }
0467: return out;
0468: }
0469:
0470: @Override
0471: public Object put(String key, Object value) {
0472: return put(key, value, true);
0473: }
0474:
0475: @Override
0476: public Object remove(Object key) {
0477: String fileName = (String) key;
0478: Object out = get(fileName);
0479: // No directory means no persistence.
0480: if (directory != null) {
0481: possiblyCreateCacheDirectory();
0482: FileEntry e = new FileEntry(fileName);
0483: e.remove();
0484: }
0485: cache.remove(key);
0486: return out;
0487: }
0488:
0489: private long lastModified(String className) {
0490: if (directory == null) {
0491: // we have no file on disk to refer to, so should return the same result
0492: // as if the file did not exist -- namely 0.
0493: return 0;
0494: }
0495: return new FileEntry(className).lastModified();
0496: }
0497:
0498: /**
0499: * This method marks the cache directory as being invalid, so we do not try
0500: * to use it.
0501: */
0502: private void markCacheDirectoryUnusable() {
0503: System.err.println("The directory "
0504: + directory.getAbsolutePath()
0505: + " is not usable as a cache directory");
0506: directory = null;
0507: }
0508:
0509: /**
0510: * This is used to ensure that if something wicked happens to the cache
0511: * directory while we are running, we do not crash.
0512: */
0513: private void possiblyCreateCacheDirectory() {
0514: directory.mkdirs();
0515: if (!(directory.exists() && directory.canWrite())) {
0516: markCacheDirectoryUnusable();
0517: }
0518: }
0519:
0520: private Object put(String key, Object value, boolean persist) {
0521: Object out = get(key);
0522:
0523: // We use toString to match the string value in FileEntry.
0524: cache.remove(key.toString());
0525:
0526: // Writes the file.
0527: if (persist && directory != null) {
0528: // This writes the file to the disk and is all that is needed.
0529: new FileEntry(key, value);
0530: }
0531: cache.put(key, value);
0532: return out;
0533: }
0534: }
0535:
0536: /**
0537: * The set of all classes whose bytecode needs to exist as bootstrap bytecode
0538: * to be taken as given by the bytecode compiler.
0539: */
0540: public static final Class<?>[] BOOTSTRAP_CLASSES = new Class<?>[] {
0541: JavaScriptHost.class, ShellJavaScriptHost.class,
0542: ShellGWT.class };
0543:
0544: /**
0545: * The set of bootstrap classes, which are marked transient, but are
0546: * nevertheless not recompiled each time, as they are bootstrap classes.
0547: */
0548: private static final Set<String> TRANSIENT_CLASS_NAMES;
0549:
0550: static {
0551: TRANSIENT_CLASS_NAMES = new HashSet<String>(
0552: BOOTSTRAP_CLASSES.length + 3);
0553: for (int i = 0; i < BOOTSTRAP_CLASSES.length; i++) {
0554: TRANSIENT_CLASS_NAMES.add(BOOTSTRAP_CLASSES[i].getName());
0555: }
0556: }
0557:
0558: // This method must be outside of DiskCache because of the restriction against
0559: // defining static methods in inner classes.
0560: private static String possiblyAddTmpExtension(Object className) {
0561: String fileName = className.toString();
0562: if (fileName.indexOf("-") == -1) {
0563: int hashCode = fileName.hashCode();
0564: String hashCodeStr = Integer.toHexString(hashCode);
0565: while (hashCodeStr.length() < 8) {
0566: hashCodeStr = '0' + hashCodeStr;
0567: }
0568: fileName = fileName + "-" + hashCodeStr + ".tmp";
0569: }
0570: return fileName;
0571: }
0572:
0573: // This method must be outside of DiskCache because of the restriction against
0574: // defining static methods in inner classes.
0575: private static String possiblyRemoveTmpExtension(Object fileName) {
0576: String className = fileName.toString();
0577: if (className.indexOf("-") != -1) {
0578: className = className.split("-")[0];
0579: }
0580: return className;
0581: }
0582:
0583: private final Set<CompilationUnitProvider> addedCups = new HashSet<CompilationUnitProvider>();
0584:
0585: private final AstCompiler astCompiler;
0586:
0587: private final DiskCache byteCodeCache;
0588:
0589: private final File cacheDir;
0590:
0591: private final Set<String> changedFiles;
0592:
0593: private final Map<String, CompilationUnitDeclaration> cudsByFileName;
0594:
0595: private final Map<String, CompilationUnitProvider> cupsByLocation = new HashMap<String, CompilationUnitProvider>();
0596:
0597: private boolean firstTime = true;
0598:
0599: /**
0600: * Set of {@link CompilationUnitProvider} locations for all of the compilation
0601: * units generated by {@link com.google.gwt.core.ext.Generator Generator}s.
0602: *
0603: * TODO: This seems like it should be a Set of CUPs rather than a set of CUP
0604: * locations.
0605: */
0606: private final Set<String> generatedCupLocations = new HashSet<String>();
0607:
0608: private final Set<String> generatedResources = new HashSet<String>();
0609:
0610: private final Mapper identityMapper = new Mapper();
0611:
0612: private final Set<String> invalidatedTypes = new HashSet<String>();
0613:
0614: private final TypeOracle oracle;
0615:
0616: private final Map<String, Long> timesByLocation = new HashMap<String, Long>();
0617:
0618: private boolean typeOracleBuilderFirstTime = true;
0619:
0620: private final Map<String, ICompilationUnitAdapter> unitsByCup = new HashMap<String, ICompilationUnitAdapter>();
0621:
0622: /**
0623: * Creates a new <code>CacheManager</code>, creating a new
0624: * <code>TypeOracle</code>. This constructor does not specify a cache
0625: * directory, and therefore is to be used in unit tests and executables that
0626: * do not need caching.
0627: */
0628: public CacheManager() {
0629: this (null, null);
0630: }
0631:
0632: /**
0633: * Creates a new <code>CacheManager</code>, creating a new
0634: * <code>TypeOracle</code>. This constructor uses the specified cacheDir,
0635: * and does cache information across reloads. If the specified cacheDir is
0636: * null, caching across reloads will be disabled.
0637: */
0638: public CacheManager(String cacheDir, TypeOracle oracle) {
0639: if (oracle == null) {
0640: this .oracle = new TypeOracle();
0641: } else {
0642: this .oracle = oracle;
0643: }
0644: changedFiles = new HashSet<String>();
0645: cudsByFileName = new HashMap<String, CompilationUnitDeclaration>();
0646: if (cacheDir != null) {
0647: this .cacheDir = new File(cacheDir);
0648: this .cacheDir.mkdirs();
0649: byteCodeCache = new DiskCache(
0650: new File(cacheDir, "bytecode"));
0651: } else {
0652: this .cacheDir = null;
0653: byteCodeCache = new DiskCache(null);
0654: }
0655: SourceOracleOnTypeOracle sooto = new SourceOracleOnTypeOracle(
0656: this .oracle);
0657: astCompiler = new AstCompiler(sooto);
0658: }
0659:
0660: /**
0661: * Creates a new <code>CacheManager</code>, using the supplied
0662: * <code>TypeOracle</code>. This constructor does not specify a cache
0663: * directory, and therefore is to be used in unit tests and executables that
0664: * do not need caching.
0665: */
0666: public CacheManager(TypeOracle typeOracle) {
0667: this (null, typeOracle);
0668: }
0669:
0670: /**
0671: * Adds the specified {@link CompilationUnitProvider} to the set of CUPs
0672: * generated by {@link com.google.gwt.core.ext.Generator Generator}s.
0673: * Generated <code>CompilationUnitProviders</code> are not cached across
0674: * reloads.
0675: */
0676: public void addGeneratedCup(CompilationUnitProvider generatedCup) {
0677: assert (generatedCup != null);
0678:
0679: generatedCupLocations.add(generatedCup.getLocation());
0680: }
0681:
0682: public void addGeneratedResource(String partialPath) {
0683: generatedResources.add(partialPath);
0684: }
0685:
0686: /**
0687: * This method returns the <code>TypeOracle</code> associated with this
0688: * <code>CacheManager</code>.
0689: */
0690: public TypeOracle getTypeOracle() {
0691: return oracle;
0692: }
0693:
0694: public boolean hasGeneratedResource(String partialPath) {
0695: return generatedResources.contains(partialPath);
0696: }
0697:
0698: /**
0699: * Ensures that all compilation units generated via generators are removed
0700: * from the system so that they will be generated again, and thereby take into
0701: * account input that may have changed since the last reload.
0702: */
0703: public void invalidateVolatileFiles() {
0704: for (Iterator<CompilationUnitProvider> iter = addedCups
0705: .iterator(); iter.hasNext();) {
0706: CompilationUnitProvider cup = iter.next();
0707: if (isGeneratedCup(cup)) {
0708: iter.remove();
0709: }
0710: }
0711: generatedResources.clear();
0712: }
0713:
0714: /**
0715: * This method adds byte.
0716: *
0717: * @param logger
0718: * @param binaryTypeName
0719: * @param byteCode
0720: * @return
0721: */
0722: boolean acceptIntoCache(TreeLogger logger, String binaryTypeName,
0723: ByteCode byteCode) {
0724: synchronized (byteCodeCache) {
0725: if (getByteCode(binaryTypeName) == null) {
0726: byteCodeCache.put(binaryTypeName, byteCode, (!byteCode
0727: .isTransient()));
0728: logger.log(TreeLogger.SPAM, "Cached bytecode for "
0729: + binaryTypeName, null);
0730: return true;
0731: } else {
0732: logger.log(TreeLogger.SPAM,
0733: "Bytecode not re-cached for " + binaryTypeName,
0734: null);
0735: return false;
0736: }
0737: }
0738: }
0739:
0740: /**
0741: * Adds this compilation unit if it is not present, or is older. Otherwise
0742: * does nothing.
0743: *
0744: * @throws UnableToCompleteException thrown if we cannot figure out when this
0745: * cup was modified
0746: */
0747: void addCompilationUnit(CompilationUnitProvider cup)
0748: throws UnableToCompleteException {
0749: Long lastModified = new Long(cup.getLastModified());
0750: if (isCupUnchanged(cup, lastModified)) {
0751: return;
0752: }
0753: CompilationUnitProvider oldCup = getCup(cup);
0754: if (oldCup != null) {
0755: addedCups.remove(oldCup);
0756: markCupChanged(cup);
0757: }
0758: timesByLocation.put(cup.getLocation(), lastModified);
0759: cupsByLocation.put(cup.getLocation(), cup);
0760: addedCups.add(cup);
0761: }
0762:
0763: /**
0764: * This method modifies the field <code>changedFiles</code> to contain all
0765: * of the additional files that are capable of reaching any of the files
0766: * currently contained within <code>changedFiles</code>.
0767: */
0768: void addDependentsToChangedFiles() {
0769: final Dependencies dependencies = new Dependencies();
0770:
0771: DependencyVisitor trv = new DependencyVisitor(dependencies);
0772:
0773: // Find references to type in units that aren't any longer valid.
0774: //
0775: for (CompilationUnitDeclaration cud : cudsByFileName.values()) {
0776: cud.traverse(trv, cud.scope);
0777: }
0778:
0779: Set<String> toTraverse = new HashSet<String>(changedFiles);
0780: for (Iterator<String> iter = toTraverse.iterator(); iter
0781: .hasNext();) {
0782: String fileName = iter.next();
0783: changedFiles.addAll(dependencies
0784: .transitiveClosure(fileName));
0785: }
0786: }
0787:
0788: ICompilationUnit findUnitForCup(CompilationUnitProvider cup) {
0789: if (!unitsByCup.containsKey(cup.getLocation())) {
0790: unitsByCup.put(cup.getLocation(),
0791: new ICompilationUnitAdapter(cup));
0792: }
0793: return unitsByCup.get(cup.getLocation());
0794: }
0795:
0796: Set<CompilationUnitProvider> getAddedCups() {
0797: return addedCups;
0798: }
0799:
0800: AstCompiler getAstCompiler() {
0801: return astCompiler;
0802: }
0803:
0804: /**
0805: * Gets the bytecode from the cache, rejecting it if an incompatible change
0806: * occurred since it was cached.
0807: */
0808: ByteCode getByteCode(String binaryTypeName) {
0809: synchronized (byteCodeCache) {
0810: ByteCode byteCode = (ByteCode) byteCodeCache
0811: .get(binaryTypeName);
0812: // we do not want bytecode created with a different classpath or os or
0813: // version of GWT.
0814: if ((byteCode != null)
0815: && byteCode.getSystemIdentifier() != null
0816: && (!(byteCode.getSystemIdentifier()
0817: .equals(ByteCode
0818: .getCurrentSystemIdentifier())))) {
0819: byteCodeCache.remove(binaryTypeName);
0820: byteCode = null;
0821: }
0822: if (byteCode != null) {
0823: // Found it.
0824: //
0825: return byteCode;
0826: } else {
0827: // This type has not been compiled before, or we tried but failed.
0828: //
0829: return null;
0830: }
0831: }
0832: }
0833:
0834: Set<String> getChangedFiles() {
0835: return changedFiles;
0836: }
0837:
0838: Map<String, CompilationUnitDeclaration> getCudsByFileName() {
0839: return cudsByFileName;
0840: }
0841:
0842: CompilationUnitProvider getCup(CompilationUnitProvider cup) {
0843: return getCupsByLocation().get(cup.getLocation());
0844: }
0845:
0846: Object getCupLastUpdateTime(CompilationUnitProvider cup) {
0847: return getTimesByLocation().get(cup.getLocation());
0848: }
0849:
0850: Map<String, CompilationUnitProvider> getCupsByLocation() {
0851: return cupsByLocation;
0852: }
0853:
0854: Mapper getIdentityMapper() {
0855: return identityMapper;
0856: }
0857:
0858: Map<String, Long> getTimesByLocation() {
0859: return timesByLocation;
0860: }
0861:
0862: JType getTypeForBinding(ReferenceBinding referenceBinding) {
0863: return identityMapper.get(referenceBinding);
0864: }
0865:
0866: /**
0867: * This removes all state changed since the last time the typeOracle was run.
0868: * Since the typeOracle information is not cached on disk, this is not needed
0869: * the first time.
0870: *
0871: * @param typeOracle
0872: */
0873: void invalidateOnRefresh(TypeOracle typeOracle) {
0874: // If a class is changed, the set of classes in the transitive closure
0875: // of "refers to" must be marked changed as well.
0876: // The initial change set is computed in addCompilationUnit.
0877: // For the first time we do not do this because the compiler
0878: // has no cached info.
0879: if (!isTypeOracleBuilderFirstTime()) {
0880: changedFiles.addAll(generatedCupLocations);
0881: addDependentsToChangedFiles();
0882:
0883: for (Iterator<String> iter = changedFiles.iterator(); iter
0884: .hasNext();) {
0885: String location = iter.next();
0886: CompilationUnitProvider cup = getCupsByLocation().get(
0887: location);
0888: unitsByCup.remove(location);
0889: Util.invokeInaccessableMethod(TypeOracle.class,
0890: "invalidateTypesInCompilationUnit",
0891: new Class[] { CompilationUnitProvider.class },
0892: typeOracle, new Object[] { cup });
0893: }
0894: astCompiler.invalidateChangedFiles(changedFiles,
0895: invalidatedTypes);
0896: } else {
0897: becomeTypeOracleNotFirstTime();
0898: }
0899: }
0900:
0901: /**
0902: * Was this cup, last modified at time lastModified modified since it was last
0903: * processed by the system?
0904: */
0905: boolean isCupUnchanged(CompilationUnitProvider cup,
0906: Long lastModified) {
0907: Long oldTime = (Long) getCupLastUpdateTime(cup);
0908: if (oldTime != null) {
0909: if (oldTime.longValue() >= lastModified.longValue()
0910: && (!cup.isTransient())) {
0911: return true;
0912: }
0913: }
0914: return false;
0915: }
0916:
0917: /**
0918: * This method is called when a cup is known to have changed. This will ensure
0919: * that all the types defined in this cup are invalidated.
0920: *
0921: * @param cup the cup modified
0922: */
0923: void markCupChanged(CompilationUnitProvider cup) {
0924: changedFiles.add(String.valueOf(cup.getLocation()));
0925: }
0926:
0927: boolean removeFromCache(TreeLogger logger, String binaryTypeName) {
0928: synchronized (byteCodeCache) {
0929: if (getByteCode(binaryTypeName) == null) {
0930: logger.log(TreeLogger.SPAM, "Bytecode for "
0931: + binaryTypeName
0932: + " was not cached, so not removing", null);
0933: return false;
0934: } else {
0935: byteCodeCache.remove(binaryTypeName);
0936: logger.log(TreeLogger.SPAM,
0937: "Bytecode not re-cached for " + binaryTypeName,
0938: null);
0939: return false;
0940: }
0941: }
0942: }
0943:
0944: /**
0945: * This method removes all of the bytecode which is out of date from the
0946: * bytecode cache. The set of files needing to be changed are going to be the
0947: * set already known to be changed plus those that are out of date in the
0948: * bytecode cache.
0949: */
0950: void removeStaleByteCode(TreeLogger logger,
0951: AbstractCompiler compiler) {
0952: if (cacheDir == null) {
0953: byteCodeCache.clear();
0954: return;
0955: }
0956: if (isFirstTime()) {
0957: Set<String> classNames = byteCodeCache.keySet();
0958: for (Iterator<String> iter = classNames.iterator(); iter
0959: .hasNext();) {
0960: String className = iter.next();
0961: ByteCode byteCode = ((ByteCode) (byteCodeCache
0962: .get(className)));
0963: if (byteCode == null) {
0964: iter.remove();
0965: continue;
0966: }
0967: String qname = byteCode.getBinaryTypeName();
0968: if (TRANSIENT_CLASS_NAMES.contains(qname)) {
0969: continue;
0970: }
0971: String location = byteCode.getLocation();
0972: if (byteCode.isTransient()) {
0973: // GWT transient classes; no need to test.
0974: // Either standardGeneratorContext created it
0975: // in which case we already know it is invalid
0976: // or its something like GWT and it lives.
0977: continue;
0978: }
0979: String fileName = Util.findFileName(location);
0980: CompilationUnitDeclaration compilationUnitDeclaration = cudsByFileName
0981: .get(location);
0982: if (compilationUnitDeclaration == null) {
0983: changedFiles.add(location);
0984: continue;
0985: }
0986: long srcLastModified = Long.MAX_VALUE;
0987: File srcLocation = new File(fileName);
0988: if (srcLocation.exists()) {
0989: srcLastModified = srcLocation.lastModified();
0990: }
0991: long byteCodeLastModified = byteCodeCache
0992: .lastModified(className);
0993: if (srcLastModified >= byteCodeLastModified) {
0994: changedFiles.add(location);
0995: }
0996: }
0997: addDependentsToChangedFiles();
0998: }
0999: becomeNotFirstTime();
1000: invalidateChangedFiles(logger, compiler);
1001: }
1002:
1003: void setTypeForBinding(ReferenceBinding binding, JClassType type) {
1004: identityMapper.put(binding, type);
1005: }
1006:
1007: private void becomeNotFirstTime() {
1008: firstTime = false;
1009: }
1010:
1011: private void becomeTypeOracleNotFirstTime() {
1012: typeOracleBuilderFirstTime = false;
1013: }
1014:
1015: /**
1016: * Actually performs the work of removing the invalidated data from the
1017: * system. At this point, changedFiles should be complete. After this method
1018: * is called, changedFiles should now be empty, since all invalidation that is
1019: * needed to be done.
1020: *
1021: * @param logger logs the process
1022: * @param compiler the compiler caches data, so must be invalidated
1023: */
1024: private void invalidateChangedFiles(TreeLogger logger,
1025: AbstractCompiler compiler) {
1026: Set<String> invalidTypes = new HashSet<String>();
1027: if (logger.isLoggable(TreeLogger.TRACE)) {
1028: TreeLogger branch = logger.branch(TreeLogger.TRACE,
1029: "The following compilation units have changed since "
1030: + "the last compilation to bytecode", null);
1031: for (Iterator<String> iter = changedFiles.iterator(); iter
1032: .hasNext();) {
1033: String filename = iter.next();
1034: branch.log(TreeLogger.TRACE, filename, null);
1035: }
1036: }
1037: for (String key : byteCodeCache.keySet()) {
1038: ByteCode byteCode = ((ByteCode) (byteCodeCache.get(key)));
1039: if (byteCode != null) {
1040: String location = byteCode.getLocation();
1041: if (changedFiles.contains(location)) {
1042: String binaryTypeName = byteCode
1043: .getBinaryTypeName();
1044: invalidTypes.add(binaryTypeName);
1045: removeFromCache(logger, binaryTypeName);
1046: }
1047: }
1048: }
1049: compiler.invalidateUnitsInFiles(changedFiles, invalidTypes);
1050: changedFiles.clear();
1051: }
1052:
1053: private boolean isFirstTime() {
1054: return firstTime;
1055: }
1056:
1057: private boolean isGeneratedCup(CompilationUnitProvider cup) {
1058: return generatedCupLocations.contains(cup.getLocation());
1059: }
1060:
1061: private boolean isTypeOracleBuilderFirstTime() {
1062: return typeOracleBuilderFirstTime;
1063: }
1064: }
|