001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: * $Header:$
018: */
019: package org.apache.beehive.controls.api.events;
020:
021: import java.lang.reflect.Method;
022: import java.util.HashMap;
023: import java.util.WeakHashMap;
024:
025: /**
026: * The EventRef class represents a reference to a specific Control event. EventRefs can
027: * be used to fire external events into a Control, in contexts where the event source may
028: * not share the associated EventSet class instance with the event target, or even have
029: * access to the EventSet class itself.
030: * <p>
031: * It is roughly equivalent to the java.lang.reflect.Method object that refers to a method
032: * on an EventSet interface, but has several additional properties:
033: * <ul>
034: * <li>It is serializable, so can be persisted/restored or passed across the wire</li>
035: * <li>It supports materializing the EventRef back to a Method reference in a way that allows
036: * EventRefs to be passed across class loaders</li>
037: * <li>It can be constructed in contexts where a reference to the actual EventSet class might
038: * not be available (using a String event descriptor format to describe events)</li>
039: * </ul>
040: */
041: public class EventRef implements java.io.Serializable {
042: //
043: // Static helper map to go from a primitive type to the type descriptor string
044: //
045: static private HashMap<Class, String> _primToType = new HashMap<Class, String>();
046: static {
047: _primToType.put(Integer.TYPE, "I");
048: _primToType.put(Boolean.TYPE, "Z");
049: _primToType.put(Byte.TYPE, "B");
050: _primToType.put(Character.TYPE, "C");
051: _primToType.put(Short.TYPE, "S");
052: _primToType.put(Long.TYPE, "J");
053: _primToType.put(Float.TYPE, "F");
054: _primToType.put(Double.TYPE, "D");
055: _primToType.put(Void.TYPE, "V");
056: }
057:
058: /**
059: * Constructs a new EventRef based upon a Method reference. The input method must be one
060: * that is declared within a Control EventSet interface.
061: * @param eventMethod the Method associated with the event
062: */
063: public EventRef(Method eventMethod) {
064: _method = eventMethod;
065: _descriptor = computeEventDescriptor(eventMethod);
066: }
067:
068: /**
069: * Constructs a new EventRef using an event descriptor string. The format of this string
070: * is:
071: * <pre>
072: * <eventSet>.<eventName><eventDescriptor>
073: * </pre>
074: * where <i>eventSet</i> refers to the fully qualified name of the EventSet class,
075: * <i>eventName</i> refers to the name of the event Method, and <i>eventDescriptor</i>
076: * describes the event argument and return types using the method descriptor format
077: * defined in the Java Language Specification.
078: * <p>
079: * For example, given the following EventSet interface:
080: * <pre>
081: * <sp>@ControlInterface
082: * public interface MyControl
083: * {
084: * <sp>@EventSet
085: * public interface MyEvents
086: * {
087: * public String myEvent(int arg0, Object arg2);
088: * }
089: * }
090: * </pre>
091: * the eventDescriptor for myEvent would be:
092: * <pre>
093: * MyControl.MyEvents.myEvent(ILjava/lang/Object;)Ljava/lang/String;
094: * </pre>
095: * @param eventDescriptor the event descriptor string associated with the event
096: */
097: public EventRef(String eventDescriptor) {
098: _descriptor = eventDescriptor;
099: }
100:
101: /**
102: * Returns the event descriptor string associated with the EventRef.
103: * @param controlInterface the ControlInterface
104: */
105: public String getEventDescriptor(Class controlInterface) {
106: //
107: // NOTE: The input controlInterface is currently unused, but included to
108: // enable downstream optimization of serialization representation. See the
109: // OPTIMIZE comment below for more details. If implemented, the interface
110: // is needed to reverse the transformation from a hash back to a method or
111: // descriptor.
112: //
113: if (_descriptor == null)
114: _descriptor = computeEventDescriptor(_method);
115:
116: return _descriptor;
117: }
118:
119: /**
120: * Helper method that computes the event descriptor sting for a method
121: */
122: private String computeEventDescriptor(Method method) {
123: StringBuilder sb = new StringBuilder();
124:
125: // Add event class and method name
126: sb.append(method.getDeclaringClass().getName());
127: sb.append(".");
128: sb.append(method.getName());
129:
130: // Add event arguments
131: Class[] parms = method.getParameterTypes();
132: sb.append("(");
133: for (int i = 0; i < parms.length; i++)
134: appendTypeDescriptor(sb, parms[i]);
135: sb.append(")");
136:
137: // Add event return type
138: appendTypeDescriptor(sb, method.getReturnType());
139:
140: return sb.toString();
141: }
142:
143: /**
144: * Helper method that appends a type descriptor to a StringBuilder. Used
145: * while accumulating an event descriptor string.
146: */
147: private void appendTypeDescriptor(StringBuilder sb, Class clazz) {
148: if (clazz.isPrimitive())
149: sb.append(_primToType.get(clazz));
150: else if (clazz.isArray())
151: sb.append(clazz.getName().replace('.', '/'));
152: else {
153: sb.append("L");
154: sb.append(clazz.getName().replace('.', '/'));
155: sb.append(";");
156: }
157: }
158:
159: /**
160: * Returns the event Method associated with this EventRef.
161: */
162: public Method getEventMethod(Class controlInterface) {
163: //
164: // If we already hold a method reference and its loader matches up with the input
165: // interface, then just return it.
166: //
167: if (_method != null
168: && _method.getDeclaringClass().getClassLoader().equals(
169: controlInterface.getClassLoader()))
170: return _method;
171:
172: //
173: // Otherwise, obtain the mapping from descriptors to methods, and use it to
174: // convert back to a method.
175: //
176: String eventDescriptor = getEventDescriptor(controlInterface);
177: HashMap<String, Method> descriptorMap = getDescriptorMap(controlInterface);
178: if (!descriptorMap.containsKey(eventDescriptor)) {
179: throw new IllegalArgumentException("Control interface "
180: + controlInterface
181: + " does not contain an event method that "
182: + " corresponds to " + eventDescriptor);
183: }
184: return descriptorMap.get(eventDescriptor);
185: }
186:
187: /**
188: * A WeakHashMap used to cache the event descriptor-to-Method mapping for control
189: * interfaces.
190: */
191: static private WeakHashMap<Class, HashMap<String, Method>> _descriptorMaps = new WeakHashMap<Class, HashMap<String, Method>>();
192:
193: private HashMap<String, Method> getDescriptorMap(
194: Class controlInterface) {
195: //
196: // If the local cache has the mapping, then return it.
197: //
198: HashMap<String, Method> descMap = _descriptorMaps
199: .get(controlInterface);
200: if (descMap == null) {
201: //
202: // Compute the mapping from event descriptors to event methods, using reflection
203: //
204: descMap = new HashMap<String, Method>();
205: Class[] innerClasses = controlInterface.getClasses();
206: for (int i = 0; i < innerClasses.length; i++) {
207: if (!innerClasses[i].isInterface()
208: || !innerClasses[i]
209: .isAnnotationPresent(EventSet.class))
210: continue;
211:
212: Method[] eventMethods = innerClasses[i].getMethods();
213: for (int j = 0; j < eventMethods.length; j++)
214: descMap.put(
215: computeEventDescriptor(eventMethods[j]),
216: eventMethods[j]);
217: }
218: _descriptorMaps.put(controlInterface, descMap);
219: }
220: return descMap;
221: }
222:
223: /**
224: * Two EventRefs are equal if the method descriptor string associated with them is equal
225: */
226: public boolean equals(Object obj) {
227: if (obj == null || !(obj instanceof EventRef))
228: return false;
229:
230: return _descriptor.equals(((EventRef) obj)._descriptor);
231: }
232:
233: public String toString() {
234: return "EventRef: " + _descriptor;
235: }
236:
237: //
238: // OPTIMIZE: A more efficient internal representation for serialization/wire purposes
239: // would be to compute a hash of the descriptor string (ala RMI opnums), that could be
240: // reconstituted on the other end, given a candidate ControlInterface. The public APIs
241: // are structured to support this downstream optimization.
242: //
243: private String _descriptor;
244: transient private Method _method;
245: }
|