diff --git a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUI.java b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUI.java index 47f8512b815a..4c06f9454d49 100644 --- a/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUI.java +++ b/java/java.lsp.server/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUI.java @@ -33,12 +33,14 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.StringTokenizer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.function.Function; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import javax.lang.model.SourceVersion; import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.MessageParams; import org.eclipse.lsp4j.MessageType; @@ -81,14 +83,17 @@ "ERR_InvalidPath={0} isn't valid folder", "# {0} - path", "ERR_ExistingPath={0} already exists", + "# {0} - packageName", + "ERR_InvalidPackageName={0} isn't valid package name" }) -final class LspTemplateUI { + final class LspTemplateUI { /** * Creation thread. All requests are serialized; make sure that no creation process can block e.g. waiting * for the client's response. */ private static final RequestProcessor CREATION_RP = new RequestProcessor(LspTemplateUI.class); - private static final Logger LOG = Logger.getLogger(LspTemplateUI.class.getName()); + private static final Logger LOG = Logger.getLogger(LspTemplateUI.class.getName()); + private static final String JAVA_PKG_TEMPLATE = "Templates/Classes/Package"; private LspTemplateUI() { } @@ -108,27 +113,18 @@ static CompletableFuture createProject(String templates, NbCodeLanguageC private CompletableFuture templateUI(DataFolder templates, NbCodeLanguageClient client, ExecuteCommandParams params) { CompletionStage findTemplate = findTemplate(templates, client, params); CompletionStage findTargetFolder = findTargetForTemplate(findTemplate, client, params); - return findTargetFolder.thenCombine(findTemplate, (target, source) -> { - final FileObject templateFileObject = source.getPrimaryFile(); - return new FileBuilder(templateFileObject, target.getPrimaryFile()).name(templateFileObject.getName()); - }).thenCompose(builder -> configure(builder, client)).thenApplyAsync(builder -> { - try { - if (builder != null) { - List created = builder.build(); - if (created == null) { - return null; - } else if (created.isEmpty()) { - return Collections.emptyList(); - } - // Make sure the newly created files are indexed before returned to client - IndexingManager.getDefault().refreshAllIndices(false, true, created.toArray(new FileObject[0])); - return (Object) created.stream().map(fo -> fo.toURI().toString()).collect(Collectors.toList()); - } - return null; - } catch (IOException ex) { - throw raise(RuntimeException.class, ex); + return findTargetFolder.thenCombine(findTemplate, Pair::of).thenCompose(targetAndTemplate -> { + final DataFolder target = targetAndTemplate.first(); + final DataObject source = targetAndTemplate.second(); + + if (isPkgTemplate(source)) { + return createPackage(target, client); } - }, CREATION_RP).exceptionally((error) -> { + + final FileObject templateFileObject = source.getPrimaryFile(); + return configure(new FileBuilder(templateFileObject, target.getPrimaryFile()).name(templateFileObject.getName()), client) + .thenApplyAsync(LspTemplateUI::buildTemplate, CREATION_RP); + }).exceptionally((error) -> { if (error instanceof UserCancelException || error.getCause() instanceof UserCancelException) { return null; } @@ -137,6 +133,25 @@ private CompletableFuture templateUI(DataFolder templates, NbCodeLanguag }).toCompletableFuture(); } + private static Object buildTemplate(FileBuilder builder) { + try { + if (builder != null) { + List created = builder.build(); + if (created == null) { + return null; + } else if (created.isEmpty()) { + return Collections.emptyList(); + } + // Make sure the newly created files are indexed before returned to client + IndexingManager.getDefault().refreshAllIndices(false, true, created.toArray(new FileObject[0])); + return created.stream().map(fo -> fo.toURI().toString()).collect(Collectors.toList()); + } + return null; + } catch (IOException ex) { + throw raise(RuntimeException.class, ex); + } + } + private CompletableFuture projectUI(DataFolder templates, NbCodeLanguageClient client, ExecuteCommandParams params) { CompletionStage findTemplate = findTemplate(templates, client, params); CompletionStage> findTargetFolderAndName = findTargetAndNameForProject(findTemplate, client); @@ -229,13 +244,105 @@ private static CompletionStage configure(FileBuilder builder, NbCod FileObject template = desc.getTemplate(); Object handler = template.getAttribute(FileBuilder.ATTR_TEMPLATE_HANDLER); if (handler == null) { - return client.showInputBox(new ShowInputBoxParams(Bundle.CTL_TemplateUI_SelectName(), desc.getProposedName())).thenApply(name -> { - return name != null ? builder.name(name) : null; - }); + class ValidateJavaObjectName implements Function> { + + @Override + public CompletionStage apply(String name) { + FileObject existingTargetFile = findExistingTargetFile(desc, name); + if (existingTargetFile != null) { + client.showMessage(new MessageParams(MessageType.Error, + Bundle.ERR_ExistingPath(FileUtil.getFileDisplayName(existingTargetFile)))); + return client.showInputBox( + new ShowInputBoxParams(Bundle.CTL_TemplateUI_SelectName(), desc.getProposedName())).thenCompose(this); + } + return CompletableFuture.completedFuture(name); + } + } } return CompletableFuture.completedFuture(builder); } + private static CompletionStage createPackage(DataFolder target, NbCodeLanguageClient client) { + class ValidatePackageName implements Function> { + @Override + public CompletionStage apply(String packageName) { + if (packageName == null) { + throw raise(RuntimeException.class, new UserCancelException("")); + } + if (!SourceVersion.isName(packageName)) { + client.showMessage(new MessageParams(MessageType.Error, Bundle.ERR_InvalidPackageName(packageName))); + return client.showInputBox(new ShowInputBoxParams(Bundle.CTL_TemplateUI_SelectName(), "")).thenCompose(this); + } + FileObject existingTarget = checkExistingPkgTarget(target.getPrimaryFile(), packageName); + if (existingTarget != null) { + client.showMessage(new MessageParams(MessageType.Error, Bundle.ERR_ExistingPath(FileUtil.getFileDisplayName(existingTarget)))); + return client.showInputBox(new ShowInputBoxParams(Bundle.CTL_TemplateUI_SelectName(), "")).thenCompose(this); + } + try { + FileObject created = createPkgFolderStructure(target.getPrimaryFile(), packageName); + IndexingManager.getDefault().refreshAllIndices(false, true, created); + return CompletableFuture.completedFuture(created.toURI().toString()); + } catch (IOException ex) { + throw raise(RuntimeException.class, ex); + } + } + } + return client.showInputBox(new ShowInputBoxParams(Bundle.CTL_TemplateUI_SelectName(), "")).thenCompose(new ValidatePackageName()); + } + + static FileObject findExistingTargetFile(CreateDescriptor desc, String name) { + if (desc == null || name == null) { + return null; + } + + FileObject target = desc.getTarget(); + if (target == null) { + return null; + } + + if (desc.hasFreeExtension()) { + return target.getFileObject(name); + } + + String extension = desc.getTemplate().getExt(); + String baseName = name; + + if (!extension.isEmpty() && name.endsWith("." + extension)) { + baseName = name.substring(0, name.length() - extension.length() - 1); + } + + return extension.isEmpty() + ? target.getFileObject(baseName) + : target.getFileObject(baseName, extension); + } + + static FileObject checkExistingPkgTarget(FileObject target, String pkgName) { + if (target == null || pkgName == null) { + return null; + } + FileObject current = target; + StringTokenizer pkgTokens = new StringTokenizer(pkgName, "."); + while (pkgTokens.hasMoreTokens()) { + FileObject next = current.getFileObject(pkgTokens.nextToken()); + if (next == null) { + return null; + } + if (next.isData() || !pkgTokens.hasMoreTokens()) { + return next; + } + current = next; + } + return null; + } + + static FileObject createPkgFolderStructure(FileObject targetFile, String pkgName) throws IOException { + return FileUtil.createFolder(targetFile, pkgName.replace('.', '/')); + } + + private static boolean isPkgTemplate(DataObject inputTemplate) { + return JAVA_PKG_TEMPLATE.equals(inputTemplate.getPrimaryFile().getPath()); + } + private static String suggestWorkspaceRoot(List folders) throws IllegalArgumentException { String suggestion = System.getProperty("user.home"); if (folders != null && !folders.isEmpty()) try { diff --git a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUITest.java b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUITest.java index e1408434ab8e..403b86b71803 100644 --- a/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUITest.java +++ b/java/java.lsp.server/test/unit/src/org/netbeans/modules/java/lsp/server/protocol/LspTemplateUITest.java @@ -18,8 +18,16 @@ */ package org.netbeans.modules.java.lsp.server.protocol; +import java.util.Collections; +import org.netbeans.api.templates.CreateDescriptor; +import org.netbeans.api.templates.FileBuilder; import org.junit.Test; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import org.openide.filesystems.FileObject; +import org.openide.filesystems.FileUtil; public class LspTemplateUITest { @Test @@ -53,4 +61,69 @@ public void testStripConsecutiveWhitespces() { String result = LspTemplateUI.stripHtml(s); assertEquals(expResult, result); } +/*@aksinsin bug-1 start*/ + @Test + public void testFindExistingTargetFileWithTemplateExtension() throws Exception { + //given + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject template = FileUtil.createData(root, "Templates/Class.java"); + FileObject target = FileUtil.createFolder(root, "src"); + FileObject existing = FileUtil.createData(target, "TestDuplicateName.java"); + //when + CreateDescriptor desc = new FileBuilder(template, target) + .withParameters(Collections.emptyMap()) + .createDescriptor(false); + //than + assertEquals(existing, LspTemplateUI.findExistingTargetFile(desc, "TestDuplicateName")); + assertEquals(existing, LspTemplateUI.findExistingTargetFile(desc, "TestDuplicateName.java")); + } + + @Test + public void testFindExistingTargetFileWithFreeExtension() throws Exception { + //given + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject template = FileUtil.createData(root, "Templates/Any.txt"); + FileObject target = FileUtil.createFolder(root, "src"); + FileObject existing = FileUtil.createData(target, "TestDuplicateName.java"); + //when + CreateDescriptor desc = new FileBuilder(template, target) + .withParameters(Collections.emptyMap()) + .param(CreateDescriptor.FREE_FILE_EXTENSION, Boolean.TRUE) + .createDescriptor(false); + + //than + assertEquals(existing, LspTemplateUI.findExistingTargetFile(desc, "TestDuplicateName.java")); + assertNull(LspTemplateUI.findExistingTargetFile(desc, "TestDuplicateName")); + }/*@aksinsin bug-1 end*/ + + @Test + public void testFindExistingPkgTargetForExistingFolder() throws Exception { + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject target = FileUtil.createFolder(root, "src"); + FileObject existing = FileUtil.createFolder(target, "org/example"); + + assertEquals(existing, LspTemplateUI.checkExistingPkgTarget(target, "org.example")); + } + + @Test + public void testFindExistingPkgTargetForBlockingFile() throws Exception { + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject target = FileUtil.createFolder(root, "src"); + FileObject blocking = FileUtil.createData(target, "org"); + + assertEquals(blocking, LspTemplateUI.checkExistingPkgTarget(target, "org.example")); + } + + @Test + public void testCreatePkgFolderCreatesHierarchy() throws Exception { + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject target = FileUtil.createFolder(root, "src"); + + FileObject created = LspTemplateUI.createPkgFolderStructure(target, "org.example.demo"); + + FileObject createdFolder = target.getFileObject("org/example/demo"); + assertNotNull(createdFolder); + assertTrue(createdFolder.isFolder()); + assertEquals(createdFolder, created); + } }