From 896c26d851c77212308f8b9cf8509bfa6b4921e6 Mon Sep 17 00:00:00 2001 From: Arthur Sadykov Date: Mon, 27 Jul 2020 11:56:37 +0500 Subject: [PATCH] Better Java class wizard A more advanced wizard for creating Java classes that let the user to specify interfaces and superclasses right in the wizard --- java/java.project.ui/nbproject/project.xml | 9 + .../modules/java/project/ui/Bundle.properties | 5 + ...ExtensionAndImplementationVisualPanel.form | 149 ++++++++ ...ExtensionAndImplementationVisualPanel.java | 335 ++++++++++++++++++ ...ExtensionAndImplementationWizardPanel.java | 90 +++++ .../project/ui/NewJavaFileWizardIterator.java | 13 +- .../project/ui/resources/Class.java.template | 24 +- .../data/SampleProject/build.xml | 24 ++ .../nbproject/project.properties | 112 ++++++ .../data/SampleProject/nbproject/project.xml | 34 ++ .../project/ui/NewJavaFileWizardTest.java | 147 ++++++++ 11 files changed, 939 insertions(+), 3 deletions(-) create mode 100644 java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.form create mode 100644 java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.java create mode 100644 java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationWizardPanel.java create mode 100644 java/java.project.ui/test/qa-functional/data/SampleProject/build.xml create mode 100644 java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.properties create mode 100644 java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.xml create mode 100644 java/java.project.ui/test/qa-functional/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardTest.java diff --git a/java/java.project.ui/nbproject/project.xml b/java/java.project.ui/nbproject/project.xml index d19f2ca4eb79..61a19df59f14 100644 --- a/java/java.project.ui/nbproject/project.xml +++ b/java/java.project.ui/nbproject/project.xml @@ -153,6 +153,15 @@ 2.25 + + org.netbeans.modules.java.sourceui + + + + 1 + 1.54 + + org.netbeans.modules.project.ant diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/Bundle.properties b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/Bundle.properties index 3f4718070fd5..6e606346b713 100644 --- a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/Bundle.properties +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/Bundle.properties @@ -96,3 +96,8 @@ LBL_MissingPlatform=The Java Platform called "{1} LBL_CreateNewPlatform=Either create a new Java Platform LBL_UseExistingPlatform=or use an &existing Java Platform: LBL_PlatformHint=Hint: For version controlled projects it''s recommended to create a new Java Platform "{0}". +ExtensionAndImplementationVisualPanel.superclassLabel.text=&Superclass: +ExtensionAndImplementationVisualPanel.superclassTextField.text= +ExtensionAndImplementationVisualPanel.browseSuperclassButton.text=Browse... +ExtensionAndImplementationVisualPanel.interfacesLabel.text=&Interfaces: +ExtensionAndImplementationVisualPanel.browseInterfacesButton.text=Browse... \ No newline at end of file diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.form b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.form new file mode 100644 index 000000000000..b0a219287d7a --- /dev/null +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.form @@ -0,0 +1,149 @@ + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.java b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.java new file mode 100644 index 000000000000..6d18a9dae864 --- /dev/null +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationVisualPanel.java @@ -0,0 +1,335 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.project.ui; + +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.StringTokenizer; +import javax.lang.model.element.ElementKind; +import javax.lang.model.element.TypeElement; +import javax.swing.JPanel; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import javax.swing.event.DocumentEvent; +import javax.swing.event.DocumentListener; +import javax.swing.text.BadLocationException; +import org.netbeans.api.java.source.*; +import org.netbeans.api.java.source.ui.TypeElementFinder; +import org.openide.WizardDescriptor; +import org.openide.util.Exceptions; +import org.openide.util.NbBundle; +import org.openide.util.Utilities; + +/** + * + * @author Arthur Sadykov + */ +public class ExtensionAndImplementationVisualPanel extends JPanel implements DocumentListener { + + private static final String SUPERCLASS = "superclass"; // NOI18N + private static final String INTERFACES = "interfaces"; // NOI18N + private final ExtensionAndImplementationWizardPanel wizardPanel; + + /** + * Creates new form ExtensionPanel + */ + private ExtensionAndImplementationVisualPanel(ExtensionAndImplementationWizardPanel wizardPanel) { + initComponents(); + this.wizardPanel = wizardPanel; + } + + public static ExtensionAndImplementationVisualPanel create(ExtensionAndImplementationWizardPanel wizardPanel) { + ExtensionAndImplementationVisualPanel extensionPanel = new ExtensionAndImplementationVisualPanel(wizardPanel); + extensionPanel.getSuperclassTextField().getDocument().addDocumentListener(extensionPanel); + extensionPanel.getInterfacesTextArea().getDocument().addDocumentListener(extensionPanel); + return extensionPanel; + } + + private JTextField getSuperclassTextField() { + return superclassTextField; + } + + private JTextArea getInterfacesTextArea() { + return interfacesTextArea; + } + + String getSuperclass() { + return superclassTextField.getText(); + } + + String getInterfaces() { + return interfacesTextArea.getText(); + } + + /** + * This method is called from within the constructor to initialize the form. WARNING: Do NOT modify this code. The + * content of this method is always regenerated by the Form Editor. + */ + @SuppressWarnings("unchecked") + // //GEN-BEGIN:initComponents + private void initComponents() { + + superclassLabel = new javax.swing.JLabel(); + interfacesLabel = new javax.swing.JLabel(); + superclassTextField = new javax.swing.JTextField(); + browseSuperclassButton = new javax.swing.JButton(); + browseInterfacesButton = new javax.swing.JButton(); + interfacesScrollPane = new javax.swing.JScrollPane(); + interfacesTextArea = new javax.swing.JTextArea(); + + superclassLabel.setLabelFor(superclassTextField); + org.openide.awt.Mnemonics.setLocalizedText(superclassLabel, org.openide.util.NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, "ExtensionAndImplementationVisualPanel.superclassLabel.text")); // NOI18N + + interfacesLabel.setLabelFor(interfacesTextArea); + org.openide.awt.Mnemonics.setLocalizedText(interfacesLabel, org.openide.util.NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, "ExtensionAndImplementationVisualPanel.interfacesLabel.text")); // NOI18N + + superclassTextField.setEditable(false); + superclassTextField.setText(org.openide.util.NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, "ExtensionAndImplementationVisualPanel.superclassTextField.text")); // NOI18N + + org.openide.awt.Mnemonics.setLocalizedText(browseSuperclassButton, org.openide.util.NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, "ExtensionAndImplementationVisualPanel.browseSuperclassButton.text")); // NOI18N + browseSuperclassButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseSuperclassButtonActionPerformed(evt); + } + }); + + org.openide.awt.Mnemonics.setLocalizedText(browseInterfacesButton, org.openide.util.NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, "ExtensionAndImplementationVisualPanel.browseInterfacesButton.text")); // NOI18N + browseInterfacesButton.addActionListener(new java.awt.event.ActionListener() { + public void actionPerformed(java.awt.event.ActionEvent evt) { + browseInterfacesButtonActionPerformed(evt); + } + }); + + interfacesTextArea.setEditable(false); + interfacesTextArea.setColumns(20); + interfacesTextArea.setRows(5); + interfacesScrollPane.setViewportView(interfacesTextArea); + + javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(superclassLabel) + .addComponent(interfacesLabel)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(interfacesScrollPane, javax.swing.GroupLayout.DEFAULT_SIZE, 253, Short.MAX_VALUE) + .addComponent(superclassTextField)) + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(browseSuperclassButton, javax.swing.GroupLayout.Alignment.TRAILING) + .addComponent(browseInterfacesButton, javax.swing.GroupLayout.Alignment.TRAILING))) + ); + layout.setVerticalGroup( + layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addContainerGap() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(superclassTextField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addComponent(browseSuperclassButton) + .addComponent(superclassLabel)) + .addGap(4, 4, 4) + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(interfacesScrollPane) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addComponent(interfacesLabel) + .addComponent(browseInterfacesButton)) + .addGap(0, 0, Short.MAX_VALUE))) + .addContainerGap()) + ); + }// //GEN-END:initComponents + + private void browseSuperclassButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseSuperclassButtonActionPerformed + ElementHandle handle = TypeElementFinder.find(null, new TypeElementFinder.Customizer() { + @Override + public Set> query(ClasspathInfo classpathInfo, String textForQuery, + ClassIndex.NameKind nameKind, Set searchScopes) { + return classpathInfo.getClassIndex().getDeclaredTypes(textForQuery, nameKind, searchScopes); + } + + @Override + public boolean accept(ElementHandle typeHandle) { + return isNotFinalClass(typeHandle); + } + }); + if (handle != null) { + String fqn = handle.getQualifiedName(); + superclassTextField.setText(fqn); + } + }//GEN-LAST:event_browseSuperclassButtonActionPerformed + + private boolean isNotFinalClass(ElementHandle typeHandle) { + boolean isNotFinal = false; + try { + Class clazz = Class.forName(typeHandle.getQualifiedName()); + int modifiers = clazz.getModifiers(); + isNotFinal = !Modifier.isFinal(modifiers); + } catch (ClassNotFoundException ex) { + } + return typeHandle.getKind() == ElementKind.CLASS && isNotFinal; + } + + private void browseInterfacesButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_browseInterfacesButtonActionPerformed + ElementHandle handle = TypeElementFinder.find(null, new TypeElementFinder.Customizer() { + @Override + public Set> query(ClasspathInfo classpathInfo, String textForQuery, + ClassIndex.NameKind nameKind, Set searchScopes) { + return classpathInfo.getClassIndex().getDeclaredTypes(textForQuery, nameKind, searchScopes); + } + + @Override + public boolean accept(ElementHandle typeHandle) { + return isInterface(typeHandle); + } + }); + if (handle != null) { + String fqn = handle.getQualifiedName(); + if (interfacesTextArea.getText().isEmpty()) { + interfacesTextArea.setText(fqn); + } else { + String interfaces = interfacesTextArea.getText(); + if (!interfaces.contains(fqn)) { + interfacesTextArea.append("\n" + fqn); + } + } + } + }//GEN-LAST:event_browseInterfacesButtonActionPerformed + + private boolean isInterface(ElementHandle typeHandle) { + return typeHandle.getKind() == ElementKind.INTERFACE; + } + + @NbBundle.Messages({ + "INFO_JavaTargetChooser_ProvideValidSuperclass=Provide valid superclass.", + "INFO_JavaTargetChooser_ProvideValidInterfaces=Provide valid interfaces." + }) + boolean isValid(WizardDescriptor wizardDescriptor) { + if (wizardDescriptor == null) { + return false; + } + String superclass = superclassTextField.getText(); + if (!superclass.isEmpty() && !isValidSuperclass(superclass)) { + wizardDescriptor.putProperty(WizardDescriptor.PROP_ERROR_MESSAGE, + NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, + "INFO_JavaTargetChooser_ProvideValidSuperclass")); + return false; + } + String interfaces = interfacesTextArea.getText(); + if (!interfaces.isEmpty() && !isValidInterfaces(interfaces)) { + wizardDescriptor.putProperty(WizardDescriptor.PROP_ERROR_MESSAGE, + NbBundle.getMessage(ExtensionAndImplementationVisualPanel.class, + "INFO_JavaTargetChooser_ProvideValidInterfaces")); + return false; + } + return true; + } + + private boolean isValidSuperclass(String superclass) { + if (!superclass.isEmpty()) { + if (superclass.charAt(0) == '.' || superclass.charAt(superclass.length() - 1) == '.') { + return false; + } + } + return hasValidTokens(superclass); + } + + private boolean hasValidTokens(String fqn) { + StringTokenizer tokenizer = new StringTokenizer(fqn, "."); + while (tokenizer.hasMoreTokens()) { + String token = tokenizer.nextToken(); + if (token.isEmpty()) { + return false; + } + if (!Utilities.isJavaIdentifier(token)) { + return false; + } + } + return true; + } + + private boolean isValidInterfaces(String interfaces) { + if (!interfaces.isEmpty()) { + if (interfaces.charAt(0) == '.' || interfaces.charAt(interfaces.length() - 1) == '.') { + return false; + } + } + for (int i = 0; i < interfacesTextArea.getLineCount(); i++) { + try { + int lineStartOffset = interfacesTextArea.getLineStartOffset(i); + int lineEndOffset = interfacesTextArea.getLineEndOffset(i); + if (lineEndOffset > lineStartOffset) { + String implementationClass = + interfacesTextArea.getText(lineStartOffset, lineEndOffset - lineStartOffset - 1); + if (!hasValidTokens(implementationClass)) { + return false; + } + } + } catch (BadLocationException ex) { + Exceptions.printStackTrace(ex); + } + } + return true; + } + + void readSettings(WizardDescriptor wizardDescriptor) { + String superclass = (String) wizardDescriptor.getProperty(SUPERCLASS); + if (superclass != null) { + superclassTextField.setText(superclass); + } + String interfaces = (String) wizardDescriptor.getProperty(INTERFACES); + if (interfaces != null) { + interfacesTextArea.setText(interfaces); + } + } + + void storeSettings(WizardDescriptor wizardDescriptor) { + String superclass = superclassTextField.getText(); + String interfaces = interfacesTextArea.getText(); + wizardDescriptor.putProperty(SUPERCLASS, superclass); + wizardDescriptor.putProperty(INTERFACES, interfaces); + } + + @Override + public void insertUpdate(DocumentEvent event) { + wizardPanel.fireChangeEvent(); + } + + @Override + public void removeUpdate(DocumentEvent event) { + wizardPanel.fireChangeEvent(); + } + + @Override + public void changedUpdate(DocumentEvent event) { + wizardPanel.fireChangeEvent(); + } + + // Variables declaration - do not modify//GEN-BEGIN:variables + private javax.swing.JButton browseInterfacesButton; + private javax.swing.JButton browseSuperclassButton; + private javax.swing.JLabel interfacesLabel; + private javax.swing.JScrollPane interfacesScrollPane; + private javax.swing.JTextArea interfacesTextArea; + private javax.swing.JLabel superclassLabel; + private javax.swing.JTextField superclassTextField; + // End of variables declaration//GEN-END:variables +} diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationWizardPanel.java b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationWizardPanel.java new file mode 100644 index 000000000000..4c323cde8fcc --- /dev/null +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/ExtensionAndImplementationWizardPanel.java @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.project.ui; + +import java.awt.Component; +import java.util.HashSet; +import java.util.Set; +import javax.swing.event.ChangeEvent; +import javax.swing.event.ChangeListener; +import org.openide.WizardDescriptor; +import org.openide.util.HelpCtx; + +/** + * @author Arthur Sadykov + */ +public class ExtensionAndImplementationWizardPanel implements WizardDescriptor.Panel { + + private ExtensionAndImplementationVisualPanel component; + private WizardDescriptor wizardDescriptor; + private final Set listeners = new HashSet<>(1); + + @Override + public Component getComponent() { + if (component == null) { + component = ExtensionAndImplementationVisualPanel.create(this); + } + return component; + } + + @Override + public HelpCtx getHelp() { + return HelpCtx.DEFAULT_HELP; + } + + @Override + public void readSettings(WizardDescriptor wizardDescriptor) { + this.wizardDescriptor = wizardDescriptor; + component.readSettings(wizardDescriptor); + } + + @Override + public void storeSettings(WizardDescriptor wizardDescriptor) { + component.storeSettings(wizardDescriptor); + } + + @Override + public boolean isValid() { + getComponent(); + return component.isValid(wizardDescriptor); + } + + @Override + public void addChangeListener(ChangeListener listener) { + synchronized (listeners) { + listeners.add(listener); + } + } + + @Override + public void removeChangeListener(ChangeListener listener) { + synchronized (listeners) { + listeners.remove(listener); + } + } + + void fireChangeEvent() { + ChangeEvent event = new ChangeEvent(this); + synchronized (listeners) { + listeners.forEach(listener -> { + listener.stateChanged(event); + }); + } + } +} diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardIterator.java b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardIterator.java index 628afcb044ba..defe83734707 100644 --- a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardIterator.java +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardIterator.java @@ -26,9 +26,11 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.NoSuchElementException; import java.util.Optional; import java.util.Set; @@ -97,6 +99,8 @@ public class NewJavaFileWizardIterator implements WizardDescriptor.AsynchronousInstantiatingIterator { private static final String SOURCE_TYPE_GROOVY = "groovy"; // NOI18N + private static final String SUPERCLASS = "superclass"; // NOI18N + private static final String INTERFACES = "interfaces"; // NOI18N static final String FOLDER = "Classes"; @@ -159,7 +163,7 @@ private WizardDescriptor.Panel[] createPanels ( else { final List> panels = new ArrayList<>(); if (this.type == Type.FILE) { - panels.add(JavaTemplates.createPackageChooser( project, groups )); + panels.add(JavaTemplates.createPackageChooser(project, groups, new ExtensionAndImplementationWizardPanel())); } else if (type == Type.PKG_INFO) { panels.add(new JavaTargetChooserPanel(project, groups, null, Type.PKG_INFO, true)); } else if (type == Type.MODULE_INFO) { @@ -322,7 +326,12 @@ public Set instantiate () throws IOException { createdFile = dobj.getPrimaryFile(); } else { DataObject dTemplate = DataObject.find( template ); - DataObject dobj = dTemplate.createFromTemplate( df, targetName ); + String superclass = (String) wiz.getProperty(SUPERCLASS); + String interfaces = (String) wiz.getProperty(INTERFACES); + Map parameters = new HashMap<>(); + parameters.put(SUPERCLASS, superclass); + parameters.put(INTERFACES, interfaces); + DataObject dobj = dTemplate.createFromTemplate(df, targetName, parameters); createdFile = dobj.getPrimaryFile(); } final Set res = new HashSet<>(); diff --git a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/resources/Class.java.template b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/resources/Class.java.template index e9890cf1ebbf..2cd83a4bd19b 100644 --- a/java/java.project.ui/src/org/netbeans/modules/java/project/ui/resources/Class.java.template +++ b/java/java.project.ui/src/org/netbeans/modules/java/project/ui/resources/Class.java.template @@ -6,11 +6,33 @@ <#if package?? && package != ""> package ${package}; + +<#if superclass?? && superclass != "" && !superclass?starts_with("java.lang") && !superclass?matches(package + "\\.\\w+")> +import ${superclass}; + + +<#if interfaces?? && interfaces != ""> + <#list "${interfaces}"?split("\n") as interface> + <#if interface?? && interface != "" && !interface?starts_with("java.lang") && !interface?matches(package + "\\.\\w+")> +import ${interface}; + + + /** * * @author ${user} */ -public class ${name} { +<#if superclass?? && superclass != ""> + <#assign extension = "${superclass}"[("${superclass}"?last_index_of(".") + 1)..]> + +<#if interfaces?? && interfaces != ""> + <#assign implementation = ""> + <#list "${interfaces}"?split("\n") as interface> + <#assign implementation += "${interface}"[("${interface}"?last_index_of(".") + 1)..] + ", "> + + <#assign implementation = "${implementation}"?remove_ending(", ")> + +public class ${name}<#if extension?? && extension != ""> extends ${extension}<#if implementation?? && implementation != ""> implements ${implementation} { } diff --git a/java/java.project.ui/test/qa-functional/data/SampleProject/build.xml b/java/java.project.ui/test/qa-functional/data/SampleProject/build.xml new file mode 100644 index 000000000000..8cc09d330d3f --- /dev/null +++ b/java/java.project.ui/test/qa-functional/data/SampleProject/build.xml @@ -0,0 +1,24 @@ + + + + + Builds, tests, and runs the project SampleProject. + + diff --git a/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.properties b/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.properties new file mode 100644 index 000000000000..9770653837fb --- /dev/null +++ b/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.properties @@ -0,0 +1,112 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + +annotation.processing.enabled=true +annotation.processing.enabled.in.editor=false +annotation.processing.processors.list= +annotation.processing.run.all.processors=true +annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output +application.title=SampleProject +build.classes.dir=${build.dir}/classes +build.classes.excludes=**/*.java,**/*.form +# This directory is removed when the project is cleaned: +build.dir=build +build.generated.dir=${build.dir}/generated +build.generated.sources.dir=${build.dir}/generated-sources +# Only compile against the classpath explicitly listed here: +build.sysclasspath=ignore +build.test.classes.dir=${build.dir}/test/classes +build.test.results.dir=${build.dir}/test/results +# Uncomment to specify the preferred debugger connection transport: +#debug.transport=dt_socket +debug.classpath=\ + ${run.classpath} +debug.modulepath=\ + ${run.modulepath} +debug.test.classpath=\ + ${run.test.classpath} +debug.test.modulepath=\ + ${run.test.modulepath} +# Files in build.classes.dir which should be excluded from distribution jar +dist.archive.excludes= +# This directory is removed when the project is cleaned: +dist.dir=dist +dist.jar=${dist.dir}/SampleProject.jar +dist.javadoc.dir=${dist.dir}/javadoc +dist.jlink.dir=${dist.dir}/jlink +dist.jlink.output=${dist.jlink.dir}/SampleProject +endorsed.classpath= +excludes= +includes=** +jar.compress=false +javac.classpath= +# Space-separated list of extra javac options +javac.compilerargs= +javac.deprecation=false +javac.external.vm=true +javac.modulepath= +javac.processormodulepath= +javac.processorpath=\ + ${javac.classpath} +javac.source=1.8 +javac.target=1.8 +javac.test.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +javac.test.modulepath=\ + ${javac.modulepath} +javac.test.processorpath=\ + ${javac.test.classpath} +javadoc.additionalparam= +javadoc.author=false +javadoc.encoding=${source.encoding} +javadoc.html5=false +javadoc.noindex=false +javadoc.nonavbar=false +javadoc.notree=false +javadoc.private=false +javadoc.splitindex=true +javadoc.use=true +javadoc.version=false +javadoc.windowtitle= +# The jlink additional root modules to resolve +jlink.additionalmodules= +# The jlink additional command line parameters +jlink.additionalparam= +jlink.launcher=true +jlink.launcher.name=SampleProject +manifest.file=manifest.mf +meta.inf.dir=${src.dir}/META-INF +mkdist.disabled=false +platform.active=default_platform +run.classpath=\ + ${javac.classpath}:\ + ${build.classes.dir} +# Space-separated list of JVM arguments used when running the project. +# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value. +# To set system properties for unit tests define test-sys-prop.name=value: +run.jvmargs= +run.modulepath=\ + ${javac.modulepath} +run.test.classpath=\ + ${javac.test.classpath}:\ + ${build.test.classes.dir} +run.test.modulepath=\ + ${javac.test.modulepath} +source.encoding=UTF-8 +src.dir=src +test.src.dir=test diff --git a/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.xml b/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.xml new file mode 100644 index 000000000000..5733f6b80d03 --- /dev/null +++ b/java/java.project.ui/test/qa-functional/data/SampleProject/nbproject/project.xml @@ -0,0 +1,34 @@ + + + + + org.netbeans.modules.java.j2seproject + + + SampleProject + + + + + + + + + diff --git a/java/java.project.ui/test/qa-functional/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardTest.java b/java/java.project.ui/test/qa-functional/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardTest.java new file mode 100644 index 000000000000..e2bdb5128e0b --- /dev/null +++ b/java/java.project.ui/test/qa-functional/src/org/netbeans/modules/java/project/ui/NewJavaFileWizardTest.java @@ -0,0 +1,147 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.netbeans.modules.java.project.ui; + +import java.io.File; +import java.io.IOException; +import javax.swing.JComboBox; +import javax.swing.JTextArea; +import javax.swing.JTextField; +import junit.framework.Test; +import org.netbeans.jellytools.EditorOperator; +import org.netbeans.jellytools.JellyTestCase; +import org.netbeans.jellytools.NewFileWizardOperator; +import org.netbeans.jellytools.ProjectsTabOperator; +import org.netbeans.jemmy.operators.JComboBoxOperator; +import org.netbeans.jemmy.operators.JLabelOperator; +import org.netbeans.jemmy.operators.JTextAreaOperator; +import org.netbeans.jemmy.operators.JTextFieldOperator; + +/** + * + * @author: Arthur Sadykov + */ +public class NewJavaFileWizardTest extends JellyTestCase { + + private static final String SAMPLE_PROJECT_NAME = "SampleProject"; + private static final String JAVA_CATEGORY = "Java"; + private static final String JAVA_CLASS_FILE_TYPE = "Java Class"; + private static final String SERIALIZABLE_FQN = "java.io.Serializable"; + private static final String OBSERVER_FQN = "java.util.Observer"; + private static final String INTERFACES_LABEL_TEXT = "Interfaces:"; + private static final String SUPERCLASS_LABEL_TEXT = "Superclass:"; + private static final String SAMPLE_PACKAGE_NAME = "sample"; + private static final String MAIN_CLASS_NAME = "Main"; + private static final String PACKAGE_LABEL_TEXT = "Package:"; + private static final String CLASS_NAME_LABEL_TEXT = "Class Name:"; + private static final String STACK_FQN = "java.util.Stack"; + private static final String THREAD_FQN = "java.lang.Thread"; + private static final String CLONEABLE_FQN = "java.lang.Cloneable"; + private static final String IMPORT_STACK = "import java.util.Stack;"; + private static final String IMPORT_SERIALIZABLE = "import java.io.Serializable;"; + private static final String IMPORT_OBSERVER = "import java.util.Observer;"; + private static final String IMPORT_THREAD = "import java.lang.Thread;"; + private static final String IMPORT_CLONEABLE = "import java.lang.Cloneable;"; + private static final String BASE_CLASS_FQN = "sample.BaseClass"; + private static final String IMPORT_BASE_CLASS = "import sample.BaseClass;"; + private JTextFieldOperator superclassTextFieldOperator; + private JTextAreaOperator interfacesTextAreaOperator; + private NewFileWizardOperator wizardOperator; + + public NewJavaFileWizardTest(String testName) { + super(testName); + } + + public static Test suite() { + return createModuleTest(NewJavaFileWizardTest.class); + } + + @Override + public void setUp() throws IOException { + clearWorkDir(); + openDataProjects(SAMPLE_PROJECT_NAME); + ProjectsTabOperator.invoke(); + wizardOperator = NewFileWizardOperator.invoke(); + wizardOperator.selectProject(SAMPLE_PROJECT_NAME); + wizardOperator.selectCategory(JAVA_CATEGORY); + wizardOperator.selectFileType(JAVA_CLASS_FILE_TYPE); + wizardOperator.next(); + JLabelOperator classNameLabelOperator = new JLabelOperator(wizardOperator, CLASS_NAME_LABEL_TEXT); + JTextFieldOperator classNameTextFieldOperator = + new JTextFieldOperator((JTextField) classNameLabelOperator.getLabelFor()); + classNameTextFieldOperator.setText(MAIN_CLASS_NAME); + JLabelOperator packageLabelOperator = new JLabelOperator(wizardOperator, PACKAGE_LABEL_TEXT); + JComboBoxOperator packageComboBoxOperator = + new JComboBoxOperator((JComboBox) packageLabelOperator.getLabelFor()); + packageComboBoxOperator.typeText(SAMPLE_PACKAGE_NAME); + JLabelOperator superclassLabelOperator = new JLabelOperator(wizardOperator, SUPERCLASS_LABEL_TEXT); + superclassTextFieldOperator = new JTextFieldOperator((JTextField) superclassLabelOperator.getLabelFor()); + JLabelOperator interfacesLabelOperator = + new JLabelOperator(wizardOperator, INTERFACES_LABEL_TEXT); + interfacesTextAreaOperator = + new JTextAreaOperator((JTextArea) interfacesLabelOperator.getLabelFor()); + } + + public void testShouldAddImportsExtendsAndImplementsClauses() { + superclassTextFieldOperator.setText(STACK_FQN); + interfacesTextAreaOperator.append(SERIALIZABLE_FQN); + interfacesTextAreaOperator.append("\n"); + interfacesTextAreaOperator.append(OBSERVER_FQN); + wizardOperator.finish(); + EditorOperator editorOperator = new EditorOperator(MAIN_CLASS_NAME); + String text = editorOperator.getText(); + assertTrue("Generated class should contain import for java.util.Stack", text.contains(IMPORT_STACK)); + assertTrue("Generated class should contain import for java.io.Serializable", text.contains(IMPORT_SERIALIZABLE)); + assertTrue("Generated class should contain import for java.util.Observer", text.contains(IMPORT_OBSERVER)); + String expectedClassSignature = "public class Main extends Stack implements Serializable, Observer"; + assertTrue("Generated class should have expected signature", text.contains(expectedClassSignature)); + } + + public void testShouldNotAddImportsForClassesFromJavaLangPackage() { + superclassTextFieldOperator.setText(THREAD_FQN); + interfacesTextAreaOperator.setText(CLONEABLE_FQN); + wizardOperator.finish(); + EditorOperator editorOperator = new EditorOperator(MAIN_CLASS_NAME); + String text = editorOperator.getText(); + assertTrue("Generated class should not contain import for java.lang.Thread", !text.contains(IMPORT_THREAD)); + assertTrue("Generated class should not contain import for java.lang.Cloneable", !text.contains(IMPORT_CLONEABLE)); + String expectedClassSignature = "public class Main extends Thread implements Cloneable"; + assertTrue("Generated class should have expected signature", text.contains(expectedClassSignature)); + } + + public void testShouldNotAddImportsForClassesFromSamePackage() { + superclassTextFieldOperator.setText(BASE_CLASS_FQN); + wizardOperator.finish(); + EditorOperator editorOperator = new EditorOperator(MAIN_CLASS_NAME); + String text = editorOperator.getText(); + assertTrue("Generated class should not contain import for sample.BaseClass", !text.contains(IMPORT_BASE_CLASS)); + String expectedClassSignature = "public class Main extends Base"; + assertTrue("Generated class should have expected signature", text.contains(expectedClassSignature)); + } + + @Override + protected void tearDown() throws Exception { + closeOpenedProjects(); + String separator = File.separator; + String testFileName = getDataDir().getAbsolutePath() + separator + SAMPLE_PROJECT_NAME + separator + "src" + + separator + SAMPLE_PACKAGE_NAME + separator + MAIN_CLASS_NAME + ".java"; + File testFile = new File(testFileName); + testFile.delete(); + } +}