blob: edcc561fd02eec73e0886c844a8c1896462925e2 [file] [log] [blame]
/*
* 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 com.sun.source.tree.ModuleTree;
import java.awt.Component;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Optional;
import java.util.Set;
import javax.swing.JComponent;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.java.classpath.ClassPath;
import org.netbeans.api.java.classpath.JavaClassPathConstants;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.queries.BinaryForSourceQuery;
import org.netbeans.api.java.queries.UnitTestForSourceQuery;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.java.source.SourceUtils;
import org.netbeans.api.java.source.TreeMaker;
import org.netbeans.api.java.source.WorkingCopy;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
import org.netbeans.api.project.SourceGroup;
import org.netbeans.api.project.Sources;
import org.netbeans.api.templates.TemplateRegistration;
import org.netbeans.api.templates.TemplateRegistrations;
import org.netbeans.spi.java.project.support.ui.templates.JavaTemplates;
import org.netbeans.spi.project.ui.templates.support.Templates;
import org.openide.WizardDescriptor;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataFolder;
import org.openide.loaders.DataObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle.Messages;
import org.netbeans.spi.java.project.support.ui.templates.JavaFileWizardIteratorFactory;
import org.openide.util.NbBundle;
/**
* Wizard to create a new Java file.
*/
@TemplateRegistrations({
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 100, content = "resources/Class.java.template", scriptEngine = "freemarker", displayName = "#Class.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/Class.html", category = {"java-classes", "java-classes-basic"}),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 200, content = "resources/Interface.java.template", scriptEngine = "freemarker", displayName = "#Interface.java", iconBase = JavaTemplates.INTERFACE_ICON, description = "resources/Interface.html", category = {"java-classes", "java-classes-basic"}),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 300, content = "resources/Enum.java.template", scriptEngine = "freemarker", displayName = "#Enum.java", iconBase = JavaTemplates.ENUM_ICON, description = "resources/Enum.html", category = {"java-classes", NewJavaFileWizardIterator.JDK_5}),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 400, content = "resources/AnnotationType.java.template", scriptEngine = "freemarker", displayName = "#AnnotationType.java", iconBase = JavaTemplates.ANNOTATION_TYPE_ICON, description = "resources/AnnotationType.html", category = {"java-classes", NewJavaFileWizardIterator.JDK_5}),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 600, content = "resources/Exception.java.template", scriptEngine = "freemarker", displayName = "#Exception.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/Exception.html", category = {"java-classes", "java-classes-basic"}),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 700, content = "resources/JApplet.java.template", scriptEngine = "freemarker", displayName = "#JApplet.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/JApplet.html", category = "java-classes"),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 800, content = "resources/Applet.java.template", scriptEngine = "freemarker", displayName = "#Applet.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/Applet.html", category = "java-classes"),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 900, content = "resources/Main.java.template", scriptEngine = "freemarker", displayName = "#Main.java", iconBase = "org/netbeans/modules/java/project/ui/resources/main-class.png", description = "resources/Main.html", category = "java-main-class"),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 950, content = "resources/Singleton.java.template", scriptEngine = "freemarker", displayName = "#Singleton.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/Singleton.html", category = "java-classes"),
@TemplateRegistration(folder = NewJavaFileWizardIterator.FOLDER, position = 1000, content = "resources/Empty.java.template", scriptEngine = "freemarker", displayName = "#Empty.java", iconBase = JavaTemplates.JAVA_ICON, description = "resources/Empty.html", category = {"java-classes", "java-classes-basic"})
})
@Messages({
"Class.java=Java Class",
"Interface.java=Java Interface",
"Enum.java=Java Enum",
"AnnotationType.java=Java Annotation Type",
"Exception.java=Java Exception",
"JApplet.java=JApplet",
"Applet.java=Applet",
"Main.java=Java Main Class",
"Singleton.java=Java Singleton Class",
"Empty.java=Empty Java File"
})
public class NewJavaFileWizardIterator implements WizardDescriptor.AsynchronousInstantiatingIterator<WizardDescriptor> {
private static final String SOURCE_TYPE_GROOVY = "groovy"; // NOI18N
static final String FOLDER = "Classes";
static final String JDK_5 = "jdk5";
static final String JDK_9 = "jdk9";
private static final long serialVersionUID = 1L;
public enum Type {FILE, PACKAGE, PKG_INFO, MODULE_INFO}
private final Type type;
/** Create a new wizard iterator. */
public NewJavaFileWizardIterator() {
this(Type.FILE);
}
private NewJavaFileWizardIterator(Type type) {
this.type = type;
}
@TemplateRegistration(folder = FOLDER, id="Package", position = 1100, displayName = "#packageWizard", iconBase = PackageDisplayUtils.PACKAGE, description = "resources/Package.html", category = {"java-classes", "java-classes-basic"})
@Messages("packageWizard=Java Package")
public static NewJavaFileWizardIterator packageWizard() {
return new NewJavaFileWizardIterator(Type.PACKAGE);
}
@TemplateRegistration(folder = FOLDER, position = 650, content = "resources/package-info.java.template", scriptEngine = "freemarker", displayName = "#packageInfoWizard", iconBase = JavaTemplates.JAVA_ICON, description = "resources/package-info.html", category = {"java-classes", JDK_5})
@Messages("packageInfoWizard=Java Package Info")
public static NewJavaFileWizardIterator packageInfoWizard () {
return new NewJavaFileWizardIterator(Type.PKG_INFO);
}
@TemplateRegistration(folder = FOLDER, position = 670, content = "resources/module-info.java.template", scriptEngine = "freemarker", displayName = "#moduleInfoWizard", iconBase = JavaTemplates.JAVA_ICON, description = "resources/module-info.html", category = {"java-classes", JDK_9})
@Messages("moduleInfoWizard=Java Module Info")
public static NewJavaFileWizardIterator moduleInfoWizard () {
return new NewJavaFileWizardIterator(Type.MODULE_INFO);
}
private WizardDescriptor.Panel[] createPanels (
@NonNull final WizardDescriptor wizardDescriptor,
@NonNull final WizardDescriptor.Iterator<WizardDescriptor> it) {
// Ask for Java folders
Project project = Templates.getProject( wizardDescriptor );
if (project == null) throw new NullPointerException ("No project found for: " + wizardDescriptor);
Sources sources = ProjectUtils.getSources(project);
SourceGroup[] groups = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA);
assert groups != null : "Cannot return null from Sources.getSourceGroups: " + sources;
groups = checkNotNull (groups, sources);
if (groups.length == 0) {
groups = sources.getSourceGroups( Sources.TYPE_GENERIC );
groups = checkNotNull (groups, sources);
return new WizardDescriptor.Panel[] {
Templates.buildSimpleTargetChooser(project, groups).create(),
};
}
else {
final List<WizardDescriptor.Panel<?>> panels = new ArrayList<>();
if (this.type == Type.FILE) {
panels.add(JavaTemplates.createPackageChooser( project, groups ));
} else if (type == Type.PKG_INFO) {
panels.add(new JavaTargetChooserPanel(project, groups, null, Type.PKG_INFO, true));
} else if (type == Type.MODULE_INFO) {
panels.add(new JavaTargetChooserPanel(project, groups, null, Type.MODULE_INFO, false));
} else {
assert type == Type.PACKAGE;
SourceGroup[] groovySourceGroups = sources.getSourceGroups(SOURCE_TYPE_GROOVY);
if (groovySourceGroups.length > 0) {
List<SourceGroup> all = new ArrayList<>();
all.addAll(Arrays.asList(groups));
all.addAll(Arrays.asList(groovySourceGroups));
groups = all.toArray(new SourceGroup[all.size()]);
}
SourceGroup[] resources = sources.getSourceGroups(JavaProjectConstants.SOURCES_TYPE_RESOURCES);
assert resources != null;
if (resources.length > 0) { // #161244
List<SourceGroup> all = new ArrayList<SourceGroup>();
all.addAll(Arrays.asList(groups));
all.addAll(Arrays.asList(resources));
groups = all.toArray(new SourceGroup[all.size()]);
}
panels.add(new JavaTargetChooserPanel(project, groups, null, Type.PACKAGE, false));
}
if (it != null) {
if (it.current() != null) {
panels.add(it.current());
}
while(it.hasNext()) {
it.nextPanel();
panels.add(it.current());
}
}
return panels.toArray(new WizardDescriptor.Panel<?>[panels.size()]);
}
}
private static SourceGroup[] checkNotNull (SourceGroup[] groups, Sources sources) {
List<SourceGroup> sourceGroups = new ArrayList<SourceGroup> ();
for (SourceGroup sourceGroup : groups)
if (sourceGroup == null)
Exceptions.printStackTrace (new NullPointerException (sources + " returns null SourceGroup!"));
else
sourceGroups.add (sourceGroup);
return sourceGroups.toArray (new SourceGroup [sourceGroups.size ()]);
}
private String[] createSteps(String[] before, WizardDescriptor.Panel[] panels) {
assert panels != null;
// hack to use the steps set before this panel processed
int diff = 0;
if (before == null) {
before = new String[0];
} else if (before.length > 0) {
diff = ("...".equals (before[before.length - 1])) ? 1 : 0; // NOI18N
}
String[] res = new String[ (before.length - diff) + panels.length];
for (int i = 0; i < res.length; i++) {
if (i < (before.length - diff)) {
res[i] = before[i];
} else {
res[i] = panels[i - before.length + diff].getComponent ().getName ();
}
}
return res;
}
private void addRequires(FileObject createdFile, Set<String> requiredModuleNames) throws IOException {
if (requiredModuleNames == null) {
requiredModuleNames = new LinkedHashSet<>();
ClassPath modulePath = ClassPath.getClassPath(createdFile, JavaClassPathConstants.MODULE_COMPILE_PATH);
for (FileObject root : modulePath.getRoots()) {
String name = SourceUtils.getModuleName(root.toURL(), true);
if (name != null) {
requiredModuleNames.add(name);
}
}
}
if (!requiredModuleNames.isEmpty()) {
final JavaSource src = JavaSource.forFileObject(createdFile);
if (src != null) {
final Set<String> mNames = requiredModuleNames;
src.runModificationTask((WorkingCopy copy) -> {
copy.toPhase(JavaSource.Phase.RESOLVED);
TreeMaker tm = copy.getTreeMaker();
ModuleTree modle = (ModuleTree) copy.getCompilationUnit().getTypeDecls().get(0);
ModuleTree newModle = modle;
for (String mName : mNames) {
newModle = tm.addModuleDirective(newModle, tm.Requires(false, false, tm.QualIdent(mName)));
}
copy.rewrite(modle, newModle);
}).commit();
}
}
}
@Override
public Set<FileObject> instantiate () throws IOException {
FileObject dir = Templates.getTargetFolder( wiz );
String targetName = Templates.getTargetName( wiz );
DataFolder df = DataFolder.findFolder( dir );
FileObject template = Templates.getTemplate( wiz );
FileObject createdFile = null;
Set<String> requiredModuleNames = null;
if (this.type == Type.PACKAGE) {
targetName = targetName.replace( '.', '/' ); // NOI18N
createdFile = FileUtil.createFolder( dir, targetName );
} else if (this.type == Type.MODULE_INFO) {
Project project = Templates.getProject( wiz );
URL[] srcs = UnitTestForSourceQuery.findSources(dir);
if (srcs.length > 0) {
requiredModuleNames = new LinkedHashSet<>();
for (int i = 0; i < srcs.length; i++) {
for (URL root : BinaryForSourceQuery.findBinaryRoots(srcs[i]).getRoots()) {
String mName = SourceUtils.getModuleName(root, true);
if (mName != null) {
requiredModuleNames.add(mName);
}
}
}
}
final String moduleName;
final ClassPath msp = ClassPath.getClassPath(dir, JavaClassPathConstants.MODULE_SOURCE_PATH);
if (msp == null) {
//Single module project
moduleName = createModuleName(
ProjectUtils.getInformation(project).getDisplayName(),
srcs.length > 0);
} else {
//Multi module project
final String path = msp.getResourceName(dir);
if (path == null) {
moduleName = null;
} else {
final int index = path.indexOf('/'); //NOI18N
moduleName = index < 0 ?
path :
path.substring(0, index);
}
}
DataObject dTemplate = DataObject.find( template );
DataObject dobj = dTemplate.createFromTemplate(
df,
targetName,
Collections.singletonMap("moduleName", moduleName)); //NOI18N
createdFile = dobj.getPrimaryFile();
} else {
DataObject dTemplate = DataObject.find( template );
DataObject dobj = dTemplate.createFromTemplate( df, targetName );
createdFile = dobj.getPrimaryFile();
}
final Set<FileObject> res = new HashSet<>();
res.add(createdFile);
asInstantiatingIterator(projectSpecificIterator)
.map((it)->{
try {
return it.instantiate();
} catch (IOException ioe) {
Exceptions.printStackTrace(ioe);
return null;
}
})
.ifPresent(res::addAll);
if (this.type == Type.MODULE_INFO) {
addRequires(createdFile, requiredModuleNames);
}
return Collections.unmodifiableSet(res);
}
@Messages("TXT_Test=Test")
private static String createModuleName (final String projectName, boolean isTest) {
final StringBuilder name = new StringBuilder();
StringBuilder sb = new StringBuilder();
boolean first = true;
boolean needsEscape = false;
String part;
for (int i=0; i< projectName.length(); i++) {
final char c = projectName.charAt(i);
if (first) {
if (!Character.isJavaIdentifierStart(c)) {
if (Character.isJavaIdentifierPart(c)) {
needsEscape = true;
sb.append(c);
first = false;
}
} else {
sb.append(c);
first = false;
}
} else {
if (Character.isJavaIdentifierPart(c) ) {
sb.append(c);
} else if (sb.length() > 0) {
part = sb.toString();
if (!needsEscape || name.length()>0) {
name.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1));
}
sb = new StringBuilder();
first = true;
needsEscape = false;
}
}
}
if (sb.length()>0) {
part = sb.toString();
if (!needsEscape || name.length()>0) {
name.append(Character.toUpperCase(part.charAt(0))).append(part.substring(1));
}
}
if (isTest) {
name.append(Bundle.TXT_Test());
}
return name.toString();
}
@NonNull
private static Optional<WizardDescriptor.InstantiatingIterator<WizardDescriptor>> asInstantiatingIterator(
@NullAllowed final WizardDescriptor.Iterator<WizardDescriptor> it) {
return Optional.ofNullable(it)
.map((p)->{
return p instanceof WizardDescriptor.InstantiatingIterator ?
(WizardDescriptor.InstantiatingIterator<WizardDescriptor>) p:
null;
});
}
private transient int index;
private transient WizardDescriptor.Panel[] panels;
private transient WizardDescriptor wiz;
private transient WizardDescriptor.Iterator<WizardDescriptor> projectSpecificIterator;
@Override
public void initialize(WizardDescriptor wiz) {
this.wiz = wiz;
index = 0;
final Project project = Templates.getProject(wiz);
final JavaFileWizardIteratorFactory templateProvider = project != null ? project.getLookup().lookup(JavaFileWizardIteratorFactory.class) : null;
if (templateProvider != null) {
projectSpecificIterator = templateProvider.createIterator(Templates.getTemplate(wiz));
asInstantiatingIterator(projectSpecificIterator)
.ifPresent((it)->it.initialize(wiz));
}
panels = createPanels(wiz, projectSpecificIterator);
// Make sure list of steps is accurate.
String[] beforeSteps = null;
Object prop = wiz.getProperty(WizardDescriptor.PROP_CONTENT_DATA);
if (prop != null && prop instanceof String[]) {
beforeSteps = (String[])prop;
}
String[] steps = createSteps (beforeSteps, panels);
for (int i = 0; i < panels.length; i++) {
Component c = panels[i].getComponent();
if (steps[i] == null) {
// Default step name to component name of panel.
// Mainly useful for getting the name of the target
// chooser to appear in the list of steps.
steps[i] = c.getName();
}
if (c instanceof JComponent) { // assume Swing components
JComponent jc = (JComponent)c;
// Step #.
jc.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, new Integer(i));
// Step name (actually the whole list for reference).
jc.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, steps);
}
}
}
@Override
public void uninitialize (WizardDescriptor wiz) {
asInstantiatingIterator(projectSpecificIterator)
.ifPresent((it)->it.uninitialize(wiz));
this.wiz = null;
panels = null;
projectSpecificIterator = null;
}
@Override
public String name() {
//return "" + (index + 1) + " of " + panels.length;
return ""; // NOI18N
}
@Override
public boolean hasNext() {
return index < panels.length - 1;
}
@Override
public boolean hasPrevious() {
return index > 0;
}
@Override
public void nextPanel() {
if (!hasNext()) throw new NoSuchElementException();
index++;
}
@Override
public void previousPanel() {
if (!hasPrevious()) throw new NoSuchElementException();
index--;
}
@Override
public WizardDescriptor.Panel current() {
return panels[index];
}
private final transient Set<ChangeListener> listeners = new HashSet<ChangeListener>(1);
@Override
public final void addChangeListener(ChangeListener l) {
synchronized(listeners) {
listeners.add(l);
}
}
@Override
public final void removeChangeListener(ChangeListener l) {
synchronized(listeners) {
listeners.remove(l);
}
}
protected final void fireChangeEvent() {
ChangeListener[] ls;
synchronized (listeners) {
ls = listeners.toArray(new ChangeListener[listeners.size()]);
}
ChangeEvent ev = new ChangeEvent(this);
for (ChangeListener l : ls) {
l.stateChanged(ev);
}
}
}