Package org.freeplane.plugin.script

Source Code of org.freeplane.plugin.script.ScriptingConfiguration

/*
*  Freeplane - mind map editor
*  Copyright (C) 2009 Volker Boerchers
*
*  This file author is Volker Boerchers
*
*  This program is free software: you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation, either version 2 of the License, or
*  (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.plugin.script;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.script.ScriptEngineFactory;

import org.apache.commons.lang.StringUtils;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.components.UITools;
import org.freeplane.core.util.ConfigurationUtils;
import org.freeplane.core.util.FileUtils;
import org.freeplane.core.util.HtmlUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.MenuUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.features.mode.Controller;
import org.freeplane.main.addons.AddOnProperties;
import org.freeplane.main.addons.AddOnProperties.AddOnType;
import org.freeplane.main.addons.AddOnsController;
import org.freeplane.plugin.script.ExecuteScriptAction.ExecutionMode;
import org.freeplane.plugin.script.addons.ScriptAddOnProperties;
import org.freeplane.plugin.script.addons.ScriptAddOnProperties.Script;

/**
* scans for scripts to be registered via {@link ScriptingRegistration}.
*
* @author Volker Boerchers
*/
class ScriptingConfiguration {
  static class ScriptMetaData {
    private final TreeMap<ExecutionMode, String> executionModeLocationMap = new TreeMap<ExecutionMode, String>();
    private final TreeMap<ExecutionMode, String> executionModeTitleKeyMap = new TreeMap<ExecutionMode, String>();
    private boolean cacheContent = false;
    private final String scriptName;
    private ScriptingPermissions permissions;

    ScriptMetaData(final String scriptName) {
      this.scriptName = scriptName;
      executionModeLocationMap.put(ExecutionMode.ON_SINGLE_NODE, null);
      executionModeLocationMap.put(ExecutionMode.ON_SELECTED_NODE, null);
      executionModeLocationMap.put(ExecutionMode.ON_SELECTED_NODE_RECURSIVELY, null);
    }

    public Set<ExecutionMode> getExecutionModes() {
      return executionModeLocationMap.keySet();
    }

    public void addExecutionMode(final ExecutionMode executionMode, final String location, final String titleKey) {
      executionModeLocationMap.put(executionMode, location);
      if (titleKey != null)
        executionModeTitleKeyMap.put(executionMode, titleKey);
    }

    public void removeExecutionMode(final ExecutionMode executionMode) {
      executionModeLocationMap.remove(executionMode);
    }

    public void removeAllExecutionModes() {
      executionModeLocationMap.clear();
    }

    protected String getMenuLocation(final ExecutionMode executionMode) {
      return executionModeLocationMap.get(executionMode);
    }

    public String getTitleKey(final ExecutionMode executionMode) {
      final String key = executionModeTitleKeyMap.get(executionMode);
      return key == null ? getExecutionModeKey(executionMode) : key;
    }

    public boolean cacheContent() {
      return cacheContent;
    }

    public void setCacheContent(final boolean cacheContent) {
      this.cacheContent = cacheContent;
    }

    public String getScriptName() {
      return scriptName;
    }

    public void setPermissions(ScriptingPermissions permissions) {
      this.permissions = permissions;
        }

    public ScriptingPermissions getPermissions() {
          return permissions;
        }
  }

  static final String MENU_SCRIPTS_LOCATION = "main_menu_scripting";
  static final String CONTEXT_MENU_SCRIPTS_LOCATIONS = "node_popup_scripting";
  private static final String JAR_REGEX = ".+\\.jar$";
  private final TreeMap<String, String> menuTitleToPathMap = new TreeMap<String, String>();
  private final TreeMap<String, ScriptMetaData> menuTitleToMetaDataMap = new TreeMap<String, ScriptMetaData>();
    private static Map<String, Object> staticProperties = createStaticProperties();

  ScriptingConfiguration() {
      ScriptResources.setClasspath(buildClasspath());
    addPluginDefaults();
    initMenuTitleToPathMap();
  }

  private void addPluginDefaults() {
    final URL defaults = this.getClass().getResource(ResourceController.PLUGIN_DEFAULTS_RESOURCE);
    if (defaults == null)
      throw new RuntimeException("cannot open " + ResourceController.PLUGIN_DEFAULTS_RESOURCE);
    Controller.getCurrentController().getResourceController().addDefaults(defaults);
  }

  private void initMenuTitleToPathMap() {
    final Map<File, Script> addOnScriptMap = createAddOnScriptMap();
    addAddOnScripts(addOnScriptMap);
    addNonAddOnScripts(addOnScriptMap);
  }

    private void addAddOnScripts(Map<File, Script> addOnScriptMap) {
        for (File file : addOnScriptMap.keySet()) {
            addScript(file, addOnScriptMap);
        }
    }

    private void addNonAddOnScripts(final Map<File, Script> addOnScriptMap) {
        final FilenameFilter scriptFilenameFilter = createFilenameFilter(createScriptRegExp());
    for (File dir : getScriptDirs()) {
            addNonAddOnScripts(dir, addOnScriptMap, scriptFilenameFilter);
    }
    }

  private Map<File, Script> createAddOnScriptMap() {
    Map<File, Script> result = new LinkedHashMap<File, Script>();
    for (ScriptAddOnProperties scriptAddOnProperties : getInstalledScriptAddOns()) {
            final List<Script> scripts = scriptAddOnProperties.getScripts();
            for (Script script : scripts) {
                script.active = scriptAddOnProperties.isActive();
                result.put(findScriptFile(scriptAddOnProperties, script), script);
            }
    }
    return result;
    }

    private List<ScriptAddOnProperties> getInstalledScriptAddOns() {
        final List<ScriptAddOnProperties> installedAddOns = new ArrayList<ScriptAddOnProperties>();
    for (AddOnProperties addOnProperties : AddOnsController.getController().getInstalledAddOns()) {
          if (addOnProperties.getAddOnType() == AddOnType.SCRIPT) {
            installedAddOns.add((ScriptAddOnProperties) addOnProperties);
          }
        }
        return installedAddOns;
    }

    private File findScriptFile(AddOnProperties addOnProperties, Script script) {
        final File dir = new File(getPrivateAddOnDirectory(addOnProperties), "scripts");
        final File result = new File(dir, script.name);
        return result.exists() ? result : findScriptFile_pre_1_3_x_final(script);
    }

    private File getPrivateAddOnDirectory(AddOnProperties addOnProperties) {
        return new File(AddOnsController.getController().getAddOnsDir(), addOnProperties.getName());
    }

    // add-on scripts are installed in a add-on-private directory since 1.3.x_beta
    @Deprecated
    private File findScriptFile_pre_1_3_x_final(Script script) {
        return new File(ScriptResources.getUserScriptsDir(), script.name);
    }

  private TreeSet<File> getScriptDirs() {
    final ResourceController resourceController = ResourceController.getResourceController();
    final String dirsString = resourceController.getProperty(ScriptResources.RESOURCES_SCRIPT_DIRECTORIES);
    final TreeSet<File> dirs = new TreeSet<File>(); // remove duplicates -> Set
    if (dirsString != null) {
      for (String dir : ConfigurationUtils.decodeListValue(dirsString, false)) {
          dirs.add(createFile(dir));
            }
    }
    dirs.add(ScriptResources.getBuiltinScriptsDir());
    dirs.add(ScriptResources.getUserScriptsDir());
    return dirs;
  }

  /**
   * if <code>path</code> is not an absolute path, prepends the freeplane user
   * directory to it.
   */
  private File createFile(final String path) {
    File file = new File(path);
    if (!file.isAbsolute()) {
      file = new File(ResourceController.getResourceController().getFreeplaneUserDirectory(), path);
    }
    return file;
  }

    /** scans <code>dir</code> for script files matching a given rexgex. */
    private void addNonAddOnScripts(final File dir, final Map<File, Script> addOnScriptMap,
                            FilenameFilter filenameFilter) {
        // add all addOn scripts
        // find further scripts in configured directories
        if (dir.isDirectory()) {
            final File[] files = dir.listFiles(filenameFilter);
            if (files != null) {
                for (final File file : files) {
                    if (addOnScriptMap.get(file) == null)
                        addScript(file, addOnScriptMap);
                }
            }
        }
        else {
            LogUtils.warn("not a (script) directory: " + dir);
        }
    }

    private String createScriptRegExp() {
        final ArrayList<String> extensions = new ArrayList<String>();
//        extensions.add("clj");
        for (ScriptEngineFactory scriptEngineFactory : GenericScript.getScriptEngineManager().getEngineFactories()) {
            extensions.addAll(scriptEngineFactory.getExtensions());
        }
        LogUtils.info("looking for scripts with the following endings: " + extensions);
        return ".+\\.(" + StringUtils.join(extensions, "|") + ")$";
    }

  private FilenameFilter createFilenameFilter(final String regexp) {
    final FilenameFilter filter = new FilenameFilter() {
      public boolean accept(final File dir, final String name) {
        return name.matches(regexp);
      }
    };
    return filter;
  }

  private void addScript(final File file, final Map<File, Script> addOnScriptMap) {
    final Script scriptConfig = addOnScriptMap.get(file);
    if (scriptConfig != null && !scriptConfig.active) {
      LogUtils.info("skipping deactivated " + scriptConfig);
      return;
    }
    final String menuTitle = disambiguateMenuTitle(getOrCreateMenuTitle(file, scriptConfig));
    try {
      menuTitleToPathMap.put(menuTitle, file.getAbsolutePath());
      final ScriptMetaData metaData = createMetaData(file, menuTitle, scriptConfig);
      menuTitleToMetaDataMap.put(menuTitle, metaData);
      final File parentFile = file.getParentFile();
      if (parentFile.equals(ScriptResources.getBuiltinScriptsDir())) {
        metaData.setPermissions(ScriptingPermissions.getPermissiveScriptingPermissions());
      }
    }
    catch (final IOException e) {
      LogUtils.warn("problems with script " + file.getAbsolutePath(), e);
      menuTitleToPathMap.remove(menuTitle);
      menuTitleToMetaDataMap.remove(menuTitle);
    }
  }

    private String disambiguateMenuTitle(final String menuTitleOrig) {
        String menuTitle = menuTitleOrig;
    // add suffix if the same script exists in multiple dirs
    for (int i = 2; menuTitleToPathMap.containsKey(menuTitle); ++i) {
      menuTitle = menuTitleOrig + i;
    }
        return menuTitle;
    }

    private ScriptMetaData createMetaData(final File file, final String scriptName,
                                          final Script scriptConfig) throws IOException {
        return scriptConfig == null ? analyseScriptContent(FileUtils.slurpFile(file), scriptName) //
                : createMetaData(scriptName, scriptConfig);
    }

  // not private to enable tests
  ScriptMetaData analyseScriptContent(final String content, final String scriptName) {
    final ScriptMetaData metaData = new ScriptMetaData(scriptName);
    if (ScriptingConfiguration.firstCharIsEquals(content)) {
      // would make no sense
      metaData.removeExecutionMode(ExecutionMode.ON_SINGLE_NODE);
    }
    setExecutionModes(content, metaData);
    setCacheMode(content, metaData);
    return metaData;
  }
 
  private ScriptMetaData createMetaData(final String scriptName, final Script scriptConfig) {
    final ScriptMetaData metaData = new ScriptMetaData(scriptName);
    metaData.removeAllExecutionModes();
    metaData.addExecutionMode(scriptConfig.executionMode, scriptConfig.menuLocation, scriptConfig.menuTitleKey);
//    metaData.setCacheContent(true);
    metaData.setPermissions(scriptConfig.permissions);
    return metaData;
  }

  private void setCacheMode(final String content, final ScriptMetaData metaData) {
    final Pattern cacheScriptPattern = ScriptingConfiguration
        .makeCaseInsensitivePattern("@CacheScriptContent\\s*\\(\\s*(true|false)\\s*\\)");
    final Matcher matcher = cacheScriptPattern.matcher(content);
    if (matcher.find()) {
      metaData.setCacheContent(new Boolean(matcher.group(1)));
    }
  }

  public static void setExecutionModes(final String content, final ScriptMetaData metaData) {
    final String modeName = StringUtils.join(ExecutionMode.values(), "|");
    final String modeDef = "(?:ExecutionMode\\.)?(" + modeName + ")(?:=\"([^]\"]+)(?:\\[([^]\"]+)\\])?\")?";
    final String modeDefs = "(?:" + modeDef + ",?)+";
    final Pattern pOuter = makeCaseInsensitivePattern("@ExecutionModes\\(\\{(" + modeDefs + ")\\}\\)");
    final Matcher mOuter = pOuter.matcher(content.replaceAll("\\s+", ""));
    if (!mOuter.find()) {
//      System.err.println(metaData.getScriptName() + ": '" + pOuter + "' did not match "
//              + content.replaceAll("\\s+", ""));
      return;
    }
    metaData.removeAllExecutionModes();
    final Pattern pattern = makeCaseInsensitivePattern(modeDef);
    final String[] locations = mOuter.group(1).split(",");
    for (String match : locations) {
      final Matcher m = pattern.matcher(match);
      if (m.matches()) {
//        System.err.println(metaData.getScriptName() + ":" + m.group(1) + "->" + m.group(2) + "->" + m.group(3));
                metaData.addExecutionMode(ExecutionMode.valueOf(m.group(1).toUpperCase(Locale.ENGLISH)), m.group(2),
                    m.group(3));
      }
      else {
        LogUtils.severe("script " + metaData.getScriptName() + ": not a menu location: '" + match + "'");
        continue;
      }
    }
  }

  private static boolean firstCharIsEquals(final String content) {
    return content.length() == 0 ? false : content.charAt(0) == '=';
  }

  /** some beautification: remove directory and suffix + make first letter uppercase. */
  private String getOrCreateMenuTitle(final File file, Script scriptConfig) {
    if (scriptConfig != null)
      return scriptConfig.menuTitleKey;
    // TODO: we could add mnemonics handling here! (e.g. by reading '_' as '&')
    String string = file.getName().replaceFirst("\\.[^.]+", "");
    // fixup characters that might cause problems in menus
    string = string.replaceAll("\\s+", "_");
    return string.length() < 2 ? string : string.substring(0, 1).toUpperCase() + string.substring(1);
  }

  private static Pattern makeCaseInsensitivePattern(final String regexp) {
    return Pattern.compile(regexp, Pattern.CASE_INSENSITIVE | Pattern.DOTALL);
  }

  SortedMap<String, String> getMenuTitleToPathMap() {
    return Collections.unmodifiableSortedMap(menuTitleToPathMap);
  }

  SortedMap<String, ScriptMetaData> getMenuTitleToMetaDataMap() {
    return Collections.unmodifiableSortedMap(menuTitleToMetaDataMap);
  }

  private ArrayList<String> buildClasspath() {
      final ArrayList<String> classpath = new ArrayList<String>();
      addClasspathForAddOns(classpath);
        addClasspathForConfiguredEntries(classpath);
        return classpath;
    }

    private void addClasspathForAddOns(final ArrayList<String> classpath) {
        final List<ScriptAddOnProperties> installedScriptAddOns = getInstalledScriptAddOns();
        for (ScriptAddOnProperties scriptAddOnProperties : installedScriptAddOns) {
            final List<String> lib = scriptAddOnProperties.getLib();
            if (lib != null) {
                for (String libEntry : lib) {
                    final File dir = new File(getPrivateAddOnDirectory(scriptAddOnProperties), "lib");
                    classpath.add(new File(dir, libEntry).getAbsolutePath());
                }
            }
        }
    }

    private void addClasspathForConfiguredEntries(final ArrayList<String> classpath) {
        for (File classpathElement : uniqueClassPathElements(ResourceController.getResourceController())) {
            addClasspathElement(classpath, classpathElement);
        }
    }

    private Set<File> uniqueClassPathElements(final ResourceController resourceController) {
        final String classpathString = resourceController.getProperty(ScriptResources.RESOURCES_SCRIPT_CLASSPATH);
        final TreeSet<File> classpathElements = new TreeSet<File>();
        if (classpathString != null) {
            for (String string : ConfigurationUtils.decodeListValue(classpathString, false)) {
                classpathElements.add(createFile(string));
            }
        }
        classpathElements.add(ScriptResources.getUserLibDir());
        return classpathElements;
    }

    private void addClasspathElement(final ArrayList<String> classpath, File classpathElement) {
        final File file = classpathElement;
        if (!file.exists()) {
            LogUtils.warn("classpath entry '" + classpathElement + "' doesn't exist. (Use " + File.pathSeparator
                    + " to separate entries.)");
        }
        else if (file.isDirectory()) {
            classpath.add(file.getAbsolutePath());
            for (final File jar : file.listFiles(createFilenameFilter(JAR_REGEX))) {
                classpath.add(jar.getAbsolutePath());
            }
        }
        else {
            classpath.add(file.getAbsolutePath());
        }
    }

  List<String> getClasspath() {
    return ScriptResources.getClasspath();
  }

  public static Map<String, Object> getStaticProperties() {
        return staticProperties;
    }

    private static Map<String, Object> createStaticProperties() {
        Map<String, Object> properties = new LinkedHashMap<String, Object>();
      properties.put("logger", new LogUtils());
      properties.put("ui", new UITools());
      properties.put("htmlUtils", HtmlUtils.getInstance());
      properties.put("textUtils", new TextUtils());
      properties.put("menuUtils", new MenuUtils());
      properties.put("config", new FreeplaneScriptBaseClass.ConfigProperties());
        return properties;
    }

    static String getExecutionModeKey(final ExecuteScriptAction.ExecutionMode executionMode) {
    switch (executionMode) {
      case ON_SINGLE_NODE:
        return "ExecuteScriptOnSingleNode.text";
      case ON_SELECTED_NODE:
        return "ExecuteScriptOnSelectedNode.text";
      case ON_SELECTED_NODE_RECURSIVELY:
        return "ExecuteScriptOnSelectedNodeRecursively.text";
      default:
        throw new AssertionError("unknown ExecutionMode " + executionMode);
    }
  }

  public static String getScriptsLocation(String parentKey) {
    return  parentKey + "/scripts";
  }
}
TOP

Related Classes of org.freeplane.plugin.script.ScriptingConfiguration

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.