| /* |
| * 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.javafx2.project; |
| |
| import java.awt.Dialog; |
| import java.awt.event.MouseEvent; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Set; |
| import java.util.StringTokenizer; |
| import java.util.concurrent.atomic.AtomicBoolean; |
| import java.util.concurrent.atomic.AtomicReference; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.script.ScriptEngineFactory; |
| import javax.script.ScriptEngineManager; |
| import javax.swing.JButton; |
| import javax.swing.event.ChangeEvent; |
| import javax.swing.event.ChangeListener; |
| import org.apache.tools.ant.module.api.support.ActionUtils; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.annotations.common.NullAllowed; |
| import org.netbeans.api.extexecution.startup.StartupExtender; |
| import org.netbeans.api.progress.BaseProgressUtils; |
| import org.netbeans.api.project.Project; |
| import org.netbeans.api.project.ProjectUtils; |
| import org.netbeans.api.scripting.Scripting; |
| import org.netbeans.modules.java.api.common.project.ProjectProperties; |
| import org.netbeans.modules.java.j2seproject.api.J2SEBuildPropertiesProvider; |
| import org.netbeans.modules.java.j2seproject.api.J2SEPropertyEvaluator; |
| import org.netbeans.modules.javafx2.project.ui.JFXApplicationClassChooser; |
| import org.netbeans.modules.javafx2.project.ui.JFXRunPanel; |
| import org.netbeans.spi.project.ActionProgress; |
| import org.netbeans.spi.project.ActionProvider; |
| import org.netbeans.spi.project.LookupProvider; |
| import org.netbeans.spi.project.ProjectServiceProvider; |
| import org.netbeans.spi.project.support.ant.GeneratedFilesHelper; |
| import org.netbeans.spi.project.support.ant.PropertyEvaluator; |
| import org.openide.DialogDescriptor; |
| import org.openide.DialogDisplayer; |
| import org.openide.NotifyDescriptor; |
| import org.openide.awt.MouseUtils; |
| import org.openide.execution.ExecutorTask; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.Exceptions; |
| import org.openide.util.Lookup; |
| import org.openide.util.NbBundle; |
| import org.openide.util.Parameters; |
| import org.openide.util.Task; |
| import org.openide.util.TaskListener; |
| |
| /** |
| * JFX Action Provider |
| */ |
| @ProjectServiceProvider( |
| service=ActionProvider.class, |
| projectTypes={@LookupProvider.Registration.ProjectType(id="org-netbeans-modules-java-j2seproject",position=90)}) |
| public class JFXActionProvider implements ActionProvider { |
| |
| private static final Logger LOG = Logger.getLogger(JFXActionProvider.class.getName()); |
| |
| private final Project prj; |
| private boolean isJSAvailable = true; |
| private boolean isJSAvailableChecked = false; |
| |
| private static final Map<String,String> ACTIONS = new HashMap<String,String>(){ |
| { |
| put(COMMAND_BUILD,"jfx-build"); //NOI18N |
| put(COMMAND_REBUILD,"jfx-rebuild"); //NOI18N |
| put(COMMAND_RUN,"run"); //NOI18N |
| put(COMMAND_DEBUG,"debug"); //NOI18N |
| put(COMMAND_PROFILE,"profile"); //NOI18N |
| } |
| }; |
| |
| private static final Map<String,String> RUN_ACTIONS = new HashMap<String,String>(){ |
| { |
| put(COMMAND_RUN,"run"); //NOI18N |
| put(COMMAND_DEBUG,"debug"); //NOI18N |
| put(COMMAND_PROFILE,"profile"); //NOI18N |
| } |
| }; |
| |
| public JFXActionProvider(@NonNull final Project project) { |
| Parameters.notNull("project", project); // NOI18N |
| this.prj = project; |
| } |
| |
| @Override |
| @NonNull |
| public String[] getSupportedActions() { |
| return ACTIONS.keySet().toArray(new String[ACTIONS.size()]); |
| } |
| |
| @Override |
| public void invokeAction(@NonNull String command, @NonNull Lookup context) throws IllegalArgumentException { |
| if (command != null) { |
| if(RUN_ACTIONS.containsKey(command) && JFXProjectUtils.isFXPreloaderProject(prj)) { |
| NotifyDescriptor d = |
| new NotifyDescriptor.Message(NbBundle.getMessage(JFXActionProvider.class,"WARN_PreloaderExecutionUnsupported", // NOI18N |
| ProjectUtils.getInformation(prj).getDisplayName()), NotifyDescriptor.INFORMATION_MESSAGE); |
| DialogDisplayer.getDefault().notify(d); |
| return; |
| } |
| FileObject buildFo = findBuildXml(); |
| assert buildFo != null && buildFo.isValid(); |
| String noScript = isJavaScriptAvailable() ? "" : "-noscript"; // NOI18N |
| String runAs = JFXProjectUtils.getFXProjectRunAs(prj); |
| if(runAs == null) { |
| runAs = JFXProjectProperties.RunAsType.STANDALONE.getString(); |
| } |
| |
| Properties props = verifyApplicationClass(); |
| if(props != null) { |
| final ActionProgress listener = ActionProgress.start(context); |
| try { |
| List<String> targets; |
| final Map<String,List<String>> targetReplacements = loadTargetsFromConfig(prj); |
| targets = targetReplacements.get(command); |
| if (targets == null) { |
| if(command.equalsIgnoreCase(COMMAND_BUILD) || command.equalsIgnoreCase(COMMAND_REBUILD)) { |
| targets = Collections.singletonList(ACTIONS.get(command).concat(noScript)); // NOI18N |
| } else { |
| if(runAs.equalsIgnoreCase(JFXProjectProperties.RunAsType.STANDALONE.getString())) { |
| targets = Collections.singletonList("jfxsa-".concat(ACTIONS.get(command)).concat(noScript)); //NOI18N |
| } else { |
| if(runAs.equalsIgnoreCase(JFXProjectProperties.RunAsType.ASWEBSTART.getString())) { |
| targets = Collections.singletonList("jfxws-".concat(ACTIONS.get(command)).concat(noScript)); //NOI18N |
| } else { //JFXProjectProperties.RunAsType.INBROWSER |
| targets = Collections.singletonList("jfxbe-".concat(ACTIONS.get(command)).concat(noScript)); //NOI18N |
| } |
| } |
| } |
| } |
| |
| collectStartupExtenderArgs(props, command, context); |
| final Set<String> concealedProperties = collectAdditionalBuildProperties(props, command, context); |
| ActionUtils.runTarget( |
| buildFo, |
| targets.toArray(new String[targets.size()]), |
| props, |
| concealedProperties).addTaskListener(new TaskListener() { |
| @Override public void taskFinished(Task task) { |
| listener.finished(((ExecutorTask) task).result() == 0); |
| } |
| }); |
| } catch (IOException ex) { |
| Exceptions.printStackTrace(ex); |
| listener.finished(false); |
| } |
| } |
| } else { |
| throw new IllegalArgumentException(command); |
| } |
| } |
| |
| @Override |
| public boolean isActionEnabled(@NonNull String command, @NonNull Lookup context) throws IllegalArgumentException { |
| if (isFXProject(prj)) { |
| if (findBuildXml() == null) { |
| return false; |
| } |
| return findTarget(command) != null; |
| } |
| return false; |
| } |
| |
| @NonNull |
| private static String getBuildXmlName (@NonNull final PropertyEvaluator evaluator) { |
| String buildScriptPath = evaluator.getProperty("buildfile"); //NOI18N |
| if (buildScriptPath == null) { |
| buildScriptPath = GeneratedFilesHelper.BUILD_XML_PATH; |
| } |
| return buildScriptPath; |
| } |
| |
| @NonNull |
| private static HashMap<String,List<String>> loadTargetsFromConfig( |
| @NonNull final Project project) { |
| HashMap<String,List<String>> targets = new HashMap<>(6); |
| final J2SEPropertyEvaluator ep = project.getLookup().lookup(J2SEPropertyEvaluator.class); |
| final PropertyEvaluator evaluator = ep.evaluator(); |
| String config = evaluator.getProperty(ProjectProperties.PROP_PROJECT_CONFIGURATION_CONFIG); |
| // load targets from shared config |
| FileObject propFO = project.getProjectDirectory().getFileObject("nbproject/configs/" + config + ".properties"); |
| if (propFO == null) { |
| return targets; |
| } |
| Properties props = new Properties(); |
| try (final InputStream is = propFO.getInputStream()) { |
| props.load(is); |
| } catch (IOException ex) { |
| LOG.warning(ex.getMessage()); |
| return targets; |
| } |
| Enumeration<?> propNames = props.propertyNames(); |
| while (propNames.hasMoreElements()) { |
| String propName = (String) propNames.nextElement(); |
| if (propName.startsWith("$target.")) { |
| String tNameVal = props.getProperty(propName); |
| if (tNameVal != null && !tNameVal.equals("")) { |
| String cmdNameKey = propName.substring("$target.".length()); |
| StringTokenizer stok = new StringTokenizer(tNameVal.trim(), " "); |
| List<String> targetNames = new ArrayList<String>(3); |
| while (stok.hasMoreTokens()) { |
| targetNames.add(stok.nextToken()); |
| } |
| targets.put( |
| cmdNameKey, |
| targetNames.isEmpty() ? null : targetNames); |
| } |
| } |
| } |
| return targets; |
| } |
| |
| @CheckForNull |
| private FileObject findBuildXml () { |
| final J2SEPropertyEvaluator ep = prj.getLookup().lookup(J2SEPropertyEvaluator.class); |
| assert ep != null; |
| return prj.getProjectDirectory().getFileObject (getBuildXmlName(ep.evaluator())); |
| } |
| |
| @CheckForNull |
| private static String findTarget(@NonNull final String command) { |
| return ACTIONS.get(command); |
| } |
| |
| private void collectStartupExtenderArgs(Map<? super String,? super String> p, String command, Lookup context) { |
| StringBuilder b = new StringBuilder(); |
| for (String arg : runJvmargsIde(command, context)) { |
| b.append(' ').append(arg); // NOI18N |
| } |
| if (b.length() > 0) { |
| p.put("run.jvmargs.ide", b.toString()); // NOI18N |
| } |
| } |
| |
| @NonNull |
| private Set<String> collectAdditionalBuildProperties( |
| @NonNull final Map<? super String, ? super String> p, |
| @NonNull final String command, |
| @NonNull final Lookup context) { |
| final Set<String> concealedProperties = new HashSet<>(); |
| for (J2SEBuildPropertiesProvider pp : prj.getLookup().lookupAll(J2SEBuildPropertiesProvider.class)) { |
| final Map<String,String> contrib = pp.createAdditionalProperties(command, context); |
| assert contrib != null; |
| if (LOG.isLoggable(Level.FINE)) { |
| LOG.log( |
| Level.FINE, |
| "J2SEBuildPropertiesProvider: {0} added following build properties: {1}", //NOI18N |
| new Object[]{ |
| pp.getClass(), |
| contrib |
| }); |
| } |
| p.putAll(contrib); |
| final Set<String> concealedContrib = pp.createConcealedProperties(command, context); |
| assert concealedContrib != null; |
| if (LOG.isLoggable(Level.FINE)) { |
| LOG.log( |
| Level.FINE, |
| "J2SEBuildPropertiesProvider: {0} added following concealed properties: {1}", //NOI18N |
| new Object[]{ |
| pp.getClass(), |
| concealedContrib |
| }); |
| } |
| concealedProperties.addAll(concealedContrib); |
| } |
| return Collections.unmodifiableSet(concealedProperties); |
| } |
| |
| private List<String> runJvmargsIde(String command, Lookup context) { |
| StartupExtender.StartMode mode; |
| if (command.equals(COMMAND_RUN) || command.equals(COMMAND_RUN_SINGLE)) { |
| mode = StartupExtender.StartMode.NORMAL; |
| } else if (command.equals(COMMAND_DEBUG) || command.equals(COMMAND_DEBUG_SINGLE) || command.equals(COMMAND_DEBUG_STEP_INTO)) { |
| mode = StartupExtender.StartMode.DEBUG; |
| } else if (command.equals(COMMAND_PROFILE) || command.equals(COMMAND_PROFILE_SINGLE)) { |
| mode = StartupExtender.StartMode.PROFILE; |
| } else if (command.equals(COMMAND_TEST) || command.equals(COMMAND_TEST_SINGLE)) { |
| mode = StartupExtender.StartMode.TEST_NORMAL; |
| } else if (command.equals(COMMAND_DEBUG_TEST_SINGLE)) { |
| mode = StartupExtender.StartMode.TEST_DEBUG; |
| } else if (command.equals(COMMAND_PROFILE_TEST_SINGLE)) { |
| mode = StartupExtender.StartMode.TEST_PROFILE; |
| } else { |
| return Collections.emptyList(); |
| } |
| List<String> args = new ArrayList<String>(); |
| |
| for (StartupExtender group : StartupExtender.getExtenders(context, mode)) { |
| args.addAll(group.getArguments()); |
| } |
| return args; |
| } |
| |
| private boolean isJavaScriptAvailable() { |
| if(isJSAvailableChecked) { |
| return isJSAvailable; |
| } |
| ScriptEngineManager mgr = Scripting.createManager(); |
| List<ScriptEngineFactory> factories = mgr.getEngineFactories(); |
| for (ScriptEngineFactory factory: factories) { |
| List<String> engNames = factory.getNames(); |
| for(String name: engNames) { |
| if(name.equalsIgnoreCase("js") || name.equalsIgnoreCase("javascript")) { //NOI18N |
| isJSAvailableChecked = true; |
| isJSAvailable = true; |
| return isJSAvailable; |
| } |
| } |
| } |
| isJSAvailableChecked = true; |
| isJSAvailable = false; |
| return isJSAvailable; |
| } |
| |
| /** |
| * Verify that the currently selected Application class exists. If not, |
| * offer a chooser dialog to select among existing ones. If no valid choice |
| * is made, return false. Otherwise, continue with the current choice. |
| * @return true if current Application class is valid, false otherwise |
| */ |
| private Properties verifyApplicationClass() { |
| final JButton okButton = new JButton (NbBundle.getMessage (JFXRunPanel.class, "LBL_ChooseMainClass_OK")); // NOI18N |
| okButton.getAccessibleContext().setAccessibleDescription (NbBundle.getMessage (JFXRunPanel.class, "AD_ChooseMainClass_OK")); // NOI18N |
| final boolean FXinSwing = JFXProjectUtils.isFXinSwingProject(prj); |
| final Collection<? extends FileObject> roots = JFXProjectUtils.getClassPathMap(prj).keySet(); |
| final Set<String> appClassNames; |
| if (FXinSwing) { |
| appClassNames = JFXProjectUtils.getMainClassNames(prj); |
| } else { |
| final AtomicBoolean cancel = new AtomicBoolean(); |
| final AtomicReference<Set<String>> result = new AtomicReference<>(Collections.<String>emptySet()); |
| try { |
| BaseProgressUtils.runOffEventDispatchThread(new Runnable() { |
| @Override |
| public void run() { |
| if (!cancel.get()) { |
| final Set<String> appClasses = JFXProjectUtils.getAppClassNames(roots, "javafx.application.Application"); //NOI18N |
| result.set(appClasses); |
| } |
| } |
| }, NbBundle.getMessage(JFXActionProvider.class, "TXT_ActionInProgress"), cancel, true); //NOI18N |
| } catch (IllegalStateException ex) { |
| LOG.log(Level.INFO, "Canceled operation did not finish in time.", ex); //NOI18N |
| } |
| appClassNames = result.get(); |
| } |
| final PropertyEvaluator eval = prj.getLookup().lookup(J2SEPropertyEvaluator.class).evaluator(); |
| |
| String appClassName = eval.getProperty(FXinSwing ? ProjectProperties.MAIN_CLASS : JFXProjectProperties.MAIN_CLASS); |
| Properties props = new Properties(); |
| if (!JFXProjectUtils.isFXPreloaderProject(prj) && (appClassName == null || !appClassNames.contains(appClassName))) { |
| final JFXApplicationClassChooser panel = new JFXApplicationClassChooser(prj, eval); |
| Object[] options = new Object[] { |
| okButton, |
| DialogDescriptor.CANCEL_OPTION |
| }; |
| panel.addChangeListener (new ChangeListener () { |
| @Override |
| public void stateChanged(ChangeEvent e) { |
| if (e.getSource () instanceof MouseEvent && MouseUtils.isDoubleClick (((MouseEvent)e.getSource ()))) { |
| // click button and finish the dialog with selected class |
| okButton.doClick (); |
| } else { |
| okButton.setEnabled (panel.getSelectedClass () != null); |
| } |
| } |
| }); |
| okButton.setEnabled (false); |
| DialogDescriptor desc = new DialogDescriptor ( |
| panel, |
| NbBundle.getMessage (JFXRunPanel.class, FXinSwing ? "LBL_ChooseMainClass_Title_Swing" : "LBL_ChooseMainClass_Title" ), // NOI18N |
| true, |
| options, |
| options[0], |
| DialogDescriptor.BOTTOM_ALIGN, |
| null, |
| null); |
| //desc.setMessageType (DialogDescriptor.INFORMATION_MESSAGE); |
| Dialog dlg = DialogDisplayer.getDefault ().createDialog (desc); |
| dlg.setVisible (true); |
| if (desc.getValue() == options[0]) { |
| try { |
| JFXProjectUtils.updatePropertyInActiveConfig(prj, FXinSwing ? ProjectProperties.MAIN_CLASS : JFXProjectProperties.MAIN_CLASS, panel.getSelectedClass()); |
| props.setProperty(FXinSwing ? ProjectProperties.MAIN_CLASS : JFXProjectProperties.MAIN_CLASS, panel.getSelectedClass() ); |
| } catch(IOException e) { |
| props = null; |
| } |
| } else { |
| props = null; |
| } |
| dlg.dispose(); |
| } |
| return props; |
| } |
| |
| private static boolean isFXProject(@NonNull final Project prj) { |
| final J2SEPropertyEvaluator eval = prj.getLookup().lookup(J2SEPropertyEvaluator.class); |
| if (eval == null) { |
| return false; |
| } |
| //Don't use JFXProjectProperties.isTrue to prevent JFXProjectProperties from being loaded |
| //JFXProjectProperties.JAVAFX_ENABLED is inlined by compliler |
| return isTrue(eval.evaluator().getProperty(JFXProjectProperties.JAVAFX_ENABLED)); |
| } |
| |
| private static boolean isTrue(@NullAllowed final String value) { |
| return value != null && ( |
| "true".equalsIgnoreCase(value) || //NOI18N |
| "yes".equalsIgnoreCase(value) || //NOI18N |
| "on".equalsIgnoreCase(value)); //NOI18N |
| } |
| } |