blob: cdb0cba2e672af9b19127d0516ac88a1c5152d00 [file] [log] [blame]
/*
* $Id$
*
* Copyright 2005 (C) Guillaume Laforge. All Rights Reserved.
*
* Redistribution and use of this software and associated documentation
* ("Software"), with or without modification, are permitted provided that the
* following conditions are met:
* 1. Redistributions of source code must retain copyright statements and
* notices. Redistributions must also contain a copy of this document.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name "groovy" must not be used to endorse or promote products
* derived from this Software without prior written permission of The Codehaus.
* For written permission, please contact info@codehaus.org.
* 4. Products derived from this Software may not be called "groovy" nor may
* "groovy" appear in their names without prior written permission of The
* Codehaus. "groovy" is a registered trademark of The Codehaus.
* 5. Due credit should be given to The Codehaus - http://groovy.codehaus.org/
*
* THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
* DAMAGE.
*
*/
package org.codehaus.groovy.scriptom;
import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.DispatchEvents;
import groovy.lang.Closure;
import groovy.lang.GroovyObjectSupport;
import groovy.lang.GroovyShell;
import groovy.lang.Binding;
import java.util.HashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.Date;
import org.codehaus.groovy.control.CompilationFailedException;
/**
* Provides a hooking mechanism to use an "events" property belonging to the ActiveXProxy,
* containing closures for the event handling.
* <p/>
* This "events" is backed by a Map that contains keys representing the event to subscribe to,
* and closures representing the code to execute when the event is triggered.
* <p/>
* Jacob allows only to pass to the <code>DispatchEvents</code> class.
* But Scriptom pass a dynamically generated class in Groovy through GroovyShell,
* which delegates calls to the closures stored in the Map.
* <p/>
*
* <p>
* Event support can be done by adding closures to the event object,
* then calling listen() method will subscribe to all events:
* <code>
* comProxy.events.SomeEvent = { // do something }
* comProxy.events.OtherEvent = { // do something else }
* comProxy.events.listen()
* </code>
* </p>
*
* @author Guillaume Laforge
*/
public class EventSupport extends GroovyObjectSupport
{
/**
* Map containing closures for each events which has been subscribed to
*/
private Map eventHandlers = new HashMap();
/**
* Underlying Jacob ActiveXComponent
*/
private ActiveXComponent activex;
/**
* Source code of the class dealing with event support
*/
private String eventClassSourceCode = "// no event support script generated";
/**
* In the constructor, we pass the reference to the <code>ActiveXComponent</code>.
*
* @param activex the component
*/
EventSupport(ActiveXComponent activex)
{
this.activex = activex;
}
/**
* Invokes directly a closure in the <code>eventHandlers</code> Map,
* or call the <code>listen()</code> pseudo-method that triggers the creation of the <code>EventHandler</code>
* and registers it with <code>DispatchEvents</code>.
*
* @param name name of the closure to call, or the "listen" pseudo-method.
* @param args arguments to be passed to the closure
* @return result returned by the closre
*/
public Object invokeMethod(String name, Object args)
{
if ("listen".equals(name))
{
try {
StringBuffer methods = new StringBuffer();
for (Iterator iterator = eventHandlers.keySet().iterator(); iterator.hasNext();) {
String eventName = (String) iterator.next();
methods.append(" void ")
.append(eventName)
.append("(Variant[] variants) {\n")
.append(" evtHandlers['")
.append(eventName)
.append("'].call( VariantProxy.defineArray(variants) )\n }\n");
}
// time token to avoid duplicate classes with the same name
long time = new Date().getTime();
StringBuffer classSource = new StringBuffer();
classSource.append("import com.jacob.com.*\n")
.append("import org.codehaus.groovy.scriptom.VariantProxy\n")
.append("class EventHandler")
.append(time)
.append(" {\n")
.append(" def evtHandlers\n")
.append(" EventHandler")
.append(time)
.append("(scriptBinding) {\n")
.append(" evtHandlers = scriptBinding\n")
.append(" }\n")
.append(methods.toString())
.append("}\n")
.append("new EventHandler")
.append(time)
.append("(binding)\n");
Map eventHandlersContainer = new HashMap();
eventHandlersContainer.put("eventHandlers", eventHandlers);
Binding binding = new Binding(eventHandlers);
eventClassSourceCode = classSource.toString();
Object generatedInstance = new GroovyShell(binding).evaluate(eventClassSourceCode);
new DispatchEvents(this.activex, generatedInstance);
} catch (CompilationFailedException e) {
e.printStackTrace();
}
return null;
}
else
{
// call the closure from the eventHandlers Map
return ((Closure) eventHandlers.get(name)).call(args);
}
}
/**
* Sets the property only if a <code>Closure</code> for event handling is passed as value.
* The name of the property represents the name of the events triggered by the ActiveX/COM component.
* The closure is the code to be executed upon the event being triggered.
*
* @param property the name of the event
* @param newValue the closure to execute
*/
public void setProperty(String property, Object newValue)
{
if (newValue instanceof Closure)
eventHandlers.put(property, newValue);
}
/**
* <p>Retrieves the action event closure associated with a given event.</p>
*
* <p>
* For debugging purpose, the generated scripted can be retrieved with:
* <code>
* println comProxy.events.eventSourceScript
* </code>
* </p>
*
* @param property the name of the event
* @return the closure associated with the handling of the given event
*/
public Object getProperty(String property)
{
// used to print the source of the generated class dealing with event support for debugging purpose
if ("eventSourceScript".equals(property)) {
return eventClassSourceCode;
}
return eventHandlers.get(property);
}
}