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:
Jinbo Wang 2018-08-28 11:16:09 +08:00 коммит произвёл GitHub
Родитель e9ed6a18b5
Коммит 7328c67c02
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 166 добавлений и 8 удалений

Просмотреть файл

@ -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;
}
}
}