| /* |
| * 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.templatesui; |
| |
| import java.awt.Dimension; |
| import java.awt.EventQueue; |
| import java.io.IOException; |
| import java.io.InterruptedIOException; |
| import java.lang.ref.Reference; |
| import java.lang.ref.WeakReference; |
| import java.lang.reflect.Method; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.NoSuchElementException; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.concurrent.Callable; |
| import java.util.concurrent.ExecutionException; |
| import java.util.concurrent.FutureTask; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.JComponent; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import net.java.html.BrwsrCtx; |
| import net.java.html.js.JavaScriptBody; |
| import net.java.html.json.Model; |
| import net.java.html.json.Models; |
| import netscape.javascript.JSObject; |
| import org.netbeans.api.htmlui.HTMLDialog.Builder; |
| import org.netbeans.api.templates.FileBuilder; |
| import org.openide.WizardDescriptor; |
| import org.openide.WizardValidationException; |
| import org.openide.filesystems.FileObject; |
| import org.openide.loaders.DataObject; |
| import org.openide.loaders.TemplateWizard; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| |
| /** |
| */ |
| @Model(className = "InitWizard", targetId = "", properties = { |
| }) |
| abstract class AbstractWizard |
| implements WizardDescriptor.InstantiatingIterator<WizardDescriptor> { |
| private static final Logger LOG = Logger.getLogger(AbstractWizard.class.getName()); |
| |
| private int index; |
| private List<String> steps = Collections.emptyList(); |
| private List<String> stepNames = Collections.emptyList(); |
| private String current; |
| private Object data; |
| private Object ref; |
| private JComponent p; |
| private BrwsrCtx ctx; |
| private ChangeListener listener; |
| private int errorCode = 0; |
| private WizardDescriptor wizard; |
| |
| protected abstract Object initSequence(ClassLoader l) throws Exception; |
| protected abstract URL initPage(ClassLoader l); |
| protected abstract void initializationDone(Throwable error); |
| protected abstract String[] getTechIds(); |
| |
| @Override |
| public Set<? extends Object> instantiate() throws IOException { |
| try { |
| final TemplateWizard tw = (TemplateWizard) wizard; |
| FutureTask<Map<String,Object>> t = new FutureTask<>(new Callable<Map<String,Object>>() { |
| @Override |
| public Map<String,Object> call() throws Exception { |
| Object[] namesAndValues = rawProps(data); |
| Map<String,Object> map = new TreeMap<>(); |
| for (int i = 0; i < namesAndValues.length; i += 2) { |
| String name = (String) namesAndValues[i]; |
| Object value = namesAndValues[i + 1]; |
| map.put(name, value); |
| } |
| return map; |
| } |
| }); |
| if (ctx != null) { |
| ctx.execute(t); |
| } |
| Map<String, Object> params = new HashMap<>(); |
| |
| for (Map.Entry<String, Object> entry : tw.getProperties().entrySet()) { |
| String key = entry.getKey(); |
| Object value = entry.getValue(); |
| params.put(key, value); |
| } |
| if (ctx != null) { |
| params.put("wizard", t.get()); // NOI18N |
| } |
| |
| List<FileObject> result = new FileBuilder( |
| tw.getTemplate().getPrimaryFile(), |
| tw.getTargetFolder().getPrimaryFile() |
| ). |
| name(tw.getTargetName()). |
| withParameters(params).build(); |
| |
| Set<DataObject> objs = new LinkedHashSet<>(result.size() * 2); |
| for (FileObject fileObject : result) { |
| objs.add(DataObject.find(fileObject)); |
| } |
| return objs; |
| } catch (Exception ex) { |
| throw (IOException)new InterruptedIOException().initCause(ex); |
| } |
| } |
| |
| @Override |
| public void initialize(WizardDescriptor wizard) { |
| this.wizard = wizard; |
| } |
| |
| @Override |
| public void uninitialize(WizardDescriptor wizard) { |
| this.wizard = null; |
| } |
| |
| |
| private List<? extends WizardDescriptor.Panel<WizardDescriptor>> getPanels() { |
| List<WizardDescriptor.Panel<WizardDescriptor>> panels = new ArrayList<>(); |
| fillPanels((TemplateWizard)wizard, this, panels, steps); |
| return Collections.unmodifiableList(panels); |
| } |
| |
| static void fillPanels( |
| TemplateWizard wizard, AbstractWizard aw, |
| List<WizardDescriptor.Panel<WizardDescriptor>> panels, List<String> steps |
| ) { |
| int cnt = steps.size(); |
| if (cnt == 0) { |
| cnt = 1; |
| } |
| for (int i = 0; i < cnt; i++) { |
| if (steps.size() > i) { |
| final String panelName = steps.get(i); |
| if ("targetChooser".equals(panelName)) { // NOI18N |
| panels.add(wizard.targetChooser()); |
| continue; |
| } |
| final String tcPrefix = "targetChooser:"; // NOI18N |
| if (panelName != null && panelName.startsWith(tcPrefix)) { |
| WizardDescriptor.Panel<WizardDescriptor> panel = aw.getChooser(wizard, panelName.substring(tcPrefix.length())); |
| panels.add(panel); |
| continue; |
| } |
| } |
| final HTMLPanel p = new HTMLPanel(i, aw); |
| panels.add(p); |
| } |
| } |
| |
| @Override |
| public WizardDescriptor.Panel<WizardDescriptor> current() { |
| WizardDescriptor.Panel<WizardDescriptor> ret = getPanels().get(index); |
| if (ret.getComponent() != p) { |
| if (ret.getComponent() instanceof JComponent) { |
| JComponent update = (JComponent) ret.getComponent(); |
| update.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, |
| p.getClientProperty(WizardDescriptor.PROP_CONTENT_DATA) |
| ); |
| update.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, |
| p.getClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX) |
| ); |
| } |
| } |
| return ret; |
| } |
| |
| @NbBundle.Messages({ |
| "# {0} - current index", |
| "# {1} - number of panels", |
| "MSG_HTMLWizardName={0} of {1}" |
| }) |
| @Override |
| public String name() { |
| return Bundle.MSG_HTMLWizardName(index + 1, getPanels().size()); |
| } |
| |
| @Override |
| public boolean hasNext() { |
| return index < getPanels().size() - 1; |
| } |
| |
| @Override |
| public boolean hasPrevious() { |
| return index > 0; |
| } |
| |
| @Override |
| public void nextPanel() { |
| if (!hasNext()) { |
| throw new NoSuchElementException(); |
| } |
| index++; |
| onStepsChange(null); |
| } |
| |
| @Override |
| public void previousPanel() { |
| if (!hasPrevious()) { |
| throw new NoSuchElementException(); |
| } |
| index--; |
| onStepsChange(null); |
| } |
| |
| @Override |
| public synchronized void addChangeListener(ChangeListener l) { |
| assert this.listener == null; |
| this.listener = l; |
| } |
| |
| @Override |
| public synchronized void removeChangeListener(ChangeListener l) { |
| if (this.listener == l) { |
| this.listener = null; |
| } |
| } |
| |
| final void fireChange() { |
| final ChangeListener l; |
| synchronized (this) { |
| l = this.listener; |
| notifyAll(); |
| } |
| if (l != null) { |
| EventQueue.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| l.stateChanged(new ChangeEvent(this)); |
| } |
| }); |
| } |
| } |
| |
| final JComponent component(final int index) { |
| if (p == null) { |
| ClassLoader tmpL = Lookup.getDefault().lookup(ClassLoader.class); |
| if (tmpL == null) { |
| tmpL = Thread.currentThread().getContextClassLoader(); |
| } |
| if (tmpL == null) { |
| tmpL = HTMLPanel.class.getClassLoader(); |
| } |
| |
| final ClassLoader l = tmpL; |
| |
| URL u = initPage(l); |
| p = Builder.newDialog(u.toExternalForm()). |
| addTechIds(getTechIds()). |
| loadFinished(new Runnable() { |
| @Override |
| public void run() { |
| try { |
| ctx = BrwsrCtx.findDefault(HTMLPanel.class); |
| Models.toRaw(new InitWizard()); |
| Object ret = initSequence(l); |
| |
| if (ret instanceof String) { |
| data = eval((String) ret); |
| if (data == null || "undefined".equals(data)) { |
| throw new IllegalArgumentException("Executing " + ret + " returned null, that is wrong, should get JSON object with ko bindings"); |
| } |
| } else { |
| if (ret != null && Models.isModel(ret.getClass())) { |
| data = Models.toRaw(ret); |
| ref = ret; |
| } else { |
| throw new IllegalStateException("Returned value should be string or class generated by @Model annotation: " + ret); |
| } |
| } |
| registerStepHandler(data); |
| |
| boolean stepsOK = listenOnProp(data, AbstractWizard.this, "steps"); |
| boolean errorCodeOK = listenOnProp(data, AbstractWizard.this, "errorCode"); |
| |
| applyBindings(data); |
| initializationDone(null); |
| } catch (Exception ex) { |
| initializationDone(ex); |
| } catch (Error ex) { |
| initializationDone(ex); |
| } |
| } |
| }).component(javax.swing.JComponent.class); |
| p.setPreferredSize(new Dimension(500, 340)); |
| p.putClientProperty(WizardDescriptor.PROP_AUTO_WIZARD_STYLE, true); |
| p.putClientProperty(WizardDescriptor.PROP_CONTENT_DISPLAYED, true); |
| p.putClientProperty(WizardDescriptor.PROP_CONTENT_NUMBERED, true); |
| } |
| if (index < stepNames.size()) { |
| p.setName(stepNames.get(index)); |
| } |
| return p; |
| } |
| |
| |
| final void onChange(String prop, Object data) { |
| if ("steps".equals(prop)) { |
| onStepsChange((Object[])data); |
| } |
| if ("errorCode".equals(prop)) { |
| errorCode = data instanceof Number ? ((Number)data).intValue() : -1; |
| fireChange(); |
| } |
| } |
| |
| boolean isValid() { |
| return errorCode == 0; |
| } |
| |
| final Object executeScript(final String code) throws InterruptedException, ExecutionException { |
| FutureTask<?> t = new FutureTask<>(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| return eval(code); |
| } |
| }); |
| ctx.execute(t); |
| Object ret = t.get(); |
| return ret; |
| } |
| final Object evaluateCall(final Object fn, final Object p) throws InterruptedException, ExecutionException { |
| FutureTask<?> t = new FutureTask<Object>(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| JSObject jsRegFn = (JSObject) fn; |
| return jsRegFn.call("call", null, p); |
| } |
| }); |
| ctx.execute(t); |
| return t.get(); |
| } |
| final Object evaluateProp(final String prop) throws InterruptedException, ExecutionException { |
| FutureTask<?> t = new FutureTask<Object>(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| return getPropertyValue(data, prop); |
| } |
| }); |
| ctx.execute(t); |
| return t.get(); |
| } |
| |
| final void setProp(final String prop, final Object value) throws InterruptedException, ExecutionException { |
| FutureTask<?> t = new FutureTask<Object>(new Callable<Object>() { |
| @Override |
| public Object call() throws Exception { |
| return changeProperty(data, prop, value); |
| } |
| }); |
| ctx.execute(t); |
| t.get(); |
| } |
| |
| final Object data() { |
| return data; |
| } |
| |
| final Reference<?> ref() { |
| return new WeakReference<Object>(ref); |
| } |
| |
| final String[] steps(boolean localized) { |
| return (localized ? stepNames : steps).toArray(new String[0]); |
| } |
| |
| final String currentStep() { |
| return current; |
| } |
| |
| @NbBundle.Messages({ |
| "LBL_TemplatesPanel_Name=Choose File Type", |
| "LBL_TargetPanel_Name=Name and Location" |
| }) |
| private void onStepsChange(Object[] obj) { |
| if (obj != null) { |
| List<String> arr = new ArrayList<>(); |
| for (Object s : obj) { |
| arr.add(stringOrId(s, "id", null)); // NOI18N |
| } |
| if (!arr.equals(steps)) { |
| steps = arr; |
| fireChange(); |
| } |
| final List<String> names = new ArrayList<>(); |
| for (Object s : obj) { |
| String id = stringOrId(s, "text", "id"); // NOI18N |
| if (id != null && id.equals("targetChooser") || id.startsWith("targetChooser:")) { // NOI18N |
| id = Bundle.LBL_TargetPanel_Name(); |
| } |
| names.add(id); |
| } |
| stepNames = new ArrayList<>(names); |
| EventQueue.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| p.putClientProperty(WizardDescriptor.PROP_CONTENT_DATA, names.toArray(new String[names.size()])); |
| } |
| }); |
| fireChange(); |
| } |
| EventQueue.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| p.putClientProperty(WizardDescriptor.PROP_CONTENT_SELECTED_INDEX, index); |
| } |
| }); |
| if (steps != null && steps.size() > index) { |
| current = steps.get(index); |
| ctx.execute(new Runnable() { |
| @Override |
| public void run() { |
| changeProperty(data, "current", current); // NOI18N |
| } |
| }); |
| } |
| } |
| |
| boolean validationRequested; |
| boolean prepareValidation() { |
| FutureTask<Boolean> t = new FutureTask<Boolean>(new Callable<Boolean>() { |
| @Override |
| public Boolean call() throws Exception { |
| return validationRequested = callValidate(data); |
| } |
| }); |
| ctx.execute(t); |
| try { |
| return t.get(); |
| } catch (Exception ex) { |
| throw new IllegalStateException(ex); |
| } |
| } |
| |
| synchronized void waitForValidation() throws WizardValidationException { |
| if (!validationRequested) { |
| return; |
| } |
| while (errorCode == -1) { |
| try { |
| wait(); |
| } catch (InterruptedException ex) { |
| LOG.log(Level.INFO, null, ex); |
| } |
| } |
| if (errorCode != 0) { |
| throw new WizardValidationException(p, null, null); |
| } |
| } |
| |
| @JavaScriptBody(args = "code", body = "return 0 || eval(code);") |
| static native Object eval(String code); |
| |
| @JavaScriptBody(args = { "arr" }, body = |
| "for (var i = 0; i < arr.length; i++) {\n" + |
| " arr[i]();\n" + |
| "}\n" + |
| "" |
| ) |
| native void invokeFn(Object[] arr); |
| |
| @JavaScriptBody(args = { "raw" }, body = |
| "if (raw.errorCode() !== -1) return false;" + |
| "if (raw.validate) {" + |
| " raw.validate();" + |
| " return true;" + |
| "}" + |
| "return false;" |
| ) |
| static native boolean callValidate(Object raw); |
| |
| @JavaScriptBody(args = {"data", "onChange", "p" }, |
| javacall = true, body = "" |
| + "if (typeof data[p] !== 'function') {\n" |
| + " throw 'Type of property ' + p + ' should be a function!';\n" |
| + "}\n" |
| + "data[p].subscribe(function(value) {\n" |
| + " onChange.@org.netbeans.modules.templatesui.AbstractWizard::onChange(Ljava/lang/String;Ljava/lang/Object;)(p, value);\n" |
| + "});\n" |
| + "onChange.@org.netbeans.modules.templatesui.AbstractWizard::onChange(Ljava/lang/String;Ljava/lang/Object;)(p, data[p]());\n" |
| + "return true;\n" |
| ) |
| static native boolean listenOnProp( |
| Object raw, AbstractWizard onChange, String propName |
| ); |
| |
| @JavaScriptBody(args = { "raw", "propName", "value" }, body = "" |
| + "var fn = raw[propName];\n" |
| + "if (typeof fn !== 'function') return false;\n" |
| + "fn(value);\n" |
| + "return true;\n" |
| ) |
| private static native boolean changeProperty(Object raw, String propName, Object value); |
| |
| @JavaScriptBody(args = { "fn", "arr" }, body = "" |
| + "return fn.apply(null, arr);" |
| ) |
| private static native Object callFn(Object fn, Object[] arr); |
| |
| @JavaScriptBody(args = { "raw", "propName" }, body = "" |
| + "var fn = raw[propName];\n" |
| + "if (typeof fn !== 'function') return null;\n" |
| + "return fn();\n" |
| ) |
| static native Object getPropertyValue(Object raw, String propName); |
| |
| @JavaScriptBody(args = { "raw" }, body = "" |
| + "var ret = [];\n" |
| + "for (var n in raw) {\n" |
| + " if (n === 'current') continue;\n" |
| + " if (n === 'errorCode') continue;\n" |
| + " if (n === 'steps') continue;\n" |
| + " var fn = raw[n];\n" |
| + " ret.push(n);\n" |
| + " if (typeof fn === 'function') ret.push(fn()); else ret.push(fn);\n" |
| + "}\n" |
| + "return ret;\n" |
| ) |
| static native Object[] rawProps(Object raw); |
| |
| @JavaScriptBody(args = { "obj", "id", "fallback" }, body = |
| "if (typeof obj === 'string') return obj;\n" |
| + "if (obj[id]) return obj[id].toString();\n" |
| + "if (fallback && obj[fallback]) return obj[fallback].toString();\n" |
| + "return null;\n" |
| ) |
| static native String stringOrId(Object obj, String id, String fallback); |
| |
| @JavaScriptBody(args = { "raw" }, body = "" |
| + "var current = raw.current || (raw.current = ko.observable());\n" |
| + "var steps = raw.steps || (raw.steps = ko.observableArray());\n" |
| + "if (!raw.errorCode) raw.errorCode = ko.computed(function() {\n" |
| + " return 1;\n" |
| + "});\n" |
| + "ko.bindingHandlers.step = {\n" |
| + " init : function(element, valueAccessor, allBindings, viewModel, bindingContext) {\n" |
| + " steps.push(valueAccessor());\n" |
| + " },\n" |
| + " update : function(element, valueAccessor, allBindings, viewModel, bindingContext) {\n" |
| + " var v = valueAccessor();\n" |
| + " if (typeof v !== 'string') v = v.id;\n" |
| + " if (current() === v) {\n" |
| + " element.style.display = '';\n" |
| + " } else {\n" |
| + " element.style.display = 'none';\n" |
| + " }\n;\n" |
| + " }\n" |
| + "};\n" |
| ) |
| static native void registerStepHandler(Object raw); |
| |
| @JavaScriptBody(args = { "raw" }, body = "ko.applyBindings(raw);") |
| static native void applyBindings(Object raw); |
| |
| Map<String,WizardDescriptor.Panel<WizardDescriptor>> choosers; |
| WizardDescriptor.Panel<WizardDescriptor> getChooser(TemplateWizard wizard, String type) { |
| if (choosers == null) { |
| choosers = new HashMap<>(); |
| } |
| WizardDescriptor.Panel<WizardDescriptor> panel = choosers.get(type); |
| |
| if (panel == null) { |
| panel = loadPanel(type, wizard); |
| choosers.put(type, panel); |
| } |
| return panel; |
| } |
| |
| private static WizardDescriptor.Panel<WizardDescriptor> loadPanel(String type, TemplateWizard tw) { |
| WizardDescriptor.Panel<WizardDescriptor> panel; |
| try { |
| ClassLoader l = Lookup.getDefault().lookup(ClassLoader.class); |
| if (l == null) { |
| l = Thread.currentThread().getContextClassLoader(); |
| } |
| if (l == null) { |
| l = AbstractWizard.class.getClassLoader(); |
| } |
| Method create; |
| if ("archetype".equals(type)) { // NOI18N |
| Class<?> clazz = Class.forName("org.netbeans.modules.maven.api.archetype.ArchetypeWizards", true, l); // NOI18N |
| create = clazz.getDeclaredMethod("basicWizardPanel", Object.class, String.class); // NOI18N |
| } else { |
| Class<?> clazz = Class.forName("org.netbeans.spi.java.project.support.ui.templates.JavaTemplates", true, l); // NOI18N |
| create = clazz.getDeclaredMethod("createPackageChooser", Object.class, String.class); // NOI18N |
| } |
| create.setAccessible(true); |
| panel = (WizardDescriptor.Panel<WizardDescriptor>) create.invoke(null, tw.getProperty("project"), type); // NOI18N |
| } catch (Throwable t) { |
| LOG.log(Level.WARNING, "Cannot create targetChooser for type " + type + " using default. " |
| + "Don't forget to include org.netbeans.modules.java.project.ui module in your application.", t |
| ); |
| panel = tw.targetChooser(); |
| } |
| return panel; |
| } |
| } |