Issue #141886: let user select alternate file extensions in simple target chooser. diff --git a/openide.filesystems/src/org/openide/filesystems/FileUtil.java b/openide.filesystems/src/org/openide/filesystems/FileUtil.java --- a/openide.filesystems/src/org/openide/filesystems/FileUtil.java +++ b/openide.filesystems/src/org/openide/filesystems/FileUtil.java @@ -1262,7 +1262,7 @@ * * @param folder parent folder * @param name preferred base name of file - * @param ext extension to use + * @param ext extension to use (or null) * @return a free file name (without the extension) */ public static String findFreeFileName(FileObject folder, String name, String ext) { diff --git a/openide.loaders/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateHandler.java b/openide.loaders/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateHandler.java --- a/openide.loaders/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateHandler.java +++ b/openide.loaders/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateHandler.java @@ -71,9 +71,10 @@ protected FileObject createFromTemplate(FileObject template, FileObject f, String name, Map values) throws IOException { + boolean noExt = Boolean.TRUE.equals(values.get(FREE_FILE_EXTENSION)) && name.indexOf('.') != -1; - String nameUniq = FileUtil.findFreeFileName(f, name, template.getExt()); - FileObject output = FileUtil.createData(f, nameUniq + '.' + template.getExt()); + String nameUniq = FileUtil.findFreeFileName(f, name, noExt ? null : template.getExt()); + FileObject output = FileUtil.createData(f, noExt ? nameUniq : nameUniq + '.' + template.getExt()); Charset targetEnc = FileEncodingQuery.getEncoding(output); Charset sourceEnc = FileEncodingQuery.getEncoding(template); diff --git a/openide.loaders/src/org/openide/loaders/CreateFromTemplateHandler.java b/openide.loaders/src/org/openide/loaders/CreateFromTemplateHandler.java --- a/openide.loaders/src/org/openide/loaders/CreateFromTemplateHandler.java +++ b/openide.loaders/src/org/openide/loaders/CreateFromTemplateHandler.java @@ -57,7 +57,7 @@ /** Handles the creation of new file. * @param orig the source file * @param f the folder to create a file in - * @param name the name of new file to create in the folder (extension will be inherited from orig) + * @param name the name of new file to create in the folder (see {@link #FREE_FILE_EXTENSION} regarding extension) * @param parameters map of additional arguments as specified by registered {@link CreateFromTemplateAttributesProvider}s * @return the newly create file * @throws IOException if something goes wrong with I/O @@ -68,5 +68,19 @@ String name, Map parameters ) throws IOException; + + /** + * Parameter to enable free file extension mode. + * By default, the extension of the newly created file will be inherited + * from the template. But if {@link #createFromTemplate} is called with this + * parameter set to {@link Boolean#TRUE} + * (such as from {@link DataObject#createFromTemplate(DataFolder,String,Map)}), + * and the file name already seems to + * include an extension (*.*), the handler should not append + * any extension from the template. + * @since XXX + * @see Templates.SimpleTargetChooserBuilder.freeFileExtension + */ + public static final String FREE_FILE_EXTENSION = "freeFileExtension"; // NOI18N } diff --git a/openide.loaders/src/org/openide/loaders/DataObject.java b/openide.loaders/src/org/openide/loaders/DataObject.java --- a/openide.loaders/src/org/openide/loaders/DataObject.java +++ b/openide.loaders/src/org/openide/loaders/DataObject.java @@ -1323,8 +1323,11 @@ all.put(e.getKey(), e.getValue()); } } - + if (!all.containsKey("name") && name != null) { // NOI18N + if (Boolean.TRUE.equals(all.get(CreateFromTemplateHandler.FREE_FILE_EXTENSION))) { + name = name.replaceFirst("[.].*", ""); + } all.put("name", name); // NOI18N } if (!all.containsKey("user")) { // NOI18N @@ -1344,7 +1347,8 @@ public static Map enhanceParameters(Map old, String name, String ext) { HashMap all = new HashMap(old); if (!all.containsKey("nameAndExt") && name != null) { // NOI18N - if (ext != null && ext.length() > 0) { + if (ext != null && ext.length() > 0 && + (!Boolean.TRUE.equals(old.get(CreateFromTemplateHandler.FREE_FILE_EXTENSION)) || name.indexOf('.') == -1)) { all.put("nameAndExt", name + '.' + ext); // NOI18N } else { all.put("nameAndExt", name); // NOI18N diff --git a/openide.loaders/test/unit/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateTest.java b/openide.loaders/test/unit/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateTest.java --- a/openide.loaders/test/unit/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateTest.java +++ b/openide.loaders/test/unit/src/org/netbeans/modules/templates/ScriptingCreateFromTemplateTest.java @@ -35,6 +35,7 @@ import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Collections; +import java.util.HashMap; import java.util.Map; import javax.swing.text.DefaultEditorKit; import javax.swing.text.Document; @@ -51,6 +52,8 @@ import org.openide.loaders.MultiDataObject; import org.openide.loaders.MultiFileLoader; import org.netbeans.api.editor.mimelookup.test.MockMimeLookup; +import org.openide.loaders.CreateFromTemplateHandler; +import org.openide.util.SharedClassObject; import org.openide.util.test.MockLookup; /** @@ -71,7 +74,7 @@ @Override protected void setUp() throws Exception { - MockLookup.setInstances(new SimpleLoader()); + MockLookup.setInstances(SharedClassObject.findObject(SimpleLoader.class, true)); } public void testCreateFromTemplateEncodingProperty() throws Exception { @@ -102,6 +105,50 @@ assertNotNull("Template encoding is null", targetEnc); assertEquals("Encoding in template doesn't match", targetEnc.name(), instFO.asText()); } + + public void testFreeFileExtension() throws Exception { + FileObject root = FileUtil.createMemoryFileSystem().getRoot(); + FileObject template = FileUtil.createData(root, "simple.pl"); + OutputStream os = template.getOutputStream(); + os.write("#!/usr/bin/perl\n# ${license}\n# ${name} in ${nameAndExt}\n".getBytes()); + os.close(); + template.setAttribute("template", true); + template.setAttribute("javax.script.ScriptEngine", "freemarker"); + Map parameters = new HashMap(); + parameters.put("license", "GPL"); + parameters.put(CreateFromTemplateHandler.FREE_FILE_EXTENSION, true); + ClassLoader oldLoader = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader().getParent()); + try { + FileObject inst; + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "nue", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# nue in nue.pl\n", inst.asText()); + assertEquals("nue.pl", inst.getPath()); + /* XXX perhaps irrelevant since typical wizards disable Finish in this condition + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "nue", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# nue_1 in nue_1.pl\n", inst.asText()); + assertEquals("nue_1.pl", inst.getPath()); + */ + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "nue.cgi", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# nue in nue.cgi\n", inst.asText()); + assertEquals("nue.cgi", inst.getPath()); + /* XXX + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "nue.cgi", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# nue_1 in nue_1.cgi\n", inst.asText()); + assertEquals("nue_1.cgi", inst.getPath()); + */ + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "explicit.pl", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# explicit in explicit.pl\n", inst.asText()); + assertEquals("explicit.pl", inst.getPath()); + /* XXX + inst = DataObject.find(template).createFromTemplate(DataFolder.findFolder(root), "explicit.pl", parameters).getPrimaryFile(); + assertEquals("#!/usr/bin/perl\n# GPL\n# explicit_1 in explicit_1.pl\n", inst.asText()); + assertEquals("explicit_1.pl", inst.getPath()); + */ + } finally { + Thread.currentThread().setContextClassLoader(oldLoader); + } + } //fix for this test was rolled back because of issue #120865 public void XtestCreateFromTemplateDocumentCreated() throws Exception { diff --git a/php.project/src/org/netbeans/modules/php/project/resources/layer.xml b/php.project/src/org/netbeans/modules/php/project/resources/layer.xml --- a/php.project/src/org/netbeans/modules/php/project/resources/layer.xml +++ b/php.project/src/org/netbeans/modules/php/project/resources/layer.xml @@ -111,8 +111,8 @@ - - + + @@ -120,8 +120,8 @@ - - + + @@ -129,18 +129,18 @@ - + - + - + - + diff --git a/php.project/src/org/netbeans/modules/php/project/ui/wizards/Bundle.properties b/php.project/src/org/netbeans/modules/php/project/ui/wizards/Bundle.properties --- a/php.project/src/org/netbeans/modules/php/project/ui/wizards/Bundle.properties +++ b/php.project/src/org/netbeans/modules/php/project/ui/wizards/Bundle.properties @@ -46,9 +46,6 @@ Templates/Scripting/PHPClass=PHP Class Templates/Scripting/PHPInterface=PHP Interface -# new file wizard -TXT_FileExists=File {0} already exists! - # PHP project wizards TXT_PhpProject=PHP Project TXT_ExistingPhpProject=PHP Project with Existing Sources diff --git a/php.project/src/org/netbeans/modules/php/project/ui/wizards/NewFileWizardIterator.java b/php.project/src/org/netbeans/modules/php/project/ui/wizards/NewFileWizardIterator.java --- a/php.project/src/org/netbeans/modules/php/project/ui/wizards/NewFileWizardIterator.java +++ b/php.project/src/org/netbeans/modules/php/project/ui/wizards/NewFileWizardIterator.java @@ -42,8 +42,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import java.util.NoSuchElementException; import java.util.Set; import java.util.logging.Level; @@ -52,8 +50,6 @@ import javax.swing.event.ChangeListener; import org.netbeans.api.project.Project; import org.netbeans.api.project.SourceGroup; -import org.netbeans.modules.php.api.util.FileUtils; -import org.netbeans.modules.php.api.util.StringUtils; import org.netbeans.modules.php.project.PhpProject; import org.netbeans.modules.php.project.ProjectPropertiesSupport; import org.netbeans.modules.php.project.SourceRoots; @@ -62,10 +58,9 @@ import org.openide.WizardDescriptor; import org.openide.WizardDescriptor.Panel; import org.openide.filesystems.FileObject; -import org.openide.filesystems.FileUtil; +import org.openide.loaders.CreateFromTemplateHandler; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; -import org.openide.util.NbBundle; /** * Just as simple wrapper for the standard new file iterator as possible. @@ -84,37 +79,10 @@ FileObject dir = Templates.getTargetFolder(wizard); FileObject template = Templates.getTemplate(wizard); - Map wizardProps = new HashMap(); - DataFolder dataFolder = DataFolder.findFolder(dir); DataObject dataTemplate = DataObject.find(template); - String fname = Templates.getTargetName(wizard); - String ext = FileUtil.getExtension(fname); - - FileObject foo = FileUtil.createData(FileUtil.createMemoryFileSystem().getRoot(), fname); - if (foo == null || !FileUtils.isPhpFile(foo)) { - if (!StringUtils.hasText(ext)) { - Templates.setTargetName(wizard, fname + ".php"); // NOI18N - fname = Templates.getTargetName(wizard); - ext = FileUtil.getExtension(fname); - } - } - if (StringUtils.hasText(ext)) { - String name = fname.substring(0, fname.length() - ext.length() - 1); - name = name.replaceAll("\\W", ""); // NOI18N - wizardProps.put("name", name); // NOI18N - - // #168723 - String templateExt = FileUtil.getExtension(template.getNameExt()); - if (StringUtils.hasText(templateExt)) { - Templates.setTargetName(wizard, name); - } - } - String targetName = Templates.getTargetName(wizard); - if (dir.getFileObject(targetName) != null) { - throw new IOException(NbBundle.getMessage(NewFileWizardIterator.class, "TXT_FileExists", targetName)); - } - DataObject createdFile = dataTemplate.createFromTemplate(dataFolder, targetName, wizardProps); + DataObject createdFile = dataTemplate.createFromTemplate(dataFolder, Templates.getTargetName(wizard), + Collections.singletonMap(CreateFromTemplateHandler.FREE_FILE_EXTENSION, true)); return Collections.singleton(createdFile.getPrimaryFile()); } @@ -133,9 +101,6 @@ Templates.setTargetFolder(wizard, srcDir); } } - FileObject template = Templates.getTemplate(wizard); - String targetName = targetFolder != null ? FileUtil.findFreeFileName(targetFolder, template.getName(), "php") : template.getName(); // NOI18N - Templates.setTargetName(wizard, targetName + ".php"); // NOI18N wizardPanels = getPanels(); // Make sure list of steps is accurate. @@ -271,7 +236,7 @@ new IllegalStateException("No source roots found (attach your IDE log to https://netbeans.org/bugzilla/show_bug.cgi?id=180054)")); groups = null; } - WizardDescriptor.Panel simpleTargetChooserPanel = Templates.createSimpleTargetChooser(p, groups); + WizardDescriptor.Panel simpleTargetChooserPanel = Templates.buildSimpleTargetChooser(p, groups).freeFileExtension().create(); @SuppressWarnings("unchecked") // Generic Array Creation WizardDescriptor.Panel[] panels = new WizardDescriptor.Panel[] { diff --git a/projectui/src/org/netbeans/modules/project/ui/NewFileIterator.java b/projectui/src/org/netbeans/modules/project/ui/NewFileIterator.java --- a/projectui/src/org/netbeans/modules/project/ui/NewFileIterator.java +++ b/projectui/src/org/netbeans/modules/project/ui/NewFileIterator.java @@ -107,9 +107,9 @@ currentProject = project; Sources sources = ProjectUtils.getSources(project); if (isFolder) { - panel = new SimpleTargetChooserPanel(project, sources.getSourceGroups(Sources.TYPE_GENERIC), null, true); + panel = new SimpleTargetChooserPanel(project, sources.getSourceGroups(Sources.TYPE_GENERIC), null, true, false); } else { - panel = Templates.createSimpleTargetChooser(project, sources.getSourceGroups(Sources.TYPE_GENERIC)); + panel = Templates.buildSimpleTargetChooser(project, sources.getSourceGroups(Sources.TYPE_GENERIC)).create(); } } return panel; diff --git a/projectui/src/org/netbeans/modules/project/ui/NewFileWizard.java b/projectui/src/org/netbeans/modules/project/ui/NewFileWizard.java --- a/projectui/src/org/netbeans/modules/project/ui/NewFileWizard.java +++ b/projectui/src/org/netbeans/modules/project/ui/NewFileWizard.java @@ -143,7 +143,7 @@ protected WizardDescriptor.Panel createTargetChooser() { Sources c = ProjectUtils.getSources(getCurrentProject()); - return Templates.createSimpleTargetChooser(getCurrentProject(), c.getSourceGroups(Sources.TYPE_GENERIC)); + return Templates.buildSimpleTargetChooser(getCurrentProject(), c.getSourceGroups(Sources.TYPE_GENERIC)).create(); } } diff --git a/projectui/src/org/netbeans/modules/project/ui/ProjectChooserFactoryImpl.java b/projectui/src/org/netbeans/modules/project/ui/ProjectChooserFactoryImpl.java --- a/projectui/src/org/netbeans/modules/project/ui/ProjectChooserFactoryImpl.java +++ b/projectui/src/org/netbeans/modules/project/ui/ProjectChooserFactoryImpl.java @@ -62,8 +62,9 @@ return ProjectChooserAccessory.createProjectChooser( false ); } - public @Override WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, WizardDescriptor.Panel bottomPanel) { - return new SimpleTargetChooserPanel( project, folders, bottomPanel, false ); + public @Override WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, + WizardDescriptor.Panel bottomPanel, boolean freeFileExtension) { + return new SimpleTargetChooserPanel(project, folders, bottomPanel, false, freeFileExtension); } public @Override File getProjectsFolder() { diff --git a/projectui/src/org/netbeans/modules/project/ui/ProjectUtilities.java b/projectui/src/org/netbeans/modules/project/ui/ProjectUtilities.java --- a/projectui/src/org/netbeans/modules/project/ui/ProjectUtilities.java +++ b/projectui/src/org/netbeans/modules/project/ui/ProjectUtilities.java @@ -322,7 +322,8 @@ * is allowed in the newObjectName * @return localized error message or null if all right */ - public static String canUseFileName (FileObject targetFolder, String folderName, String newObjectName, String extension, boolean allowFileSeparator) { + public static String canUseFileName (FileObject targetFolder, String folderName, String newObjectName, + String extension, boolean allowFileSeparator, boolean freeFileExtension) { assert newObjectName != null; // SimpleTargetChooserPanel.isValid returns false if it is... XXX should it use an error label instead? boolean allowSlash = false; @@ -372,7 +373,7 @@ } relFileName.append(newObjectName); String ext = ""; - if (extension != null && extension.length() != 0) { + if (extension != null && extension.length() != 0 && (!freeFileExtension || newObjectName.indexOf('.') == -1)) { ext = "." + extension; relFileName.append(ext); } diff --git a/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanel.java b/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanel.java --- a/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanel.java +++ b/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanel.java @@ -69,9 +69,11 @@ private WizardDescriptor.Panel bottomPanel; private WizardDescriptor wizard; private boolean isFolder; + private boolean freeFileExtension; @SuppressWarnings("LeakingThisInConstructor") - SimpleTargetChooserPanel(Project project, SourceGroup[] folders, WizardDescriptor.Panel bottomPanel, boolean isFolder) { + SimpleTargetChooserPanel(Project project, SourceGroup[] folders, + WizardDescriptor.Panel bottomPanel, boolean isFolder, boolean freeFileExtension) { this.folders = folders; if (folders != null && folders.length == 0) { throw new IllegalArgumentException("Attempting to create panel with an empty folders list"); // #161478 @@ -82,12 +84,13 @@ bottomPanel.addChangeListener( this ); } this.isFolder = isFolder; + this.freeFileExtension = freeFileExtension; this.gui = null; } public @Override Component getComponent() { if (gui == null) { - gui = new SimpleTargetChooserPanelGUI( project, folders, bottomPanel == null ? null : bottomPanel.getComponent(), isFolder ); + gui = new SimpleTargetChooserPanelGUI(project, folders, bottomPanel == null ? null : bottomPanel.getComponent(), isFolder, freeFileExtension); gui.addChangeListener(this); } return gui; @@ -117,7 +120,8 @@ // check if the file name can be created FileObject template = Templates.getTemplate( wizard ); - String errorMessage = ProjectUtilities.canUseFileName (gui.getTargetGroup().getRootFolder(), gui.getTargetFolder(), gui.getTargetName(), template.getExt (), isFolder); + String errorMessage = ProjectUtilities.canUseFileName(gui.getTargetGroup().getRootFolder(), + gui.getTargetFolder(), gui.getTargetName(), template.getExt(), isFolder, freeFileExtension); wizard.putProperty(WizardDescriptor.PROP_ERROR_MESSAGE, errorMessage); return errorMessage == null; diff --git a/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanelGUI.java b/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanelGUI.java --- a/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanelGUI.java +++ b/projectui/src/org/netbeans/modules/project/ui/SimpleTargetChooserPanelGUI.java @@ -85,13 +85,15 @@ private final ChangeSupport changeSupport = new ChangeSupport(this); private SourceGroup[] folders; private boolean isFolder; + private boolean freeFileExtension; /** Creates new form SimpleTargetChooserGUI */ @SuppressWarnings("LeakingThisInConstructor") - public SimpleTargetChooserPanelGUI( Project project, SourceGroup[] folders, Component bottomPanel, boolean isFolder ) { + public SimpleTargetChooserPanelGUI( Project project, SourceGroup[] folders, Component bottomPanel, boolean isFolder, boolean freeFileExtension) { this.project = project; this.folders = folders.clone(); this.isFolder = isFolder; + this.freeFileExtension = freeFileExtension; initComponents(); locationComboBox.setRenderer( CELL_RENDERER ); @@ -425,7 +427,7 @@ ( folderName.startsWith("/") || folderName.startsWith( File.separator ) ? "" : "/" ) + // NOI18N folderName + ( folderName.endsWith("/") || folderName.endsWith( File.separator ) || folderName.length() == 0 ? "" : "/" ) + // NOI18N - documentName + expectedExtension; + documentName + (!freeFileExtension || documentName.indexOf('.') == -1 ? expectedExtension : ""); fileTextField.setText( createdFileName.replace( '/', File.separatorChar ) ); // NOI18N diff --git a/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java b/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java --- a/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java +++ b/projectui/test/unit/src/org/netbeans/modules/project/ui/ProjectUtilitiesTest.java @@ -268,31 +268,37 @@ FileObject d = FileUtil.toFileObject(getWorkDir()); FileObject p1 = d.getFileObject("project1"); assertNotNull(p1); - assertNull("normal file addition", ProjectUtilities.canUseFileName(p1, null, "foo", "java", false)); - assertNull("normal file addition with no extension is OK", ProjectUtilities.canUseFileName(p1, null, "foo", null, false)); - assertNull("normal file addition in an existing subdir", ProjectUtilities.canUseFileName(d, "project1", "foo", "java", false)); - assertNull("normal file addition in a new subdir", ProjectUtilities.canUseFileName(d, "dir", "foo", "java", false)); + assertNull("normal file addition", ProjectUtilities.canUseFileName(p1, null, "foo", "java", false, false)); + assertNull("normal file addition with no extension is OK", ProjectUtilities.canUseFileName(p1, null, "foo", null, false, false)); + assertNull("normal file addition in an existing subdir", ProjectUtilities.canUseFileName(d, "project1", "foo", "java", false, false)); + assertNull("normal file addition in a new subdir", ProjectUtilities.canUseFileName(d, "dir", "foo", "java", false, false)); //assertNotNull("no target name", ProjectUtilities.canUseFileName(d, "dir", null, "java")); - assertNotNull("no target folder", ProjectUtilities.canUseFileName(null, "dir", "foo", "java", false)); - assertNotNull("file already exists", ProjectUtilities.canUseFileName(p1, null, "f1_1", "java", false)); - assertNotNull("file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", "java", false)); - assertNull("similar file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", "properties", false)); - assertNull("similar file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", null, false)); + assertNotNull("no target folder", ProjectUtilities.canUseFileName(null, "dir", "foo", "java", false, false)); + assertNotNull("file already exists", ProjectUtilities.canUseFileName(p1, null, "f1_1", "java", false, false)); + assertNotNull("file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", "java", false, false)); + assertNull("similar file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", "properties", false, false)); + assertNull("similar file already exists in subdir", ProjectUtilities.canUseFileName(d, "project1", "f1_1", null, false, false)); d = new XMLFileSystem().getRoot(); - assertNotNull("FS is r/o", ProjectUtilities.canUseFileName(d, null, "foo", "java", false)); + assertNotNull("FS is r/o", ProjectUtilities.canUseFileName(d, null, "foo", "java", false, false)); // #59876: deal with non-disk-based filesystems sensibly d = FileUtil.createMemoryFileSystem().getRoot(); d.createData("bar.java"); FileUtil.createData(d, "sub/dir/foo.java"); - assertNull("can create file in non-disk FS", ProjectUtilities.canUseFileName(d, null, "foo", "java", false)); - assertNotNull("file already exists", ProjectUtilities.canUseFileName(d, null, "bar", "java", false)); - assertNotNull("file already exists in subsubdir", ProjectUtilities.canUseFileName(d, "sub/dir", "foo", "java", false)); - assertNull("can otherwise create file in subsubdir", ProjectUtilities.canUseFileName(d, "sub/dir", "bar", "java", false)); + assertNull("can create file in non-disk FS", ProjectUtilities.canUseFileName(d, null, "foo", "java", false, false)); + assertNotNull("file already exists", ProjectUtilities.canUseFileName(d, null, "bar", "java", false, false)); + assertNotNull("file already exists in subsubdir", ProjectUtilities.canUseFileName(d, "sub/dir", "foo", "java", false, false)); + assertNull("can otherwise create file in subsubdir", ProjectUtilities.canUseFileName(d, "sub/dir", "bar", "java", false, false)); //#66792: allow to create whole directory tree at once using Folder Template: - assertNull("can create directory subtree", ProjectUtilities.canUseFileName(d, null, "a/b/c", null, true)); + assertNull("can create directory subtree", ProjectUtilities.canUseFileName(d, null, "a/b/c", null, true, false)); //#59654: do not allow slash and backslash for common templates: - assertNotNull("cannot create file with slashes", ProjectUtilities.canUseFileName(d, null, "a/b/c", "txt", false)); - assertNotNull("cannot create file with backslashes", ProjectUtilities.canUseFileName(d, null, "a\\b\\c", "txt", false)); + assertNotNull("cannot create file with slashes", ProjectUtilities.canUseFileName(d, null, "a/b/c", "txt", false, false)); + assertNotNull("cannot create file with backslashes", ProjectUtilities.canUseFileName(d, null, "a\\b\\c", "txt", false, false)); + // Check freeFileExtension mode: + assertNull(ProjectUtilities.canUseFileName(d, null, "foo", "java", false, true)); + assertNotNull(ProjectUtilities.canUseFileName(d, null, "bar", "java", false, true)); + assertNotNull(ProjectUtilities.canUseFileName(d, null, "bar.java", "java", false, true)); + assertNull(ProjectUtilities.canUseFileName(d, null, "bar.java", "java", false, false)); + } public void testNavigatorIsNotClosed() throws Exception { diff --git a/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectChooserFactory.java b/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectChooserFactory.java --- a/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectChooserFactory.java +++ b/projectuiapi/src/org/netbeans/modules/project/uiapi/ProjectChooserFactory.java @@ -67,6 +67,7 @@ public JFileChooser createProjectChooser(); - public WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, WizardDescriptor.Panel bottomPanel); + public WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, + WizardDescriptor.Panel bottomPanel, boolean freeFileExtension); } diff --git a/projectuiapi/src/org/netbeans/spi/project/ui/templates/support/Templates.java b/projectuiapi/src/org/netbeans/spi/project/ui/templates/support/Templates.java --- a/projectuiapi/src/org/netbeans/spi/project/ui/templates/support/Templates.java +++ b/projectuiapi/src/org/netbeans/spi/project/ui/templates/support/Templates.java @@ -49,6 +49,7 @@ import org.netbeans.spi.project.ui.support.CommonProjectActions; import org.openide.WizardDescriptor; import org.openide.filesystems.FileObject; +import org.openide.loaders.CreateFromTemplateHandler; import org.openide.loaders.DataFolder; import org.openide.loaders.DataObject; import org.openide.loaders.TemplateWizard; @@ -200,30 +201,80 @@ } /** - * Create a basic target chooser suitable for many kinds of templates. - * The user is prompted to choose a location for the new file and a (base) name. + * @deprecated Use {@link #buildSimpleTargetChooser} instead. + */ + @Deprecated + public static WizardDescriptor.Panel createSimpleTargetChooser( Project project, SourceGroup[] folders ) { + return buildSimpleTargetChooser(project, folders).create(); + } + + /** + * @deprecated Use {@link #buildSimpleTargetChooser} instead. + */ + @Deprecated + public static WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, WizardDescriptor.Panel bottomPanel) { + return buildSimpleTargetChooser(project, folders).bottomPanel(bottomPanel).create(); + } + + /** + * Builder for simple target choosers. + * The chooser is suitable for many kinds of templates. + * The user is prompted to choose a location for the new file and a name. * Instantiation is handled by {@link DataObject#createFromTemplate}. * @param project The project to work on. * @param folders a list of possible roots to create the new file in - * @return a wizard panel(s) prompting the user to choose a name and location + * @return a builder which can be used to customize and then create the target chooser + * @since XXX */ - public static WizardDescriptor.Panel createSimpleTargetChooser( Project project, SourceGroup[] folders ) { - return createSimpleTargetChooser( project, folders, null ); + public static SimpleTargetChooserBuilder buildSimpleTargetChooser(Project project, SourceGroup[] folders) { + return new SimpleTargetChooserBuilder(project, folders); } - + /** - * Create a basic target chooser suitable for many kinds of templates. - * The user is prompted to choose a location for the new file and a (base) name. - * Instantiation is handled by {@link DataObject#createFromTemplate}. - * Resulting panel can be decorated with additional panel placed below the standard target - * chooser. - * @param project The project to work on. - * @param folders a list of possible roots to create the new file in - * @param bottomPanel panel which should be placed underneth the default chooser - * @return a wizard panel(s) prompting the user to choose a name and location + * A builder for simple target choosers. + * @see #buildSimpleTargetChooser + * @since XXX */ - public static WizardDescriptor.Panel createSimpleTargetChooser(Project project, SourceGroup[] folders, WizardDescriptor.Panel bottomPanel) { - return Utilities.getProjectChooserFactory().createSimpleTargetChooser( project, folders, bottomPanel ); + public static final class SimpleTargetChooserBuilder { + final Project project; + final SourceGroup[] folders; + WizardDescriptor.Panel bottomPanel; + boolean freeFileExtension; + SimpleTargetChooserBuilder(Project project, SourceGroup[] folders) { + this.project = project; + this.folders = folders; + } + /** + * Sets a panel which should be placed underneath the default chooser. + * @param bottomPanel a custom bottom panel + * @return this builder + */ + public SimpleTargetChooserBuilder bottomPanel(WizardDescriptor.Panel bottomPanel) { + this.bottomPanel = bottomPanel; + return this; + } + /** + * Permits the file extension of the created file to be customized by the user. + * By default, the file extension is fixed to be the same as that of the template: + * whatever is entered for the filename is taken to be a base name only. + * In this mode, the GUI makes it possible to use an alternate extension: it + * simply checks for a file name containing a period (.) and + * suppresses the automatic appending of the template's extension, + * taking the entered filename as complete. + * @return this builder + * @see CreateFromTemplateHandler#FREE_FILE_EXTENSION + */ + public SimpleTargetChooserBuilder freeFileExtension() { + this.freeFileExtension = true; + return this; + } + /** + * Creates the target chooser panel. + * @return a wizard panel prompting the user to choose a name and location + */ + public WizardDescriptor.Panel create() { + return Utilities.getProjectChooserFactory().createSimpleTargetChooser(project, folders, bottomPanel, freeFileExtension); + } } }