Repository navigation
Validate mainClass and projectName configs #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
67fb7fb
8f1d614
4a31311
15d73d0
e78cc20
6f976b4
3e95a28
aae1ef2
490639f
0216457
fec3727
dc95c6d
1cf0799
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 @@ | |
|
|
||
| public class ResolveMainClassHandler { | ||
| private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME); | ||
| private static final String CLASSNAME_REGX = "([$\\w]+\\.)*[$\\w]+"; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think a better way is to validate the main Class in js side, the java side only exposure ways of querying java projects and main classes.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't want to separate the validation logic to two parts, that's hard for reading. JS side is responsible for UI related logic, and JAVA side is for computing the result. |
||
|
|
||
| /** | ||
| * 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 @@ private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) throws | |
| 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 void acceptSearchMatch(SearchMatch match) { | |
| } 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 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 { | ||
| private class ResolutionItem implements Comparable<ResolutionItem> { | ||
| private String mainClass; | ||
| private String projectName; | ||
| private String filePath; | ||
|
|
@@ -146,5 +249,53 @@ public boolean equals(Object o) { | |
| 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; | ||
| } | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about make class ResolveMainClassHandler a utility class(with a changed name) and not new ResolveMainClassHandler every time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i guess the original habit is inherited from jdt.ls handler implementation. If we just refactor this one, it will be a little weird, because ResolveClasspathsHandler has similar issue. So i prefer to keep the current convention.