blob: f0a7e3957d07a2102e4b32976eea7376cbf04448 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2013 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
* Yevgen Kogan - Bug 403475 - Hot Code Replace drops too much frames in some cases
*******************************************************************************/
package org.eclipse.jdt.internal.debug.core.hcr;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTMatcher;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
/**
* A <code>CompilationUnitDelta</code> represents the source code changes
* between a CU in the workspace and the same CU at some point in the past (from
* the local history).
* <p>
* This functionality is used in the context of Hot Code Replace to determine
* which stack frames are affected (and need to be dropped) by a class reload in
* the Java VM.
* <p>
* Typically a <code>CompilationUnitDelta</code> object is generated for a CU
* when the associated class is replaced in the VM.
*/
public class CompilationUnitDelta {
/**
* AST of the current source code
*/
private CompilationUnit fCurrentAst;
/**
* AST of the previous source code
*/
private CompilationUnit fPrevAst;
/**
* AST parser
*/
private ASTParser fParser = null;
/**
* AST matcher
*/
private ASTMatcher fMatcher = null;
private boolean fHasHistory = false;
/**
* Creates a new
* <code>CompilationUnitDelta object that calculates and stores
* the changes of the given CU since some point in time.
*/
public CompilationUnitDelta(ICompilationUnit cu, long timestamp)
throws CoreException {
if (cu.isWorkingCopy()) {
cu = cu.getPrimary();
}
// find underlying file
IFile file = (IFile) cu.getUnderlyingResource();
// get available editions
IFileState[] states = file.getHistory(null);
if (states == null || states.length <= 0) {
return;
}
fHasHistory = true;
IFileState found = null;
// find edition just before the given time stamp
for (IFileState state : states) {
long d = state.getModificationTime();
if (d < timestamp) {
found = state;
break;
}
}
if (found == null) {
found = states[states.length - 1];
}
InputStream oldContents = null;
InputStream newContents = null;
try {
oldContents = found.getContents();
newContents = file.getContents();
} catch (CoreException ex) {
return;
}
fPrevAst = parse(oldContents, cu);
fCurrentAst = parse(newContents, cu);
}
/**
* Returns <code>true</code>
* <ul>
* <li>if the source of the given member has been changed, or
* <li>if the element has been deleted, or
* <li>if the element has been newly created
* </ul>
* after the initial timestamp.
*/
public boolean hasChanged(String className, String methodName, String signature) {
if (!fHasHistory) {
return false; // optimistic: we have no history, so assume that
// member hasn't changed
}
if (fPrevAst == null || fCurrentAst == null) {
return true; // pessimistic: unable to build parse trees
}
MethodSearchVisitor visitor = new MethodSearchVisitor();
MethodDeclaration prev = findMethod(fPrevAst, visitor, className, methodName,
signature);
if (prev != null) {
MethodDeclaration curr = findMethod(fCurrentAst, visitor, className, methodName, signature);
if (curr != null) {
return !getMatcher().match(prev, curr);
}
}
return true;
}
private MethodDeclaration findMethod(CompilationUnit cu,
MethodSearchVisitor visitor, String className, String name, String signature) {
visitor.setTargetMethod(className, name, signature);
cu.accept(visitor);
return visitor.getMatch();
}
// ---- private stuff
// ----------------------------------------------------------------
/**
* Parses the given input stream and returns a tree of JavaNodes or a null
* in case of failure.
*/
private CompilationUnit parse(InputStream input, ICompilationUnit cu) {
char[] buffer = readString(input);
if (buffer != null) {
if (fParser == null) {
fParser = ASTParser.newParser(AST.JLS4);
}
fParser.setSource(buffer);
fParser.setProject(cu.getJavaProject());
fParser.setResolveBindings(true);
fParser.setKind(ASTParser.K_COMPILATION_UNIT);
fParser.setUnitName(cu.getElementName());
return (CompilationUnit) fParser.createAST(null);
}
return null;
}
/**
* Returns an AST matcher
*
* @return AST matcher
*/
private ASTMatcher getMatcher() {
if (fMatcher == null) {
fMatcher = new ASTMatcher();
}
return fMatcher;
}
/**
* Returns null if an error occurred.
*/
private char[] readString(InputStream is) {
if (is == null) {
return null;
}
BufferedReader reader = null;
try {
StringBuilder buffer = new StringBuilder();
char[] part = new char[2048];
int read = 0;
reader = new BufferedReader(new InputStreamReader(is,
ResourcesPlugin.getEncoding()));
while ((read = reader.read(part)) != -1) {
buffer.append(part, 0, read);
}
char[] b = new char[buffer.length()];
buffer.getChars(0, b.length, b, 0);
return b;
} catch (IOException ex) {
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ex) {
}
}
}
return null;
}
}