blob: 7347b430cacd5cd1ae4b32ac0ce37094a4127754 [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.DebuggerManagerThreadImpl;
import com.intellij.debugger.engine.events.DebuggerCommandImpl;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.compiler.ex.CompilerPathsEx;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.OrderEnumerator;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class HotSwapManager extends AbstractProjectComponent {
private final Map<DebuggerSession, Long> myTimeStamps = new HashMap<DebuggerSession, Long>();
private static final String CLASS_EXTENSION = ".class";
public HotSwapManager(Project project, DebuggerManagerEx manager) {
super(project);
manager.addDebuggerManagerListener(new DebuggerManagerAdapter() {
public void sessionCreated(DebuggerSession session) {
myTimeStamps.put(session, Long.valueOf(System.currentTimeMillis()));
}
public void sessionRemoved(DebuggerSession session) {
myTimeStamps.remove(session);
}
});
}
@NotNull
public String getComponentName() {
return "HotSwapManager";
}
private long getTimeStamp(DebuggerSession session) {
Long tStamp = myTimeStamps.get(session);
return tStamp != null ? tStamp.longValue() : 0;
}
void setTimeStamp(DebuggerSession session, long tStamp) {
myTimeStamps.put(session, Long.valueOf(tStamp));
}
public Map<String, HotSwapFile> scanForModifiedClasses(final DebuggerSession session, final HotSwapProgress progress, final boolean scanWithVFS) {
DebuggerManagerThreadImpl.assertIsManagerThread();
final long timeStamp = getTimeStamp(session);
final Map<String, HotSwapFile> modifiedClasses = new HashMap<String, HotSwapFile>();
if (scanWithVFS) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
final List<VirtualFile> allDirs = OrderEnumerator.orderEntries(myProject).withoutSdk().withoutLibraries().getPathsList().getRootDirs();
CompilerPathsEx.visitFiles(allDirs, new CompilerPathsEx.FileVisitor() {
protected void acceptDirectory(final VirtualFile file, final String fileRoot, final String filePath) {
if (!progress.isCancelled()) {
progress.setText(DebuggerBundle.message("progress.hotswap.scanning.path", filePath));
super.acceptDirectory(file, fileRoot, filePath);
}
}
protected void acceptFile(VirtualFile file, String fileRoot, String filePath) {
if (progress.isCancelled()) {
return;
}
if (file.getTimeStamp() > timeStamp && StdFileTypes.CLASS.equals(file.getFileType())) {
//noinspection HardCodedStringLiteral
if (SystemInfo.isFileSystemCaseSensitive ? filePath.endsWith(CLASS_EXTENSION) : StringUtil.endsWithIgnoreCase(filePath, CLASS_EXTENSION)) {
progress.setText(DebuggerBundle.message("progress.hotswap.scanning.path", filePath));
//noinspection HardCodedStringLiteral
final String qualifiedName = filePath.substring(fileRoot.length() + 1, filePath.length() - CLASS_EXTENSION.length()).replace('/', '.');
modifiedClasses.put(qualifiedName, new HotSwapFile(new File(filePath)));
}
}
}
});
}
});
}
else {
final List<File> outputRoots = new ArrayList<File>();
ApplicationManager.getApplication().runReadAction(new Runnable() {
public void run() {
final List<VirtualFile> allDirs = OrderEnumerator.orderEntries(myProject).withoutSdk().withoutLibraries().getPathsList().getRootDirs();
for (VirtualFile dir : allDirs) {
outputRoots.add(new File(dir.getPath()));
}
}
});
for (File root : outputRoots) {
final String rootPath = FileUtil.toCanonicalPath(root.getPath());
collectModifiedClasses(root, rootPath, rootPath + "/", modifiedClasses, progress, timeStamp);
}
}
return modifiedClasses;
}
private static boolean collectModifiedClasses(File file, String filePath, String rootPath, Map<String, HotSwapFile> container, HotSwapProgress progress, long timeStamp) {
if (progress.isCancelled()) {
return false;
}
final File[] files = file.listFiles();
if (files != null) {
for (File child : files) {
if (!collectModifiedClasses(child, filePath + "/" + child.getName(), rootPath, container, progress, timeStamp)) {
return false;
}
}
}
else { // not a dir
if (SystemInfo.isFileSystemCaseSensitive? StringUtil.endsWith(filePath, CLASS_EXTENSION) : StringUtil.endsWithIgnoreCase(filePath, CLASS_EXTENSION)) {
if (file.lastModified() > timeStamp) {
progress.setText(DebuggerBundle.message("progress.hotswap.scanning.path", filePath));
//noinspection HardCodedStringLiteral
final String qualifiedName = filePath.substring(rootPath.length(), filePath.length() - CLASS_EXTENSION.length()).replace('/', '.');
container.put(qualifiedName, new HotSwapFile(file));
}
}
}
return true;
}
public static HotSwapManager getInstance(Project project) {
return project.getComponent(HotSwapManager.class);
}
private void reloadClasses(DebuggerSession session, Map<String, HotSwapFile> classesToReload, HotSwapProgress progress) {
final long newSwapTime = System.currentTimeMillis();
new ReloadClassesWorker(session, progress).reloadClasses(classesToReload);
if (progress.isCancelled()) {
session.setModifiedClassesScanRequired(true);
}
else {
setTimeStamp(session, newSwapTime);
}
}
public static Map<DebuggerSession, Map<String, HotSwapFile>> findModifiedClasses(List<DebuggerSession> sessions, Map<String, List<String>> generatedPaths) {
final Map<DebuggerSession, Map<String, HotSwapFile>> result = new java.util.HashMap<DebuggerSession, Map<String, HotSwapFile>>();
List<Pair<DebuggerSession, Long>> sessionWithStamps = new ArrayList<Pair<DebuggerSession, Long>>();
for (DebuggerSession session : sessions) {
sessionWithStamps.add(new Pair<DebuggerSession, Long>(session, getInstance(session.getProject()).getTimeStamp(session)));
}
for (Map.Entry<String, List<String>> entry : generatedPaths.entrySet()) {
final File root = new File(entry.getKey());
for (String relativePath : entry.getValue()) {
if (SystemInfo.isFileSystemCaseSensitive? StringUtil.endsWith(relativePath, CLASS_EXTENSION) : StringUtil.endsWithIgnoreCase(relativePath, CLASS_EXTENSION)) {
final String qualifiedName = relativePath.substring(0, relativePath.length() - CLASS_EXTENSION.length()).replace('/', '.');
final HotSwapFile hotswapFile = new HotSwapFile(new File(root, relativePath));
final long fileStamp = hotswapFile.file.lastModified();
for (Pair<DebuggerSession, Long> pair : sessionWithStamps) {
final DebuggerSession session = pair.first;
if (fileStamp > pair.second) {
Map<String, HotSwapFile> container = result.get(session);
if (container == null) {
container = new java.util.HashMap<String, HotSwapFile>();
result.put(session, container);
}
container.put(qualifiedName, hotswapFile);
}
}
}
}
}
return result;
}
public static Map<DebuggerSession, Map<String, HotSwapFile>> scanForModifiedClasses(final List<DebuggerSession> sessions, final HotSwapProgress swapProgress, final boolean scanWithVFS) {
final Map<DebuggerSession, Map<String, HotSwapFile>> modifiedClasses = new HashMap<DebuggerSession, Map<String, HotSwapFile>>();
final MultiProcessCommand scanClassesCommand = new MultiProcessCommand();
swapProgress.setCancelWorker(new Runnable() {
public void run() {
scanClassesCommand.cancel();
}
});
for (final DebuggerSession debuggerSession : sessions) {
if (debuggerSession.isAttached()) {
scanClassesCommand.addCommand(debuggerSession.getProcess(), new DebuggerCommandImpl() {
protected void action() throws Exception {
swapProgress.setDebuggerSession(debuggerSession);
final Map<String, HotSwapFile> sessionClasses = getInstance(swapProgress.getProject()).scanForModifiedClasses(debuggerSession, swapProgress, scanWithVFS);
if (!sessionClasses.isEmpty()) {
modifiedClasses.put(debuggerSession, sessionClasses);
}
}
});
}
}
swapProgress.setTitle(DebuggerBundle.message("progress.hotswap.scanning.classes"));
scanClassesCommand.run();
if (swapProgress.isCancelled()) {
for (DebuggerSession session : sessions) {
session.setModifiedClassesScanRequired(true);
}
return new HashMap<DebuggerSession, Map<String, HotSwapFile>>();
}
return modifiedClasses;
}
public static void reloadModifiedClasses(final Map<DebuggerSession, Map<String, HotSwapFile>> modifiedClasses, final HotSwapProgress reloadClassesProgress) {
final MultiProcessCommand reloadClassesCommand = new MultiProcessCommand();
reloadClassesProgress.setCancelWorker(new Runnable() {
public void run() {
reloadClassesCommand.cancel();
}
});
for (final DebuggerSession debuggerSession : modifiedClasses.keySet()) {
reloadClassesCommand.addCommand(debuggerSession.getProcess(), new DebuggerCommandImpl() {
protected void action() throws Exception {
reloadClassesProgress.setDebuggerSession(debuggerSession);
getInstance(reloadClassesProgress.getProject()).reloadClasses(
debuggerSession, modifiedClasses.get(debuggerSession), reloadClassesProgress
);
}
protected void commandCancelled() {
debuggerSession.setModifiedClassesScanRequired(true);
}
});
}
reloadClassesProgress.setTitle(DebuggerBundle.message("progress.hotswap.reloading"));
reloadClassesCommand.run();
}
}