/******************************************************************************* * Copyright (c) 2000, 2013 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * 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 { StringBuffer buffer = new StringBuffer(); 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; } }