blob: 796aab70176538b4f16a9cdfe994e411c25b5e38 [file] [log] [blame]
/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.debugger.impl;
import com.intellij.debugger.DebuggerBundle;
import com.intellij.debugger.DebuggerManagerEx;
import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.DebuggerManagerThreadImpl;
import com.intellij.debugger.jdi.VirtualMachineProxyImpl;
import com.intellij.debugger.ui.breakpoints.BreakpointManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.util.StringBuilderSpinAllocator;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.ui.MessageCategory;
import com.sun.jdi.ReferenceType;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.io.File;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author lex
*/
class ReloadClassesWorker {
private static final Logger LOG = Logger.getInstance("#com.intellij.debugger.impl.ReloadClassesWorker");
private final DebuggerSession myDebuggerSession;
private final HotSwapProgress myProgress;
public ReloadClassesWorker(DebuggerSession session, HotSwapProgress progress) {
myDebuggerSession = session;
myProgress = progress;
}
private DebugProcessImpl getDebugProcess() {
return myDebuggerSession.getProcess();
}
private void processException(Throwable e) {
if (e.getMessage() != null) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, e.getMessage());
}
if (e instanceof ProcessCanceledException) {
myProgress.addMessage(myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("error.operation.canceled"));
return;
}
if (e instanceof UnsupportedOperationException) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.operation.not.supported.by.vm"));
}
else if (e instanceof NoClassDefFoundError) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.def.not.found", e.getLocalizedMessage()));
}
else if (e instanceof VerifyError) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.verification.error", e.getLocalizedMessage()));
}
else if (e instanceof UnsupportedClassVersionError) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.unsupported.class.version", e.getLocalizedMessage()));
}
else if (e instanceof ClassFormatError) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.format.error", e.getLocalizedMessage()));
}
else if (e instanceof ClassCircularityError) {
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, DebuggerBundle.message("error.class.circularity.error", e.getLocalizedMessage()));
}
else {
myProgress.addMessage(
myDebuggerSession, MessageCategory.ERROR,
DebuggerBundle.message("error.exception.while.reloading", e.getClass().getName(), e.getLocalizedMessage())
);
}
}
public void reloadClasses(final Map<String, HotSwapFile> modifiedClasses) {
DebuggerManagerThreadImpl.assertIsManagerThread();
if(modifiedClasses == null || modifiedClasses.size() == 0) {
myProgress.addMessage(myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("status.hotswap.loaded.classes.up.to.date"));
return;
}
final DebugProcessImpl debugProcess = getDebugProcess();
final VirtualMachineProxyImpl virtualMachineProxy = debugProcess.getVirtualMachineProxy();
if(virtualMachineProxy == null) {
return;
}
final Project project = debugProcess.getProject();
final BreakpointManager breakpointManager = (DebuggerManagerEx.getInstanceEx(project)).getBreakpointManager();
breakpointManager.disableBreakpoints(debugProcess);
//virtualMachineProxy.suspend();
try {
RedefineProcessor redefineProcessor = new RedefineProcessor(virtualMachineProxy);
int processedEntriesCount = 0;
for (final Map.Entry<String, HotSwapFile> entry : modifiedClasses.entrySet()) {
if (redefineProcessor.getProcessedClassesCount() == 0 && myProgress.isCancelled()) {
// once at least one class has been actually reloaded, do not interrupt the whole process
break;
}
processedEntriesCount++;
final String qualifiedName = entry.getKey();
if (qualifiedName != null) {
myProgress.setText(qualifiedName);
myProgress.setFraction(processedEntriesCount / (double)modifiedClasses.size());
}
try {
redefineProcessor.processClass(qualifiedName, entry.getValue().file);
}
catch (IOException e) {
reportProblem(qualifiedName, e);
}
}
if (redefineProcessor.getProcessedClassesCount() == 0 && myProgress.isCancelled()) {
// once at least one class has been actually reloaded, do not interrupt the whole process
return;
}
redefineProcessor.processPending();
myProgress.setFraction(1);
final int partiallyRedefinedClassesCount = redefineProcessor.getPartiallyRedefinedClassesCount();
if (partiallyRedefinedClassesCount == 0) {
myProgress.addMessage(
myDebuggerSession, MessageCategory.INFORMATION, DebuggerBundle.message("status.classes.reloaded", redefineProcessor.getProcessedClassesCount())
);
}
else {
final String message = DebuggerBundle.message(
"status.classes.not.all.versions.reloaded", partiallyRedefinedClassesCount, redefineProcessor.getProcessedClassesCount()
);
myProgress.addMessage(myDebuggerSession, MessageCategory.WARNING, message);
}
if (LOG.isDebugEnabled()) {
LOG.debug("classes reloaded");
}
}
catch (Throwable e) {
processException(e);
}
final Semaphore waitSemaphore = new Semaphore();
waitSemaphore.down();
//noinspection SSBasedInspection
SwingUtilities.invokeLater(new Runnable() {
public void run() {
try {
if (!project.isDisposed()) {
final BreakpointManager breakpointManager = (DebuggerManagerEx.getInstanceEx(project)).getBreakpointManager();
breakpointManager.reloadBreakpoints();
debugProcess.getRequestsManager().clearWarnings();
if (LOG.isDebugEnabled()) {
LOG.debug("requests updated");
LOG.debug("time stamp set");
}
myDebuggerSession.refresh(false);
}
}
catch (Throwable e) {
LOG.error(e);
}
finally {
waitSemaphore.up();
}
}
});
waitSemaphore.waitFor();
if (!project.isDisposed()) {
try {
breakpointManager.enableBreakpoints(debugProcess);
}
catch (Exception e) {
processException(e);
}
}
}
private void reportProblem(final String qualifiedName, @Nullable Exception ex) {
String reason = null;
if (ex != null) {
reason = ex.getLocalizedMessage();
}
if (reason == null || reason.length() == 0) {
reason = DebuggerBundle.message("error.io.error");
}
final StringBuilder buf = StringBuilderSpinAllocator.alloc();
try {
buf.append(qualifiedName).append(" : ").append(reason);
myProgress.addMessage(myDebuggerSession, MessageCategory.ERROR, buf.toString());
}
finally {
StringBuilderSpinAllocator.dispose(buf);
}
}
private static class RedefineProcessor {
/**
* number of classes that will be reloaded in one go.
* Such restriction is needed to deal with big number of classes being reloaded
*/
private static final int CLASSES_CHUNK_SIZE = 100;
private final VirtualMachineProxyImpl myVirtualMachineProxy;
private final Map<ReferenceType, byte[]> myRedefineMap = new HashMap<ReferenceType, byte[]>();
private int myProcessedClassesCount;
private int myPartiallyRedefinedClassesCount;
public RedefineProcessor(VirtualMachineProxyImpl virtualMachineProxy) {
myVirtualMachineProxy = virtualMachineProxy;
}
public void processClass(String qualifiedName, File file) throws Throwable {
final List<ReferenceType> vmClasses = myVirtualMachineProxy.classesByName(qualifiedName);
if (vmClasses.isEmpty()) {
return;
}
final byte[] content = FileUtil.loadFileBytes(file);
if (vmClasses.size() == 1) {
myRedefineMap.put(vmClasses.get(0), content);
if (myRedefineMap.size() >= CLASSES_CHUNK_SIZE) {
processChunk();
}
return;
}
int redefinedVersionsCount = 0;
Throwable error = null;
for (ReferenceType vmClass : vmClasses) {
try {
myVirtualMachineProxy.redefineClasses(Collections.singletonMap(vmClass, content));
redefinedVersionsCount++;
}
catch (Throwable t) {
error = t;
}
}
if (redefinedVersionsCount == 0) {
throw error;
}
if (redefinedVersionsCount < vmClasses.size()) {
myPartiallyRedefinedClassesCount++;
}
myProcessedClassesCount++;
}
private void processChunk() throws Throwable {
// reload this portion of classes and clear the map to free memory
try {
myVirtualMachineProxy.redefineClasses(myRedefineMap);
myProcessedClassesCount += myRedefineMap.size();
}
finally {
myRedefineMap.clear();
}
}
public void processPending() throws Throwable {
if (myRedefineMap.size() > 0) {
processChunk();
}
}
public int getProcessedClassesCount() {
return myProcessedClassesCount;
}
public int getPartiallyRedefinedClassesCount() {
return myPartiallyRedefinedClassesCount;
}
}
}