Skip to content

Commit 7328c67

Browse files
Validate mainClass and projectName configs (#205)
* Validate mainClass and projectName configs Signed-off-by: Jinbo Wang <[email protected]> * 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
1 parent e9ed6a1 commit 7328c67

File tree

4 files changed

+166
-8
lines changed

4 files changed

+166
-8
lines changed

com.microsoft.java.debug.plugin/plugin.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@
99
<command id="vscode.java.buildWorkspace"/>
1010
<command id="vscode.java.fetchUsageData"/>
1111
<command id="vscode.java.updateDebugSettings"/>
12+
<command
13+
id="vscode.java.validateLaunchConfig">
14+
</command>
1215
</delegateCommandHandler>
1316
</extension>
1417
</plugin>

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/JavaDebugDelegateCommandHandler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ public class JavaDebugDelegateCommandHandler implements IDelegateCommandHandler
3232

3333
public static String UPDATE_DEBUG_SETTINGS = "vscode.java.updateDebugSettings";
3434

35+
public static String VALIDATE_LAUNCHCONFIG = "vscode.java.validateLaunchConfig";
36+
3537
@Override
3638
public Object executeCommand(String commandId, List<Object> arguments, IProgressMonitor progress) throws Exception {
3739
if (DEBUG_STARTSESSION.equals(commandId)) {
@@ -50,6 +52,8 @@ public Object executeCommand(String commandId, List<Object> arguments, IProgress
5052
return UsageDataStore.getInstance().fetchAll();
5153
} else if (UPDATE_DEBUG_SETTINGS.equals(commandId)) {
5254
return DebugSettingUtils.configDebugSettings(arguments);
55+
} else if (VALIDATE_LAUNCHCONFIG.equals(commandId)) {
56+
return new ResolveMainClassHandler().validateLaunchConfig(arguments);
5357
}
5458

5559
throw new UnsupportedOperationException(String.format("Java debug plugin doesn't support the command '%s'.", commandId));

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveClasspathsHandler.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ private static IJavaProject getJavaProjectFromName(String projectName) throws Co
7979
* @throws CoreException
8080
* CoreException
8181
*/
82-
private static List<IJavaProject> getJavaProjectFromType(String fullyQualifiedTypeName) throws CoreException {
82+
public static List<IJavaProject> getJavaProjectFromType(String fullyQualifiedTypeName) throws CoreException {
8383
String[] splitItems = fullyQualifiedTypeName.split("/");
8484
// If the main class name contains the module name, should trim the module info.
8585
if (splitItems.length == 2) {

com.microsoft.java.debug.plugin/src/main/java/com/microsoft/java/debug/plugin/internal/ResolveMainClassHandler.java

Lines changed: 158 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,16 @@
1212
package com.microsoft.java.debug.plugin.internal;
1313

1414
import java.util.ArrayList;
15+
import java.util.Collections;
16+
import java.util.Comparator;
1517
import java.util.List;
1618
import java.util.Objects;
19+
import java.util.function.Function;
1720
import java.util.logging.Level;
1821
import java.util.logging.Logger;
1922
import java.util.stream.Collectors;
2023

24+
import org.apache.commons.lang3.StringUtils;
2125
import org.eclipse.core.resources.IFile;
2226
import org.eclipse.core.resources.IProject;
2327
import org.eclipse.core.resources.IResource;
@@ -40,19 +44,35 @@
4044

4145
public class ResolveMainClassHandler {
4246
private static final Logger logger = Logger.getLogger(Configuration.LOGGER_NAME);
47+
private static final String CLASSNAME_REGX = "([$\\w]+\\.)*[$\\w]+";
4348

4449
/**
4550
* resolve main class and project name.
4651
* @return an array of main class and project name
47-
* @throws CoreException when there are errors when resolving main class.
4852
*/
49-
public Object resolveMainClass(List<Object> arguments) throws CoreException {
53+
public Object resolveMainClass(List<Object> arguments) {
5054
return resolveMainClassCore(arguments);
5155
}
5256

53-
private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) throws CoreException {
57+
/**
58+
* Validate whether the mainClass and projectName is correctly configured or not. If not, report the validation error and provide the quick fix proposal.
59+
*
60+
* @param arguments the mainClass and projectName configs.
61+
* @return the validation response.
62+
* @throws Exception when there are any errors during validating the mainClass and projectName.
63+
*/
64+
public Object validateLaunchConfig(List<Object> arguments) throws Exception {
65+
try {
66+
return validateLaunchConfigCore(arguments);
67+
} catch (CoreException ex) {
68+
logger.log(Level.SEVERE, "Failed to validate launch config: " + ex.getMessage(), ex);
69+
throw new Exception("Failed to validate launch config: " + ex.getMessage(), ex);
70+
}
71+
}
72+
73+
private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) {
5474
IPath rootPath = null;
55-
if (arguments != null && arguments.size() > 0) {
75+
if (arguments != null && arguments.size() > 0 && arguments.get(0) != null) {
5676
rootPath = ResourceUtils.filePathFromURI((String) arguments.get(0));
5777
}
5878
final ArrayList<IPath> targetProjectPath = new ArrayList<>();
@@ -62,7 +82,7 @@ private List<ResolutionItem> resolveMainClassCore(List<Object> arguments) throws
6282
IJavaSearchScope searchScope = SearchEngine.createWorkspaceScope();
6383
SearchPattern pattern = SearchPattern.createPattern("main(String[]) void", IJavaSearchConstants.METHOD,
6484
IJavaSearchConstants.DECLARATIONS, SearchPattern.R_CASE_SENSITIVE | SearchPattern.R_EXACT_MATCH);
65-
ArrayList<ResolutionItem> res = new ArrayList<>();
85+
final List<ResolutionItem> res = new ArrayList<>();
6686
SearchRequestor requestor = new SearchRequestor() {
6787
@Override
6888
public void acceptSearchMatch(SearchMatch match) {
@@ -114,10 +134,93 @@ public void acceptSearchMatch(SearchMatch match) {
114134
} catch (Exception e) {
115135
logger.log(Level.SEVERE, String.format("Searching the main class failure: %s", e.toString()), e);
116136
}
117-
return res.stream().distinct().collect(Collectors.toList());
137+
138+
List<ResolutionItem> resolutions = res.stream().distinct().collect(Collectors.toList());
139+
Collections.sort(resolutions);
140+
return resolutions;
141+
}
142+
143+
private ValidationResponse validateLaunchConfigCore(List<Object> arguments) throws CoreException {
144+
ValidationResponse response = new ValidationResponse();
145+
146+
String mainClass = null;
147+
String projectName = null;
148+
boolean containsExternalClasspaths = false;
149+
if (arguments != null) {
150+
if (arguments.size() > 1) {
151+
mainClass = (String) arguments.get(1);
152+
}
153+
if (arguments.size() > 2) {
154+
projectName = (String) arguments.get(2);
155+
}
156+
if (arguments.size() > 3) {
157+
containsExternalClasspaths = (boolean) arguments.get(3);
158+
}
159+
}
160+
161+
response.mainClass = validateMainClass(mainClass, projectName, containsExternalClasspaths);
162+
response.projectName = validateProjectName(mainClass, projectName);
163+
164+
if (!response.mainClass.isValid || !response.projectName.isValid) {
165+
response.proposals = computeProposals(arguments, mainClass, projectName);
166+
}
167+
168+
return response;
169+
}
170+
171+
private ValidationResult validateMainClass(final String mainClass, final String projectName, boolean containsExternalClasspaths) throws CoreException {
172+
if (StringUtils.isEmpty(mainClass)) {
173+
return new ValidationResult(true);
174+
} else if (!mainClass.matches(CLASSNAME_REGX)) {
175+
return new ValidationResult(false, String.format("ConfigError: '%s' is not a valid class name.", mainClass));
176+
}
177+
178+
if (!containsExternalClasspaths && StringUtils.isEmpty(projectName)) {
179+
List<IJavaProject> javaProjects = searchClassInProjectClasspaths(mainClass);
180+
if (javaProjects.size() == 0) {
181+
return new ValidationResult(false, String.format("ConfigError: Main class '%s' doesn't exist in the workspace.", mainClass));
182+
}
183+
if (javaProjects.size() > 1) {
184+
return new ValidationResult(false, String.format("ConfigError: Main class '%s' isn't unique in the workspace.", mainClass));
185+
}
186+
}
187+
188+
return new ValidationResult(true);
189+
}
190+
191+
private List<IJavaProject> searchClassInProjectClasspaths(String fullyQualifiedClassName) throws CoreException {
192+
return ResolveClasspathsHandler.getJavaProjectFromType(fullyQualifiedClassName);
193+
}
194+
195+
private ValidationResult validateProjectName(final String mainClass, final String projectName) {
196+
if (StringUtils.isEmpty(projectName)) {
197+
return new ValidationResult(true);
198+
}
199+
200+
if (JdtUtils.getJavaProject(projectName) == null) {
201+
return new ValidationResult(false, String.format("ConfigError: The project '%s' is not a valid java project.", projectName));
202+
}
203+
204+
return new ValidationResult(true);
205+
}
206+
207+
private List<ResolutionItem> computeProposals(List<Object> arguments, final String mainClass, final String projectName) {
208+
List<ResolutionItem> proposals = resolveMainClassCore(arguments);
209+
210+
Collections.sort(proposals, new ProposalItemComparator((ResolutionItem item) -> {
211+
if (Objects.equals(item.mainClass, mainClass)) {
212+
return 1;
213+
} else if (Objects.equals(item.projectName, projectName)) {
214+
return 2;
215+
}
216+
217+
return 999;
218+
}));
219+
220+
return proposals;
118221
}
119222

120-
private class ResolutionItem {
223+
private class ResolutionItem implements Comparable<ResolutionItem> {
121224
private String mainClass;
122225
private String projectName;
123226
private String filePath;
@@ -146,5 +249,53 @@ public boolean equals(Object o) {
146249
public int hashCode() {
147250
return Objects.hash(mainClass, projectName, filePath);
148251
}
252+
253+
@Override
254+
public int compareTo(ResolutionItem o) {
255+
if (isDefaultProject(this.projectName) && !isDefaultProject(o.projectName)) {
256+
return 1;
257+
} else if (!isDefaultProject(this.projectName) && isDefaultProject(o.projectName)) {
258+
return -1;
259+
}
260+
261+
return (this.projectName + "|" + this.mainClass).compareTo(o.projectName + "|" + o.mainClass);
262+
}
263+
264+
private boolean isDefaultProject(String projectName) {
265+
return StringUtils.isEmpty(projectName);
266+
}
267+
}
268+
269+
class ProposalItemComparator implements Comparator<ResolutionItem> {
270+
Function<ResolutionItem, Integer> getRank;
271+
272+
ProposalItemComparator(Function<ResolutionItem, Integer> getRank) {
273+
this.getRank = getRank;
274+
}
275+
276+
@Override
277+
public int compare(ResolutionItem o1, ResolutionItem o2) {
278+
return getRank.apply(o1) - getRank.apply(o2);
279+
}
280+
}
281+
282+
class ValidationResponse {
283+
ValidationResult mainClass;
284+
ValidationResult projectName;
285+
List<ResolutionItem> proposals;
286+
}
287+
288+
class ValidationResult {
289+
boolean isValid;
290+
String message;
291+
292+
ValidationResult(boolean isValid) {
293+
this.isValid = isValid;
294+
}
295+
296+
ValidationResult(boolean isValid, String message) {
297+
this.isValid = isValid;
298+
this.message = message;
299+
}
149300
}
150301
}

0 commit comments

Comments
 (0)