Validate mainClass and projectName configs (#205)
* Validate mainClass and projectName configs Signed-off-by: Jinbo Wang <jinbwan@microsoft.com> * Update ValidationResult scheme * Revert 'errorMessage' to a neutral name 'message' for ValidationResult * Only report not unique validation error when projectName is not specified * Ignore mainClass validation when the user specified the classpaths manually * Use Object.equals to compare two strings
This commit is contained in:
Родитель
e9ed6a18b5
Коммит
7328c67c02
|
@ -9,6 +9,9 @@
|
|||
<command id="vscode.java.buildWorkspace"/>
|
||||
<command id="vscode.java.fetchUsageData"/>
|
||||
<command id="vscode.java.updateDebugSettings"/>
|
||||
<command
|
||||
id="vscode.java.validateLaunchConfig">
|
||||
</command>
|
||||
</delegateCommandHandler>
|
||||
</extension>
|
||||
</plugin>
|
||||
|
|
|
@ -32,6 +32,8 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler
|
|||
|
||||
public static String UPDATE_DEBUG_SETTINGS = "vscode.java.updateDebugSettings";
|
||||
|
||||
public static String VALIDATE_LAUNCHCONFIG = "vscode.java.validateLaunchConfig";
|
||||
|
||||
@Override
|
||||
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor progress) throws Exception {
|
||||
if (DEBUG_STARTSESSION.equals(commandId)) {
|
||||
|
@ -50,6 +52,8 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler
|
|||
return UsageDataStore.getInstance().fetchAll();
|
||||
} else if (UPDATE_DEBUG_SETTINGS.equals(commandId)) {
|
||||
return DebugSettingUtils.configDebugSettings(arguments);
|
||||
} else if (VALIDATE_LAUNCHCONFIG.equals(commandId)) {
|
||||
return new ResolveMainClassHandler().validateLaunchConfig(arguments);
|
||||
}
|
||||
|
||||
throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId));
|
||||
|
|
|
@ -79,7 +79,7 @@ public class ResolveClasspathsHandler {
|
|||
* @throws CoreException
|
||||
* CoreException
|
||||
*/
|
||||
private static List<IJavaProject> getJavaProjectFromType(String fullyQualifiedTypeName) throws CoreException {
|
||||
public static List<IJavaProject> getJavaProjectFromType(String fullyQualifiedTypeName) throws CoreException {
|
||||
String[] splitItems = fullyQualifiedTypeName.split("/");
|
||||
// If the main class name contains the module name, should trim the module info.
|
||||
if (splitItems.length == 2) {
|
||||
|
|
|
@ -12,12 +12,16 @@
|
|||
package com.microsoft.java.debug.plugin.internal;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Function;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.core.resources.IFile;
|
||||
import org.eclipse.core.resources.IProject;
|
||||
import org.eclipse.core.resources.IResource;
|
||||
|
@ -40,19 +44,35 @@ import com.microsoft.java.debug.core.Configuration;
|
|||
|
||||
public class ResolveMainClassHandler {
|
||||
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
|
||||
private static final String CLASSNAME_REGX = "([$\\w]+\\.)*[$\\w]+";
|
||||
|
||||
/**
|
||||
* resolve main class and project name.
|
||||
* @return an array of main class and project name
|
||||
* @throws CoreException when there are errors when resolving main class.
|
||||
*/
|
||||
public Object resolveMainClass(List<Object> arguments) throws CoreException {
|
||||
public Object resolveMainClass(List<Object> arguments) {
|
||||
return resolveMainClassCore(arguments);
|
||||
}
|
||||
|
||||
private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) throws CoreException {
|
||||
/**
|
||||
* Validate whether the mainClass and projectName is correctly configured or not. If not, report the validation error and provide the quick fix proposal.
|
||||
*
|
||||
* @param arguments the mainClass and projectName configs.
|
||||
* @return the validation response.
|
||||
* @throws Exception when there are any errors during validating the mainClass and projectName.
|
||||
*/
|
||||
public Object validateLaunchConfig(List<Object> arguments) throws Exception {
|
||||
try {
|
||||
return validateLaunchConfigCore(arguments);
|
||||
} catch (CoreException ex) {
|
||||
logger.log(Level.SEVERE, "Failed to validate launch config: " + ex.getMessage(), ex);
|
||||
throw new Exception("Failed to validate launch config: " + ex.getMessage(), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) {
|
||||
IPath rootPath = null;
|
||||
if (arguments != null && arguments.size() > 0) {
|
||||
if (arguments != null && arguments.size() > 0 && arguments.get(0) != null) {
|
||||
rootPath = ResourceUtils.filePathFromURI((String) arguments.get(0));
|
||||
}
|
||||
final ArrayList<IPath> targetProjectPath = new ArrayList<>();
|
||||
|
@ -62,7 +82,7 @@ public class ResolveMainClassHandler {
|
|||
IJavaSearchScope searchScope = SearchEngine.createWorkspaceScope();
|
||||
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
|
||||
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
|
||||
ArrayList<ResolutionItem> res = new ArrayList<>();
|
||||
final List<ResolutionItem> res = new ArrayList<>();
|
||||
SearchRequestor requestor = new SearchRequestor() {
|
||||
@Override
|
||||
public void acceptSearchMatch(SearchMatch match) {
|
||||
|
@ -114,10 +134,93 @@ public class ResolveMainClassHandler {
|
|||
} catch (Exception e) {
|
||||
logger.log(Level.SEVERE, String.format("Searching the main class failure: %s", e.toString()), e);
|
||||
}
|
||||
return res.stream().distinct().collect(Collectors.toList());
|
||||
|
||||
List<ResolutionItem> resolutions = res.stream().distinct().collect(Collectors.toList());
|
||||
Collections.sort(resolutions);
|
||||
return resolutions;
|
||||
}
|
||||
|
||||
private class ResolutionItem {
|
||||
private ValidationResponse validateLaunchConfigCore(List<Object> arguments) throws CoreException {
|
||||
ValidationResponse response = new ValidationResponse();
|
||||
|
||||
String mainClass = null;
|
||||
String projectName = null;
|
||||
boolean containsExternalClasspaths = false;
|
||||
if (arguments != null) {
|
||||
if (arguments.size() > 1) {
|
||||
mainClass = (String) arguments.get(1);
|
||||
}
|
||||
if (arguments.size() > 2) {
|
||||
projectName = (String) arguments.get(2);
|
||||
}
|
||||
if (arguments.size() > 3) {
|
||||
containsExternalClasspaths = (boolean) arguments.get(3);
|
||||
}
|
||||
}
|
||||
|
||||
response.mainClass = validateMainClass(mainClass, projectName, containsExternalClasspaths);
|
||||
response.projectName = validateProjectName(mainClass, projectName);
|
||||
|
||||
if (!response.mainClass.isValid || !response.projectName.isValid) {
|
||||
response.proposals = computeProposals(arguments, mainClass, projectName);
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
private ValidationResult validateMainClass(final String mainClass, final String projectName, boolean containsExternalClasspaths) throws CoreException {
|
||||
if (StringUtils.isEmpty(mainClass)) {
|
||||
return new ValidationResult(true);
|
||||
} else if (!mainClass.matches(CLASSNAME_REGX)) {
|
||||
return new ValidationResult(false, String.format("ConfigError: '%s' is not a valid class name.", mainClass));
|
||||
}
|
||||
|
||||
if (!containsExternalClasspaths && StringUtils.isEmpty(projectName)) {
|
||||
List<IJavaProject> javaProjects = searchClassInProjectClasspaths(mainClass);
|
||||
if (javaProjects.size() == 0) {
|
||||
return new ValidationResult(false, String.format("ConfigError: Main class '%s' doesn't exist in the workspace.", mainClass));
|
||||
}
|
||||
if (javaProjects.size() > 1) {
|
||||
return new ValidationResult(false, String.format("ConfigError: Main class '%s' isn't unique in the workspace.", mainClass));
|
||||
}
|
||||
}
|
||||
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
private List<IJavaProject> searchClassInProjectClasspaths(String fullyQualifiedClassName) throws CoreException {
|
||||
return ResolveClasspathsHandler.getJavaProjectFromType(fullyQualifiedClassName);
|
||||
}
|
||||
|
||||
private ValidationResult validateProjectName(final String mainClass, final String projectName) {
|
||||
if (StringUtils.isEmpty(projectName)) {
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
if (JdtUtils.getJavaProject(projectName) == null) {
|
||||
return new ValidationResult(false, String.format("ConfigError: The project '%s' is not a valid java project.", projectName));
|
||||
}
|
||||
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
private List<ResolutionItem> computeProposals(List<Object> arguments, final String mainClass, final String projectName) {
|
||||
List<ResolutionItem> proposals = resolveMainClassCore(arguments);
|
||||
|
||||
Collections.sort(proposals, new ProposalItemComparator((ResolutionItem item) -> {
|
||||
if (Objects.equals(item.mainClass, mainClass)) {
|
||||
return 1;
|
||||
} else if (Objects.equals(item.projectName, projectName)) {
|
||||
return 2;
|
||||
}
|
||||
|
||||
return 999;
|
||||
}));
|
||||
|
||||
return proposals;
|
||||
}
|
||||
|
||||
private class ResolutionItem implements Comparable<ResolutionItem> {
|
||||
private String mainClass;
|
||||
private String projectName;
|
||||
private String filePath;
|
||||
|
@ -146,5 +249,53 @@ public class ResolveMainClassHandler {
|
|||
public int hashCode() {
|
||||
return Objects.hash(mainClass, projectName, filePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ResolutionItem o) {
|
||||
if (isDefaultProject(this.projectName) && !isDefaultProject(o.projectName)) {
|
||||
return 1;
|
||||
} else if (!isDefaultProject(this.projectName) && isDefaultProject(o.projectName)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return (this.projectName + "|" + this.mainClass).compareTo(o.projectName + "|" + o.mainClass);
|
||||
}
|
||||
|
||||
private boolean isDefaultProject(String projectName) {
|
||||
return StringUtils.isEmpty(projectName);
|
||||
}
|
||||
}
|
||||
|
||||
class ProposalItemComparator implements Comparator<ResolutionItem> {
|
||||
Function<ResolutionItem, Integer> getRank;
|
||||
|
||||
ProposalItemComparator(Function<ResolutionItem, Integer> getRank) {
|
||||
this.getRank = getRank;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compare(ResolutionItem o1, ResolutionItem o2) {
|
||||
return getRank.apply(o1) - getRank.apply(o2);
|
||||
}
|
||||
}
|
||||
|
||||
class ValidationResponse {
|
||||
ValidationResult mainClass;
|
||||
ValidationResult projectName;
|
||||
List<ResolutionItem> proposals;
|
||||
}
|
||||
|
||||
class ValidationResult {
|
||||
boolean isValid;
|
||||
String message;
|
||||
|
||||
ValidationResult(boolean isValid) {
|
||||
this.isValid = isValid;
|
||||
}
|
||||
|
||||
ValidationResult(boolean isValid, String message) {
|
||||
this.isValid = isValid;
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче