/* * 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. * * $Header:$ */ package org.apache.beehive.controls.api.events; import java.lang.reflect.Method; import java.util.HashMap; import java.util.WeakHashMap; /** * The EventRef class represents a reference to a specific Control event. EventRefs can * be used to fire external events into a Control, in contexts where the event source may * not share the associated EventSet class instance with the event target, or even have * access to the EventSet class itself. *

* It is roughly equivalent to the java.lang.reflect.Method object that refers to a method * on an EventSet interface, but has several additional properties: *

*/ public class EventRef implements java.io.Serializable { // // Static helper map to go from a primitive type to the type descriptor string // static private HashMap _primToType = new HashMap(); static { _primToType.put(Integer.TYPE, "I"); _primToType.put(Boolean.TYPE, "Z"); _primToType.put(Byte.TYPE, "B"); _primToType.put(Character.TYPE, "C"); _primToType.put(Short.TYPE, "S"); _primToType.put(Long.TYPE, "J"); _primToType.put(Float.TYPE, "F"); _primToType.put(Double.TYPE, "D"); _primToType.put(Void.TYPE, "V"); } /** * Constructs a new EventRef based upon a Method reference. The input method must be one * that is declared within a Control EventSet interface. * @param eventMethod the Method associated with the event */ public EventRef(Method eventMethod) { _method = eventMethod; _descriptor = computeEventDescriptor(eventMethod); } /** * Constructs a new EventRef using an event descriptor string. The format of this string * is: *
 
     *      .
     * 
* where eventSet refers to the fully qualified name of the EventSet class, * eventName refers to the name of the event Method, and eventDescriptor * describes the event argument and return types using the method descriptor format * defined in the Java Language Specification. *

* For example, given the following EventSet interface: *

     * @ControlInterface
     * public interface MyControl
     * {
     *     @EventSet
     *     public interface MyEvents
     *     {
     *          public String myEvent(int arg0, Object arg2);
     *     }
     * }
     * 
* the eventDescriptor for myEvent would be: *
 
     *     MyControl.MyEvents.myEvent(ILjava/lang/Object;)Ljava/lang/String;
     * 
* @param eventDescriptor the event descriptor string associated with the event */ public EventRef(String eventDescriptor) { _descriptor = eventDescriptor; } /** * Returns the event descriptor string associated with the EventRef. * @param controlInterface the ControlInterface */ public String getEventDescriptor(Class controlInterface) { // // NOTE: The input controlInterface is currently unused, but included to // enable downstream optimization of serialization representation. See the // OPTIMIZE comment below for more details. If implemented, the interface // is needed to reverse the transformation from a hash back to a method or // descriptor. // if (_descriptor == null) _descriptor = computeEventDescriptor(_method); return _descriptor; } /** * Helper method that computes the event descriptor sting for a method */ private String computeEventDescriptor(Method method) { StringBuilder sb = new StringBuilder(); // Add event class and method name sb.append(method.getDeclaringClass().getName()); sb.append("."); sb.append(method.getName()); // Add event arguments Class [] parms = method.getParameterTypes(); sb.append("("); for (int i = 0; i < parms.length; i++) appendTypeDescriptor(sb, parms[i]); sb.append(")"); // Add event return type appendTypeDescriptor(sb, method.getReturnType()); return sb.toString(); } /** * Helper method that appends a type descriptor to a StringBuilder. Used * while accumulating an event descriptor string. */ private void appendTypeDescriptor(StringBuilder sb, Class clazz) { if (clazz.isPrimitive()) sb.append(_primToType.get(clazz)); else if (clazz.isArray()) sb.append(clazz.getName().replace('.','/')); else { sb.append("L"); sb.append(clazz.getName().replace('.','/')); sb.append(";"); } } /** * Returns the event Method associated with this EventRef. */ public Method getEventMethod(Class controlInterface) { // // If we already hold a method reference and its loader matches up with the input // interface, then just return it. // if (_method != null && _method.getDeclaringClass().getClassLoader().equals(controlInterface.getClassLoader())) return _method; // // Otherwise, obtain the mapping from descriptors to methods, and use it to // convert back to a method. // String eventDescriptor = getEventDescriptor(controlInterface); HashMap descriptorMap = getDescriptorMap(controlInterface); if (!descriptorMap.containsKey(eventDescriptor)) { throw new IllegalArgumentException("Control interface " + controlInterface + " does not contain an event method that " + " corresponds to " + eventDescriptor); } return descriptorMap.get(eventDescriptor); } /** * A WeakHashMap used to cache the event descriptor-to-Method mapping for control * interfaces. */ static private WeakHashMap> _descriptorMaps = new WeakHashMap>(); private HashMap getDescriptorMap(Class controlInterface) { // // If the local cache has the mapping, then return it. // HashMap descMap = _descriptorMaps.get(controlInterface); if (descMap == null) { // // Compute the mapping from event descriptors to event methods, using reflection // descMap = new HashMap(); Class [] innerClasses = controlInterface.getClasses(); for (int i = 0; i < innerClasses.length; i++) { if (!innerClasses[i].isInterface() || !innerClasses[i].isAnnotationPresent(EventSet.class)) continue; Method [] eventMethods = innerClasses[i].getMethods(); for (int j = 0; j < eventMethods.length; j++) descMap.put(computeEventDescriptor(eventMethods[j]), eventMethods[j]); } _descriptorMaps.put(controlInterface, descMap); } return descMap; } /** * Two EventRefs are equal if the method descriptor string associated with them is equal */ public boolean equals(Object obj) { if (obj == null || !(obj instanceof EventRef)) return false; return _descriptor.equals(((EventRef)obj)._descriptor); } public String toString() { return "EventRef: " + _descriptor; } // // OPTIMIZE: A more efficient internal representation for serialization/wire purposes // would be to compute a hash of the descriptor string (ala RMI opnums), that could be // reconstituted on the other end, given a candidate ControlInterface. The public APIs // are structured to support this downstream optimization. // private String _descriptor; transient private Method _method; }