package org.netbeans.gradle.project.tasks;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.netbeans.api.debugger.DebuggerEngine;
import org.netbeans.api.debugger.DebuggerManager;
import org.netbeans.api.debugger.jpda.JPDADebugger;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.SourceForBinaryQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.gradle.project.NbGradleProject;
import org.netbeans.gradle.project.java.query.GradleClassPathProvider;
import org.netbeans.spi.debugger.jpda.EditorContext;
import org.netbeans.spi.java.classpath.support.ClassPathSupport;
import org.netbeans.spi.project.SingleMethod;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
import org.openide.util.Lookup;
import org.openide.windows.OutputWriter;
// This class is mostly a copy-paste from the Maven plugin:
// - org.netbeans.modules.maven.debug.DebuggerChecker
// - org.netbeans.modules.maven.execute.DefaultReplaceTokenProvider
public final class DebugUtils {
public static String getActiveClassName(Project project, Lookup lookup) {
FileObject[] filesOnLookup = extractFileObjectsfromLookup(lookup);
SourceGroup group = findGroup(ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA), filesOnLookup);
FileObject file = null;
for (FileObject currentFile : filesOnLookup) {
if (!currentFile.isFolder()) {
file = currentFile;
break;
}
}
StringBuilder className = new StringBuilder();
if (group != null && file != null) {
if (filesOnLookup.length < 1) {
return "";
}
String relP = FileUtil.getRelativePath(group.getRootFolder(), file.getParent());
if (relP == null) {
return "";
}
if (!relP.isEmpty()) {
className.append(relP.replace('/', '.')).append('.');
}
className.append(file.getName());
}
return className.toString();
}
private static FileObject[] extractFileObjectsfromLookup(Lookup lookup) {
List<FileObject> files = new ArrayList<>(lookup.lookupAll(FileObject.class));
if (files.isEmpty()) { // fallback to old nodes
for (DataObject d : lookup.lookupAll(DataObject.class)) {
files.add(d.getPrimaryFile());
}
}
Collection<? extends SingleMethod> methods = lookup.lookupAll(SingleMethod.class);
if (methods.size() == 1) {
SingleMethod method = methods.iterator().next();
files.add(method.getFile());
}
return files.toArray(new FileObject[files.size()]);
}
/**
* Finds the one source group, if any, which contains all of the listed
* files.
*/
private static SourceGroup findGroup(SourceGroup[] groups, FileObject[] files) {
SourceGroup selected = null;
for (FileObject file : files) {
for (SourceGroup group : groups) {
FileObject root = group.getRootFolder();
if (file == root || FileUtil.isParentOf(root, file)) { // or group.contains(file)?
if (selected == null) {
selected = group;
}
else if (selected != group) {
return null;
}
}
}
}
return selected;
}
public static void applyChanges(Project project, OutputWriter logger, String classname) {
// check debugger state
DebuggerEngine debuggerEngine = DebuggerManager.getDebuggerManager().
getCurrentEngine();
if (debuggerEngine == null) {
logger.println("NetBeans: No debugging sessions was found.");
return;
}
JPDADebugger debugger = debuggerEngine.lookupFirst(null, JPDADebugger.class);
if (debugger == null) {
logger.println("NetBeans: Current debugger is not JPDA one.");
return;
}
if (!debugger.canFixClasses()) {
logger.println("NetBeans: The debugger does not support Fix action.");
return;
}
if (debugger.getState() == JPDADebugger.STATE_DISCONNECTED) {
logger.println("NetBeans: The debugger is not running");
return;
}
Map<String, byte[]> map = new HashMap<>();
EditorContext editorContext = DebuggerManager.
getDebuggerManager().lookupFirst(null, EditorContext.class);
String clazz = classname.replace('.', '/') + ".class"; //NOI18N
GradleClassPathProvider prv = project.getLookup().lookup(GradleClassPathProvider.class);
FileObject fo2 = prv.getBuildOutputClassPaths().findResource(clazz);
if (fo2 != null) {
try {
String basename = fo2.getName();
for (FileObject classfile : fo2.getParent().getChildren()) {
String basename2 = classfile.getName();
if (/*#220338*/!"class".equals(classfile.getExt()) || (!basename2.equals(basename) && !basename2.startsWith(basename + '$'))) {
continue;
}
String url = classToSourceURL(classfile, logger);
if (url != null) {
editorContext.updateTimeStamp(debugger, url);
}
map.put(classname + basename2.substring(basename.length()), classfile.asBytes());
}
} catch (IOException ex) {
NbGradleProject gradleProject = project.getLookup().lookup(NbGradleProject.class);
if (gradleProject != null) {
gradleProject.displayError("Unexpected error.", ex);
}
else {
throw new IllegalStateException("Unexpected error in an unexpected project type.", ex);
}
}
}
logger.println("NetBeans: classes to reload: " + map.keySet());
if (map.isEmpty()) {
logger.println("NetBeans: No class to reload");
return;
}
String error = null;
try {
debugger.fixClasses(map);
} catch (UnsupportedOperationException uoex) {
error = "The virtual machine does not support this operation: " + uoex.getLocalizedMessage();
} catch (NoClassDefFoundError ncdfex) {
error = "The bytes don't correspond to the class type (the names don't match): " + ncdfex.getLocalizedMessage();
} catch (VerifyError ver) {
error = "A \"verifier\" detects that a class, though well formed, contains an internal inconsistency or security problem: " + ver.getLocalizedMessage();
} catch (UnsupportedClassVersionError ucver) {
error = "The major and minor version numbers in bytes are not supported by the VM. " + ucver.getLocalizedMessage();
} catch (ClassFormatError cfer) {
error = "The bytes do not represent a valid class. " + cfer.getLocalizedMessage();
} catch (ClassCircularityError ccer) {
error = "A circularity has been detected while initializing a class: " + ccer.getLocalizedMessage();
}
if (error != null) {
logger.println("NetBeans:" + error);
}
}
private static String classToSourceURL(FileObject fo, OutputWriter logger) {
ClassPath cp = ClassPath.getClassPath(fo, ClassPath.EXECUTE);
if (cp == null) {
return null;
}
FileObject root = cp.findOwnerRoot(fo);
String resourceName = cp.getResourceName(fo, '/', false);
if (resourceName == null || root == null) {
logger.println("Can not find classpath resource for " + fo + ", skipping...");
return null;
}
int i = resourceName.indexOf('$');
if (i > 0) {
resourceName = resourceName.substring(0, i);
}
FileObject[] sRoots = SourceForBinaryQuery.findSourceRoots(root.toURL()).getRoots();
ClassPath sourcePath = ClassPathSupport.createClassPath(sRoots);
FileObject rfo = sourcePath.findResource(resourceName + ".java");
if (rfo == null) {
return null;
}
return rfo.toURL().toExternalForm();
}
private DebugUtils() {
throw new AssertionError();
}
}