All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.javafx2.project.JFXActionProvider Maven / Gradle / Ivy

There is a newer version: RELEASE220
Show newest version
/*
 * 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 ACTIONS = new HashMap(){
        {
            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 RUN_ACTIONS = new HashMap(){
        {
            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 targets;
                    final Map> 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 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> loadTargetsFromConfig(
            @NonNull final Project project) {
        HashMap> 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 targetNames = new ArrayList(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 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 collectAdditionalBuildProperties(
        @NonNull final Map p,
        @NonNull final String command,
        @NonNull final Lookup context) {
        final Set concealedProperties = new HashSet<>();
        for (J2SEBuildPropertiesProvider pp : prj.getLookup().lookupAll(J2SEBuildPropertiesProvider.class)) {
            final Map 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 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 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 args = new ArrayList();

        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 factories = mgr.getEngineFactories();
        for (ScriptEngineFactory factory: factories) {
            List 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 roots = JFXProjectUtils.getClassPathMap(prj).keySet();
        final Set appClassNames;
        if (FXinSwing) {
            appClassNames = JFXProjectUtils.getMainClassNames(prj);
        } else {
            final AtomicBoolean cancel = new AtomicBoolean();
            final AtomicReference> result = new AtomicReference<>(Collections.emptySet());
            try {
            BaseProgressUtils.runOffEventDispatchThread(new Runnable() {
                @Override
                public void run() {
                    if (!cancel.get()) {
                        final Set 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
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy