Log in Help
Print
Homereleasesgate-8.4-build5748-ALLpluginsGroovysrcgategroovy 〉 GroovySupport.java
 
/*
 *  Copyright (c) 2010, The University of Sheffield.
 *
 *  This file is part of the GATE/Groovy integration layer, and is free
 *  software, released under the terms of the GNU Lesser General Public
 *  Licence, version 2.1 (or any later version).  A copy of this licence
 *  is provided in the file LICENCE in the distribution.
 *
 *  Groovy is developed by The Codehaus, details are available from
 *  http://groovy.codehaus.org
 */

package gate.groovy;

import gate.Gate;
import gate.GateConstants;
import gate.Resource;
import gate.creole.AbstractResource;
import gate.creole.ResourceInstantiationException;
import gate.creole.metadata.AutoInstance;
import gate.creole.metadata.CreoleResource;
import gate.gui.ActionsPublisher;
import gate.gui.MainFrame;
import gate.persist.PersistenceException;
import gate.util.GateException;
import gate.util.GateRuntimeException;
import gate.util.persistence.PersistenceManager;
import groovy.lang.Binding;
import groovy.lang.GroovyShell;
import groovy.lang.ReadOnlyPropertyException;

import java.awt.event.ActionEvent;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.swing.AbstractAction;
import javax.swing.Action;

import org.codehaus.groovy.runtime.DefaultGroovyMethods;

/**
 * Tool resource that sets up Groovy support for GATE.  When the Groovy
 * plugin is loaded an instance of this resource is created, which (a)
 * adds a menu item to GATE Developer to run the Groovy console and (b)
 * mixes in the static methods of {@link gate.Utils} and
 * {@link gate.groovy.GateGroovyMethods} so they can be used as instance
 * methods by any Groovy code that runs after the plugin is loaded.
 */
@CreoleResource(name = "Groovy support for GATE", isPrivate = true, tool = true,
        autoinstances = @AutoInstance)
public class GroovySupport extends AbstractResource implements ActionsPublisher {

  private static final long serialVersionUID = 4763983982544551334L;
  /**
   * Standard list of import statements that are available to any groovy script
   * or console in GATE.
   */
  public static final String STANDARD_IMPORTS =
      "import gate.*;\n" +
      //"import gate.jape.*;\n" +
      //"import gate.creole.ontology.*;\n" +
      //"import gate.annotation.*;\n" +
      "import gate.util.*;\n";

  public Resource init() throws ResourceInstantiationException {
    // mix-in gate.Utils and gate.groovy.GateGroovyMethods
    mixinGlobally(gate.Utils.class);
    mixinGlobally(GateGroovyMethods.class);
    // register the ScriptableController with the persistence mechanism
    try {
      PersistenceManager.registerPersistentEquivalent(ScriptableController.class,
          ScriptableControllerPersistence.class);
    }
    catch(PersistenceException e) {
      throw new ResourceInstantiationException(e);
    }

    return this;
  }

  /**
   * Mix all the static methods of the given class into their
   * respective types.
   * @param classToMix a category class.
   */
  protected void mixinGlobally(Class<?> classToMix) {
    // find the set of types into which it needs to be mixed.
    // this means the types of the first argument of each
    // static method in the class.
    Set<Class<?>> typesToMixInto = new HashSet<Class<?>>();
    for(Method method : classToMix.getMethods()) {
      if(Modifier.isStatic(method.getModifiers())
              && method.getDeclaringClass() == classToMix
              && method.getParameterTypes().length > 0) {
        typesToMixInto.add(method.getParameterTypes()[0]);
      }
    }
    for(Class<?> clazz : typesToMixInto) {
      DefaultGroovyMethods.mixin(clazz, classToMix);
    }
  }

  private List<Action> actions;

  public List<Action> getActions() {
    if(actions == null) {
      actions = new ArrayList<Action>();
      actions.add(new AbstractAction("Groovy Console", MainFrame.getIcon("groovyConsole")) {
        {
          putValue(SHORT_DESCRIPTION, "Console for Groovy scripting");
          putValue(GateConstants.MENU_PATH_KEY, new String[] {"Groovy Tools"});
        }
        private static final long serialVersionUID = 1L;
        public void actionPerformed(ActionEvent evt) {
          groovy.ui.Console console = new groovy.ui.Console(GroovySupport.class.getClassLoader()) {

            /**
             * Overridden to (a) install a Binding that knows about the
             * corpora, docs, prs and apps variables, and (b) set up the
             * standard imports each time a script is run.
             */
            @Override
            public void newScript(ClassLoader parent, Binding binding) {
              // TODO Auto-generated method stub
              Binding realBinding = new GateBinding(binding);
              setShell(new GroovyShell(parent, realBinding) {
                public Object run(final String scriptText,
                        final String fileName, String[] args) {
                  return super.run(scriptText + "\n\n\n" + STANDARD_IMPORTS,
                          fileName, args);
                }
              });
            }

            /**
             * This is a workaround for a strange behaviour in Groovy. At some
             * point the superclass attempts to do "scriptRunning = false" from
             * inside a closure which is defined in a method of the Console
             * class.  But rather than treating that as an assignment to the
             * private field scriptRunning in that class, Groovy treats this
             * as a setProperty, which fails when this object is a subclass
             * of Console.  So we have to implement the property setter here,
             * delegating to the real private field in the superclass.
             */
            @SuppressWarnings("unused")
            public void setScriptRunning(boolean b) {
              try {
                Field f = groovy.ui.Console.class.getDeclaredField("scriptRunning");
                boolean a = f.isAccessible();
                f.setAccessible(true);
                f.set(this, b);
                f.setAccessible(a);
              }
              catch(Exception e) {
                // give up
              }
            }
          };
          console.run();
        }
      });
    }
    return actions;
  }

  /**
   * Special Binding subclass that handles requests for the variables
   * corpora, docs, prs and apps by delegating to the creole register.
   */
  static class GateBinding extends Binding {
    GateBinding(Binding delegateBinding) {
      super(delegateBinding.getVariables());
    }

    /**
     * Overridden to intercept requests for the 'pseudo-variables'
     * corpora, docs, prs and apps and direct them to the relevant
     * methods of the creole register.
     */
    public Object getVariable(String name) {
      if("corpora".equals(name)) {
        try {
          return Gate.getCreoleRegister().getAllInstances(
            "gate.Corpus");
        }
        catch(GateException e) {
          throw new GateRuntimeException(e);
        }
      }
      else if("docs".equals(name)) {
        return Gate.getCreoleRegister()
          .getLrInstances("gate.corpora.DocumentImpl");
      }
      else if("prs".equals(name)) {
        return Gate.getCreoleRegister().getPrInstances();
      }
      else if("apps".equals(name)) {
        try {
          return Gate.getCreoleRegister().getAllInstances(
            "gate.creole.AbstractController");
        }
        catch(GateException e) {
          throw new GateRuntimeException(e);
        }
      }
      else {
        return super.getVariable(name);
      }
    }

    /**
     * Overridden to enforce read-only access to the 'pseudo-variables'
     * corpora, docs, prs and apps.
     * @throws ReadOnlyPropertyException if an attempt is made to set
     *         any of these variables.
     */
    public void setVariable(String name, Object value) {
      if("corpora".equals(name) || "docs".equals(name) || "prs".equals(name)
              || "apps".equals(name)) {
        throw new ReadOnlyPropertyException(name, this.getClass());
      }
      super.setVariable(name, value);
    }
  }

}