package com.mobilesorcery.sdk.html5.debug.jsdt;
import java.util.ArrayList;
import java.util.HashMap;
import org.eclipse.core.resources.IFile;
import org.eclipse.debug.core.DebugException;
import com.mobilesorcery.sdk.core.CoreMoSyncPlugin;
import com.mobilesorcery.sdk.core.Util;
import com.mobilesorcery.sdk.html5.debug.IRedefinable;
import com.mobilesorcery.sdk.html5.debug.IRedefiner;
import com.mobilesorcery.sdk.html5.debug.RedefineException;
import com.mobilesorcery.sdk.html5.debug.RedefinitionResult;
import com.mobilesorcery.sdk.html5.debug.ReloadVirtualMachine;
import com.mobilesorcery.sdk.html5.debug.hotreplace.FileRedefinable;
import com.mobilesorcery.sdk.html5.debug.hotreplace.FunctionRedefinable;
import com.mobilesorcery.sdk.html5.debug.hotreplace.HTMLRedefinable;
public class ReloadRedefiner implements IRedefiner {
private ReloadVirtualMachine vm;
private ArrayList<IFile> fileUpdates = new ArrayList<IFile>();
private ArrayList<FunctionRedefinable> functionUpdates = new ArrayList<FunctionRedefinable>();
private int dropToFrame = -1;
private RedefinitionResult redefineResult;
private boolean forceReload;
public ReloadRedefiner(ReloadVirtualMachine vm, boolean forceReload) {
this.vm = vm;
this.forceReload = forceReload;
this.redefineResult = RedefinitionResult.ok();
}
@Override
public void changed(IRedefinable redefinable, IRedefinable replacement) {
if (redefinable instanceof FileRedefinable) {
redefineResult = redefineResult.merge(collectFile(
(FileRedefinable) redefinable,
(FileRedefinable) replacement));
} else if (!forceReload && redefinable instanceof FunctionRedefinable) {
redefineResult = redefineResult.merge(collectFunction(
(FunctionRedefinable) redefinable,
(FunctionRedefinable) replacement));
}
}
@Override
public void added(IRedefinable added) {
cannotAddOrRemove(added);
}
@Override
public void deleted(IRedefinable deleted) {
cannotAddOrRemove(deleted);
}
private void cannotAddOrRemove(IRedefinable redefinable) {
if (redefinable instanceof FileRedefinable
|| (!forceReload && redefinable instanceof FunctionRedefinable)) {
redefineResult = redefineResult.merge(RedefinitionResult
.fail("Cannot add or delete files and functions"));
}
}
private RedefinitionResult collectFunction(FunctionRedefinable redefinable,
FunctionRedefinable replacement) {
boolean sourceEqual = Util.equals(replacement.getFunctionSource(false),
redefinable.getFunctionSource(false));
boolean instrumentedSourceEqual = Util.equals(replacement.getFunctionSource(true),
redefinable.getFunctionSource(true));
if (instrumentedSourceEqual) {
return RedefinitionResult.ok();
}
RedefinitionResult result = redefinable.canRedefine(replacement);
if (!RedefinitionResult.isOk(result)) {
return result;
}
functionUpdates.add(replacement);
ReloadThreadReference mainThread = vm.mainThread();
if (mainThread.isSuspended()) {
for (Object frameObj : mainThread.frames()) {
ReloadStackFrame frame = (ReloadStackFrame) frameObj;
if (frame != null) {
if (!sourceEqual && matchesFrame(redefinable, frame)) {
int stackDepth = frame.getStackDepth();
// As usual: reverse!
dropToFrame = Math.max(dropToFrame,
mainThread.frameCount() - stackDepth - 1);
}
}
}
}
return RedefinitionResult.ok();
}
private boolean matchesFrame(FunctionRedefinable redefinable,
ReloadStackFrame frame) {
SimpleLocation location = (SimpleLocation) frame.location();
SimpleScriptReference script = (SimpleScriptReference) location
.scriptReference();
IFile file = script.getFile();
int line = location.lineNumber();
FileRedefinable fileRedefinable = redefinable
.getParent(FileRedefinable.class);
if (fileRedefinable != null) {
return Util.equals(redefinable.getFunctionName(),
location.functionName())
&& redefinable.isLineInSourceRange(line)
&& fileRedefinable.getFile().equals(file);
}
return false;
}
private RedefinitionResult collectFile(FileRedefinable redefinable,
FileRedefinable replacement) {
RedefinitionResult result = RedefinitionResult.ok();
boolean needsRedefine = false;
// Reference equality, since we made a shallow copy of the project
// redefinable!
if (redefinable != replacement) {
fileUpdates.add(redefinable.getFile());
needsRedefine = true;
}
if (redefinable instanceof HTMLRedefinable
&& replacement instanceof HTMLRedefinable) {
if (!((HTMLRedefinable) redefinable)
.areHtmlRangesEqual((HTMLRedefinable) replacement)) {
needsRedefine = true;
result = new RedefinitionResult(
RedefinitionResult.SHOULD_RELOAD
| RedefinitionResult.REDEFINE_OK,
"HTML has changed, must reload");
}
} else if (needsRedefine) {
// TODO: We want hot code replace for this one too!
return RedefinitionResult.unrecoverable("Cannot reload non-HTML/JS files");
}
if (needsRedefine && !vm.canRedefine(redefinable)) {
return RedefinitionResult
.unrecoverable("Hot code replace and file reload not enabled");
}
return result;
}
@Override
public void commit(boolean reloadHint) throws RedefineException {
boolean isOk = RedefinitionResult.isOk(redefineResult)
|| (reloadHint && !redefineResult
.isFlagSet(RedefinitionResult.CANNOT_RELOAD));
if (!isOk) {
throw new RedefineException(redefineResult);
}
boolean doReload = forceReload || reloadHint
|| redefineResult.isFlagSet(RedefinitionResult.SHOULD_RELOAD);
if (doReload) {
vm.reload();
} else {
for (IFile fileUpdate : fileUpdates) {
vm.update(fileUpdate);
}
if (!fileUpdates.isEmpty()) {
vm.refreshBreakpoints();
}
for (FunctionRedefinable functionUpdate : functionUpdates) {
vm.updateFunction(functionUpdate.key(),
functionUpdate.getFunctionSource(true));
}
if (vm.mainThread().isSuspended() && dropToFrame >= 0) {
try {
vm.dropToFrame(dropToFrame);
} catch (DebugException e) {
throw RedefineException.wrap(e);
}
}
}
if (CoreMoSyncPlugin.getDefault().isDebugging()) {
CoreMoSyncPlugin.trace(
"Committed redefinables. Files: {0}. Functions: {1}",
fileUpdates, functionUpdates);
}
}
}