1212package com .microsoft .java .debug .plugin .internal ;
1313
1414import java .util .ArrayList ;
15+ import java .util .Collections ;
16+ import java .util .Comparator ;
1517import java .util .List ;
1618import java .util .Objects ;
19+ import java .util .function .Function ;
1720import java .util .logging .Level ;
1821import java .util .logging .Logger ;
1922import java .util .stream .Collectors ;
2023
24+ import org .apache .commons .lang3 .StringUtils ;
2125import org .eclipse .core .resources .IFile ;
2226import org .eclipse .core .resources .IProject ;
2327import org .eclipse .core .resources .IResource ;
4044
4145public 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