001: /*
002: * Copyright 2007 Google Inc.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License"); you may not
005: * use this file except in compliance with the License. You may obtain a copy of
006: * the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
012: * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
013: * License for the specific language governing permissions and limitations under
014: * the License.
015: */
016: package com.google.gwt.dev.jdt;
017:
018: import com.google.gwt.core.ext.TreeLogger;
019: import com.google.gwt.core.ext.UnableToCompleteException;
020:
021: import org.eclipse.jdt.core.compiler.CharOperation;
022: import org.eclipse.jdt.internal.compiler.ClassFile;
023: import org.eclipse.jdt.internal.compiler.CompilationResult;
024: import org.eclipse.jdt.internal.compiler.env.ICompilationUnit;
025:
026: /**
027: * A facade around the JDT compiler to manage on-demand Java source to bytecode
028: * compilation, caching compiled bytecode where possible.
029: */
030: public class ByteCodeCompiler extends AbstractCompiler {
031:
032: private final CacheManager cacheManager;
033:
034: /**
035: * Creates a bytecode compiler for use not in hosted mode. All bytecode will
036: * be thrown away after reload.
037: *
038: * @param sourceOracle used to find the source
039: */
040: public ByteCodeCompiler(SourceOracle sourceOracle) {
041: super (sourceOracle, true);
042: this .cacheManager = new CacheManager();
043: }
044:
045: /**
046: * Creates a byte code compiler given the supplied sourceOracle (to find the
047: * source) and the supplied cacheManager (to keep the bytecode and other
048: * info). If the cacheManager has a cacheDir, it will keep bytecode across
049: * reload, and load them from the cacheDir on startup. Otherwise, each reload
050: * will clear the cache. In hosted mode, the cacheManager used to create this
051: * object should be the same one used to create the typeOracleBuilder.
052: *
053: * @param sourceOracle used to find the source
054: * @param cacheManager used to keep the cached information
055: */
056: public ByteCodeCompiler(SourceOracle sourceOracle,
057: CacheManager cacheManager) {
058: super (sourceOracle, true);
059: this .cacheManager = cacheManager;
060: }
061:
062: /**
063: * Get the bytecode for the specified type.
064: *
065: * @param binaryTypeName the binary type name to look up or compile
066: */
067: public byte[] getClassBytes(TreeLogger logger, String binaryTypeName)
068: throws UnableToCompleteException {
069:
070: // We use a thread logger proxy because we can't wind the logger through
071: // JDT directly.
072: //
073: String msg = "Getting bytecode for '" + binaryTypeName + "'";
074: logger = logger.branch(TreeLogger.SPAM, msg, null);
075:
076: TreeLogger oldLogger = threadLogger.push(logger);
077: try {
078:
079: // Check the bytecode cache in case we've already compiled it.
080: //
081: ByteCode byteCode = doGetByteCodeFromCache(logger,
082: binaryTypeName);
083: if (byteCode != null) {
084: // We have it already.
085: //
086: return byteCode.getBytes();
087: }
088:
089: // Need to compile it. It could be the case that we have tried before and
090: // failed, but on the off chance that it's been fixed since then, we adopt
091: // a policy of always trying to recompile if we don't have it cached.
092: //
093: ICompilationUnit start = getCompilationUnitForType(logger,
094: binaryTypeName);
095: compile(logger, new ICompilationUnit[] { start });
096:
097: // Check the cache again. If it's there now, we succeeded.
098: // If it isn't there now, we've already logged the error.
099: //
100: byteCode = doGetByteCodeFromCache(logger, binaryTypeName);
101: if (byteCode != null) {
102: return byteCode.getBytes();
103: } else {
104: throw new UnableToCompleteException();
105: }
106: } finally {
107: threadLogger.pop(oldLogger);
108: }
109: }
110:
111: /**
112: * Prevents the compile process from ever trying to compile these types from
113: * source. This is used for special types that would not compile correctly
114: * from source.
115: *
116: * @param binaryTypeName the binary name of the specified type
117: */
118: public void putClassBytes(TreeLogger logger, String binaryTypeName,
119: byte[] bytes, String location) {
120:
121: // We must remember the package name independently in case this is a type
122: // the host doesn't actually know about.
123: //
124: String pkgName = "";
125: int lastDot = binaryTypeName.lastIndexOf('.');
126: if (lastDot != -1) {
127: pkgName = binaryTypeName.substring(0, lastDot);
128: }
129: rememberPackage(pkgName);
130:
131: // Cache the bytes.
132: //
133: ByteCode byteCode = new ByteCode(binaryTypeName, bytes,
134: location, true);
135: cacheManager.acceptIntoCache(logger, binaryTypeName, byteCode);
136: }
137:
138: /**
139: * This method removes the bytecode which is no longer current, or if the
140: * cacheManager does not have a cacheDir, all the bytecode.
141: *
142: * @param logger used to describe the results to the user
143: */
144: public void removeStaleByteCode(TreeLogger logger) {
145: cacheManager.removeStaleByteCode(logger, this );
146: }
147:
148: @Override
149: protected void doAcceptResult(CompilationResult result) {
150: // Take all compiled class files and put them in the byte cache.
151: //
152: TreeLogger logger = getLogger();
153: ClassFile[] classFiles = result.getClassFiles();
154: for (int i = 0; i < classFiles.length; i++) {
155: ClassFile classFile = classFiles[i];
156: char[][] compoundName = classFile.getCompoundName();
157: char[] classNameChars = CharOperation.concatWith(
158: compoundName, '.');
159: String className = String.valueOf(classNameChars);
160: byte bytes[] = classFile.getBytes();
161: String loc = String.valueOf(result.compilationUnit
162: .getFileName());
163: boolean isTransient = true;
164: if (result.compilationUnit instanceof ICompilationUnitAdapter) {
165: ICompilationUnitAdapter unit = (ICompilationUnitAdapter) result.compilationUnit;
166: isTransient = unit.getCompilationUnitProvider()
167: .isTransient();
168: }
169: ByteCode byteCode = new ByteCode(className, bytes, loc,
170: isTransient);
171: if (cacheManager.acceptIntoCache(logger, className,
172: byteCode)) {
173: logger.log(TreeLogger.SPAM,
174: "Successfully compiled and cached '"
175: + className + "'", null);
176: }
177: }
178: }
179:
180: /**
181: * Checks the cache for bytecode for the specified binary type name. Silently
182: * removes and pretends it didn't see any bytecode that is out-of-date with
183: * respect to the compilation unit that provides it.
184: */
185: @Override
186: protected ByteCode doGetByteCodeFromCache(TreeLogger logger,
187: String binaryTypeName) {
188: return cacheManager.getByteCode(binaryTypeName);
189: }
190: }
|