You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@netbeans.apache.org by GitBox <gi...@apache.org> on 2022/03/17 17:45:17 UTC

[GitHub] [netbeans] JaroslavTulach commented on a change in pull request #3798: LSP: WebView based UI for Move refactoring.

JaroslavTulach commented on a change in pull request #3798:
URL: https://github.com/apache/netbeans/pull/3798#discussion_r829329110



##########
File path: java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/refactoring/MoveRefactoring.java
##########
@@ -116,101 +135,354 @@
     @Override
     @NbBundle.Messages({
         "DN_DefaultPackage=<default package>",
-        "DN_SelectTargetPackage=Select target package",
         "DN_CreateNewClass=<create new class>",
-        "DN_SelectTargetClass=Select target class",
     })
     public CompletableFuture<Object> processCommand(NbCodeLanguageClient client, String command, List<Object> arguments) {
         try {
             if (arguments.size() > 0) {
                 String uri = gson.fromJson(gson.toJson(arguments.get(0)), String.class);
-                QuickPickItem elementItem = arguments.size() > 1 ? gson.fromJson(gson.toJson(arguments.get(1)), QuickPickItem.class) : null;
                 FileObject file = Utils.fromUri(uri);
-                Project project = FileOwnerQuery.getOwner(file);
-                HashSet<QuickPickItem> items = new HashSet<>();
-                if (project != null) {
-                    for(SourceGroup sourceGroup : ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) {
-                        String name = sourceGroup.getDisplayName();
-                        FileObject rootFolder = sourceGroup.getRootFolder();
-                        if (elementItem == null) {
-                            items.add(new QuickPickItem(Bundle.DN_DefaultPackage(), name, null, false, Utils.toUri(rootFolder)));
-                        }
-                        for (String packageName : ClasspathInfo.create(rootFolder).getClassIndex().getPackageNames("", false, EnumSet.of(ClassIndex.SearchScope.SOURCE))) {
-                            if (elementItem == null) {
-                                String pkg = "";
-                                for (String part : packageName.split("\\.")) {
-                                    if (!part.isEmpty()) {
-                                        pkg += pkg.length() == 0 ? part : "." + part;
-                                        items.add(new QuickPickItem(pkg, name, null, false, Utils.toUri(rootFolder.getFileObject(pkg.replace('.', '/')))));
+                JavaSource js = JavaSource.forFileObject(file);
+                if (js != null) {
+                    return CompletableFuture.supplyAsync(() -> {
+                        try {
+                            js.runUserActionTask(ci -> {
+                                ci.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
+                                if (arguments.size() > 1) {
+                                    Element element = gson.fromJson(gson.toJson(arguments.get(1)), ElementData.class).resolve(ci);
+                                    if (element != null) {
+                                        if (element.getKind().isClass() || element.getKind().isInterface()) {
+                                            Pages.showMoveClassUI(ci, client, file, element);
+                                        } else {
+                                            Pages.showMoveMembersUI(ci, client, file, element);
+                                        }
                                     }
+                                } else {
+                                    Pages.showMoveClassUI(ci, client, file, null);
                                 }
-                            } else {
-                                items.add(new QuickPickItem(packageName, name, null, false, Utils.toUri(rootFolder.getFileObject(packageName.replace('.', '/')))));
-                            }
+                            }, true);
+                            return null;
+                        } catch (IOException ex) {
+                            throw new IllegalStateException(ex);
+                        }
+                    }, RequestProcessor.getDefault());
+                }
+            } else {
+                throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
+            }
+        } catch (JsonSyntaxException | IllegalArgumentException | MalformedURLException ex) {
+            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+        return CompletableFuture.completedFuture(true);
+    }
+
+    @HTMLDialog(url = "ui/MoveClass.html")
+    static HTMLDialog.OnSubmit showMoveClassUI(
+        CompilationController ci,
+        NbCodeLanguageClient client,
+        FileObject file,
+        Element element
+    ) {
+        MoveElementUI model = new MoveElementUI();
+        model.withMoveClass(true)
+                .withFrom(file.getName())
+                .assignData(client, file, element != null ? TreePathHandle.create(element, ci) : null);
+        model.applyBindings();
+        return (id) -> {
+            if ("accept".equals(id)) {
+                model.doRefactoring();
+            }
+            return true; // return false, if validation fails
+        };
+    }
+
+    @HTMLDialog(url = "ui/MoveMembers.html")
+    static HTMLDialog.OnSubmit showMoveMembersUI(
+        CompilationController ci,
+        NbCodeLanguageClient client,
+        FileObject file,
+        Element element
+    ) {
+        ElementUtilities eu = ci.getElementUtilities();
+        Element enclosingElement = element.getEnclosingElement();
+        String parentName = createLabel(ci, enclosingElement);
+        ElementUI[] members = enclosingElement.getEnclosedElements().stream()
+                .filter(memberElement -> (memberElement.getKind().isField() || memberElement.getKind() == ElementKind.METHOD) && !eu.isSynthetic(memberElement))
+                .map(memberElement -> {
+                    String label = createLabel(ci, memberElement);
+                    ElementData data = new ElementData(memberElement);
+                    ElementUI memberElementUI = new ElementUI(memberElement == element, label, memberElement.getKind().name(), data.getSignature());
+                    return memberElementUI;
+                }).toArray(ElementUI[]::new);
+        MoveElementUI model = new MoveElementUI();
+        model.withMoveClass(false)
+                .withFrom(parentName)
+                .withMembers(members)
+                .withKeepMethodSelected(false)
+                .withDeprecateMethodSelected(true)
+                .assignData(client, file, TreePathHandle.create(element, ci));
+        model.applyBindings();
+        return (id) -> {
+            if ("accept".equals(id)) {
+                model.doRefactoring();
+            }
+            return true; // return false, if validation fails
+        };
+    }
+
+    @Model(className = "MoveElementUI", targetId = "", instance = true, builder = "with", properties = {
+        @Property(name = "moveClass", type = boolean.class),
+        @Property(name = "from", type = String.class),
+        @Property(name = "selectedProject", type = NamedPath.class),
+        @Property(name = "selectedRoot", type = NamedPath.class),
+        @Property(name = "selectedPackage", type = String.class),
+        @Property(name = "selectedClass", type = ElementUI.class),
+        @Property(name = "selectedVisibility", type = Visibility.class),
+        @Property(name = "selectedJavaDoc", type = JavaDoc.class),
+        @Property(name = "members", type = ElementUI.class, array = true),
+        @Property(name = "keepMethodSelected", type = boolean.class),
+        @Property(name = "deprecateMethodSelected", type = boolean.class)
+    })
+    static final class MoveElementControl {
+
+        private NbCodeLanguageClient client;
+        private FileObject file;
+        private TreePathHandle handle;
+
+        @ModelOperation
+        void assignData(MoveElementUI ui, NbCodeLanguageClient client, FileObject file, TreePathHandle handle) {
+            this.client = client;
+            this.file = file;
+            this.handle = handle;
+        }
+
+        @ComputedProperty
+        static List<NamedPath> availableProjects() {
+            Project[] openProjects = OpenProjects.getDefault().getOpenProjects();
+            List<NamedPath> projectNames = new ArrayList<>(openProjects.length);
+            for (int i = 0; i < openProjects.length; i++) {
+                projectNames.add(new NamedPath(ProjectUtils.getInformation(openProjects[i]).getDisplayName(), Utils.toUri(openProjects[i].getProjectDirectory())));
+            }
+            return projectNames;
+        }
+
+        @ComputedProperty
+        static List<NamedPath> availableRoots(NamedPath selectedProject) {
+            Project project = getSelectedProject(selectedProject);
+            if (project != null) {
+                Sources sources = ProjectUtils.getSources(project);
+                SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
+                List<NamedPath> projectRoots = new ArrayList<>(groups.length);
+                for (int i = 0; i < groups.length; i++) {
+                    projectRoots.add(new NamedPath(groups[i].getDisplayName(), Utils.toUri(groups[i].getRootFolder())));
+                }
+                return projectRoots;
+            }
+            return Collections.emptyList();
+        }
+
+        @ComputedProperty
+        static List<String> availablePackages(boolean moveClass, NamedPath selectedRoot) {
+            FileObject rootFolder = getSelectedRoot(selectedRoot);
+            if (rootFolder != null) {
+                List<String> packages;
+                if (moveClass) {
+                    packages = new ArrayList<>();
+                    packages.add(Bundle.DN_DefaultPackage());
+                    Enumeration<? extends FileObject> children = rootFolder.getChildren(true);
+                    while (children.hasMoreElements()) {
+                        FileObject child = children.nextElement();
+                        if (child.isFolder()) {
+                            packages.add(FileUtil.getRelativePath(rootFolder, child).replace('/', '.'));
                         }
                     }
+                } else {
+                    packages = ClasspathInfo.create(rootFolder).getClassIndex().getPackageNames("", false, EnumSet.of(ClassIndex.SearchScope.SOURCE)).stream().collect(Collectors.toList());
                 }
-                ArrayList<QuickPickItem> packages = new ArrayList<>(items);
-                Collections.sort(packages, (item1, item2) -> {
-                    int i = item1.getDescription().compareTo(item2.getDescription());
-                    return i == 0 ? item1.getLabel().compareTo(item2.getLabel()) : i;
+                packages.sort((s1, s2) -> s1.compareTo(s2));
+                return packages;
+            }
+            return Collections.emptyList();
+        }
+
+        @ComputedProperty
+        static List<ElementUI> availableClasses(boolean moveClass, NamedPath selectedRoot, String selectedPackage) {
+            FileObject rootFolder = getSelectedRoot(selectedRoot);
+            if (rootFolder != null && selectedPackage != null) {
+                FileObject fo = rootFolder.getFileObject(selectedPackage.replace('.', '/'));
+                ClassPath sourcePath = ClassPath.getClassPath(fo, ClassPath.SOURCE);
+                final ClasspathInfo info = ClasspathInfo.create(EMPTY_PATH, EMPTY_PATH, sourcePath);
+                Set<ClassIndex.SearchScopeType> searchScopeType = new HashSet<>(1);
+                final Set<String> packageSet = Collections.singleton(selectedPackage);
+                searchScopeType.add(new ClassIndex.SearchScopeType() {
+                    @Override
+                    public Set<? extends String> getPackages() {
+                        return packageSet;
+                    }
+
+                    @Override
+                    public boolean isSources() {
+                        return true;
+                    }
+
+                    @Override
+                    public boolean isDependencies() {
+                        return false;
+                    }
                 });
-                Consumer<List<QuickPickItem>> f = selectedPackage -> {
-                    if (selectedPackage != null && !selectedPackage.isEmpty()) {
-                        ClasspathInfo info = ClasspathInfo.create(file);
-                        TreePathHandle tph = elementItem != null ? TreePathHandle.from(gson.fromJson(gson.toJson(elementItem.getUserData()), ElementData.class).toHandle(), info) : null;
-                        List<QuickPickItem> classes = packageClasses(selectedPackage.get(0), tph == null || tph.getKind() == Tree.Kind.CLASS);
-                        if (classes.isEmpty()) {
-                            if (tph == null) {
-                                move(client, uri, selectedPackage.get(0), ClasspathInfo.create(file));
-                            } else {
-                                throw new IllegalArgumentException(String.format("No target class found in selected package"));
-                            }
+                List<ElementUI> ret = new ArrayList<>();
+                if (moveClass) {
+                    ret.add(new ElementUI(false, Bundle.DN_CreateNewClass(), null));
+                }
+                for (ElementHandle<TypeElement> eh : info.getClassIndex().getDeclaredTypes("", ClassIndex.NameKind.PREFIX, searchScopeType)) {
+                    ElementData data = new ElementData(eh);
+                    String shortName = eh.getQualifiedName().substring(selectedPackage.length() + 1);
+                    int idx = shortName.indexOf('.');
+                    if (fo.getFileObject(idx < 0 ? shortName : shortName.substring(0, idx), "java") != null) {
+                        ret.add(new ElementUI(false, shortName, data.getKind(), data.getSignature()));
+                    }
+                }
+                ret.sort((e1, e2) -> e1.getLabel().compareTo(e2.getLabel()));
+                return ret;
+            }
+            return Collections.emptyList();
+        }
+
+        @ComputedProperty
+        static List<Visibility> availableVisibilities() {
+            return Arrays.asList(Visibility.values());
+        }
+
+        @ComputedProperty
+        static List<JavaDoc> availableJavaDoc() {
+            return Arrays.asList(JavaDoc.values());
+        }
+
+        @ModelOperation
+        @Function
+        void doRefactoring(MoveElementUI ui) {
+            try {
+                org.netbeans.modules.refactoring.api.MoveRefactoring refactoring;
+                if (handle == null) {
+                    refactoring = new org.netbeans.modules.refactoring.api.MoveRefactoring(Lookups.fixed(file));
+                    refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
+                } else {
+                    InstanceContent ic = new InstanceContent();
+                    refactoring = new org.netbeans.modules.refactoring.api.MoveRefactoring(new AbstractLookup(ic));
+                    List<TreePathHandle> selectedElements = ui.getMembers().stream().filter(member -> member.isSelected()).map(selectedMember -> {
+                        ElementHandle memberHandle = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(selectedMember.getKind()), selectedMember.getSignature().toArray(new String[selectedMember.getSignature().size()]));
+                        return TreePathHandle.from(memberHandle, ClasspathInfo.create(file));
+                    }).collect(Collectors.toList());
+                    ic.set(selectedElements, null);
+                    if (handle.getKind() == Tree.Kind.CLASS) {
+                        refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(handle.getFileObject()));
+                    } else {
+                        JavaMoveMembersProperties properties = new JavaMoveMembersProperties(selectedElements.toArray(new TreePathHandle[selectedElements.size()]));
+                        properties.setVisibility(JavaMoveMembersProperties.Visibility.valueOf(ui.getSelectedVisibility().name()));
+                        properties.setDelegate(ui.isKeepMethodSelected());
+                        properties.setUpdateJavaDoc(ui.getSelectedJavaDoc() == JavaDoc.UPDATE);
+                        properties.setAddDeprecated(ui.isDeprecateMethodSelected());
+                        refactoring.getContext().add(properties);
+                    }
+                }
+                ElementUI selectedClass = ui.getSelectedClass();
+                if (selectedClass != null) {
+                    if (selectedClass.getKind() != null && selectedClass.getSignature() != null) {
+                        ElementHandle eh = ElementHandleAccessor.getInstance().create(ElementKind.valueOf(selectedClass.getKind()), selectedClass.getSignature().toArray(new String[selectedClass.getSignature().size()]));
+                        refactoring.setTarget(Lookups.singleton(TreePathHandle.from(eh, ClasspathInfo.create(file))));
+                    } else {
+                        FileObject rootFolder = getSelectedRoot(ui.getSelectedRoot());
+                        if (rootFolder != null && ui.getSelectedPackage()!= null) {
+                            refactoring.setTarget(Lookups.singleton(rootFolder.getFileObject(ui.getSelectedPackage().replace('.', '/')).toURL()));
                         } else {
-                            client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectTargetClass(), false, classes)).thenAccept(selectedClass -> {
-                                if (selectedClass != null && !selectedClass.isEmpty()) {
-                                    QuickPickItem selected = Bundle.DN_CreateNewClass().equals(selectedClass.get(0).getLabel()) ? selectedPackage.get(0) : selectedClass.get(0);
-                                    move(client, tph != null ? tph : uri, selected, info);
-                                }
-                            });
+                            refactoring.setTarget(Lookup.EMPTY);
                         }
                     }
-                };
-                if (packages.size() == 1) {
-                    f.accept(packages);
                 } else {
-                    client.showQuickPick(new ShowQuickPickParams(Bundle.DN_SelectTargetPackage(), false, packages)).thenAccept(f);
+                    refactoring.setTarget(Lookup.EMPTY);
+                }
+                client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, "Move")));
+            } catch (Exception ex) {
+                if (client == null) {
+                    Exceptions.printStackTrace(
+                        Exceptions.attachSeverity(ex, Level.SEVERE)
+                    );
+                } else {
+                    client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
                 }
-            } else {
-                throw new IllegalArgumentException(String.format("Illegal number of arguments received for command: %s", command));
             }
-        } catch (Exception ex) {
-            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
         }
-        return CompletableFuture.completedFuture(true);
-    }
 
-    private void move(NbCodeLanguageClient client, Object source, QuickPickItem target, ClasspathInfo info) {
-        try {
-            org.netbeans.modules.refactoring.api.MoveRefactoring refactoring;
-            if (source instanceof String) {
-                FileObject file = Utils.fromUri((String) source);
-                refactoring = new org.netbeans.modules.refactoring.api.MoveRefactoring(Lookups.fixed(file));
-                refactoring.getContext().add(JavaRefactoringUtils.getClasspathInfoFor(file));
-            } else {
-                TreePathHandle tph = (TreePathHandle) source;
-                refactoring = new org.netbeans.modules.refactoring.api.MoveRefactoring(Lookups.fixed(tph));
-                refactoring.getContext().add(tph.getKind() == Tree.Kind.CLASS ? JavaRefactoringUtils.getClasspathInfoFor(tph.getFileObject()) : new JavaMoveMembersProperties(tph));
+        private static Project getSelectedProject(NamedPath selectedProject) {
+            try {
+                String path = selectedProject.getPath();
+                return path != null ? FileOwnerQuery.getOwner(Utils.fromUri(path)) : null;
+            } catch (MalformedURLException ex) {
+                return null;
             }
-            if (target.getDescription() != null) {
-                refactoring.setTarget(Lookups.singleton(new URL((String) target.getUserData())));
-            } else {
-                ElementHandle handle = gson.fromJson(gson.toJson(target.getUserData()), ElementData.class).toHandle();
-                refactoring.setTarget(Lookups.singleton(TreePathHandle.from(handle, info)));
+        }
+
+        private static FileObject getSelectedRoot(NamedPath selectedRoot) {
+            try {
+                String path = selectedRoot.getPath();
+                return path != null ? Utils.fromUri(path) : null;
+            } catch (MalformedURLException ex) {
+                return null;
             }
-            client.applyEdit(new ApplyWorkspaceEditParams(perform(refactoring, "Move")));
-        } catch (Exception ex) {
-            client.showMessage(new MessageParams(MessageType.Error, ex.getLocalizedMessage()));
+        }
+    }
+
+    @Model(className = "ElementUI", instance = true, properties = {
+        @Property(name = "selected", type = boolean.class),

Review comment:
       Safer in the presence of [NETBEANS-6478](https://issues.apache.org/jira/browse/NETBEANS-6478).

##########
File path: java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/ServerTest.java
##########
@@ -3648,9 +3649,14 @@ public void showMessage(MessageParams params) {
             }
 
             @Override
-            public CompletableFuture<List<QuickPickItem>> showQuickPick(ShowQuickPickParams params) {
-                List<QuickPickItem> items = params.getItems();
-                return CompletableFuture.completedFuture("Select target package".equals(params.getPlaceHolder()) ? items.subList(2, 3) : items.subList(0, 1));
+            public CompletableFuture<String> showHtmlPage(HtmlPageParams params) {
+                MoveElementUI ui = MockHtmlViewer.assertDialogShown(params.getUri(), MoveElementUI.class);

Review comment:
       Great test.




-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org

For queries about this service, please contact Infrastructure at:
users@infra.apache.org



---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@netbeans.apache.org
For additional commands, e-mail: notifications-help@netbeans.apache.org

For further information about the NetBeans mailing lists, visit:
https://cwiki.apache.org/confluence/display/NETBEANS/Mailing+lists