/interpreter/tags/at2dist110511/src/edu/vub/at/objects/coercion/Coercer.java
Java | 229 lines | 100 code | 22 blank | 107 comment | 29 complexity | 3041f7ab351ed07135bf7f2598a193ce MD5 | raw file
1/** 2 * AmbientTalk/2 Project 3 * Coercer.java created on 3-okt-2006 at 16:12:05 4 * (c) Programming Technology Lab, 2006 - 2007 5 * Authors: Tom Van Cutsem & Stijn Mostinckx 6 * 7 * Permission is hereby granted, free of charge, to any person 8 * obtaining a copy of this software and associated documentation 9 * files (the "Software"), to deal in the Software without 10 * restriction, including without limitation the rights to use, 11 * copy, modify, merge, publish, distribute, sublicense, and/or 12 * sell copies of the Software, and to permit persons to whom the 13 * Software is furnished to do so, subject to the following 14 * conditions: 15 * 16 * The above copyright notice and this permission notice shall be 17 * included in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 20 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 21 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 22 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 23 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 24 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 25 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 26 * OTHER DEALINGS IN THE SOFTWARE. 27 */ 28package edu.vub.at.objects.coercion; 29 30import edu.vub.at.actors.eventloops.BlockingFuture; 31import edu.vub.at.actors.eventloops.EventLoop; 32import edu.vub.at.actors.eventloops.EventLoop.EventProcessor; 33import edu.vub.at.actors.natives.ELActor; 34import edu.vub.at.exceptions.XIllegalOperation; 35import edu.vub.at.exceptions.XTypeMismatch; 36import edu.vub.at.objects.ATObject; 37import edu.vub.at.objects.mirrors.Reflection; 38import edu.vub.at.objects.symbiosis.Symbiosis; 39 40import java.io.IOException; 41import java.io.Serializable; 42import java.lang.reflect.InvocationHandler; 43import java.lang.reflect.InvocationTargetException; 44import java.lang.reflect.Method; 45import java.lang.reflect.Proxy; 46 47/** 48 * A coercer is a dynamic proxy which is used to 'cast' Ambienttalk base-level NATObjects to a certain ATxxx interface. 49 * The dynamic proxy is responsible for transforming java calls to meta_invoke calls. 50 * 51 * For example, a method from an AT interface 52 * 53 * ATExpression base_expression() throws NATException 54 * 55 * receives the following implementation: 56 * 57 * ATExpression expression() throws NATException { 58 * return principal.meta_invoke(principal, Reflection.downSelector("getExpression"), NATTable.EMPTY).asExpression(); 59 * } 60 * 61 * where principal is the original object 'coerced into' the given interface 62 * 63 * TODO: we should implement a mapping uniquely identifiying a Coercer per 64 * ATObject, Interface pair, i.e. we must implement a Map: 65 * (ATObject x Class) -> Coercer 66 * 67 * @author tvcutsem 68 */ 69public final class Coercer implements InvocationHandler, Serializable { 70 71 private final ATObject principal_; 72 73 // we have to remember which thread owned the principal 74 private transient Thread wrappingThread_; 75 76 private Coercer(ATObject principal, Thread owningThread) { 77 principal_ = principal; 78 wrappingThread_ = owningThread; 79 } 80 81 public String toString() { 82 return "<coercer on: "+principal_+">"; 83 } 84 85 /** 86 * Try to coerce the given AmbientTalk object into the given Java type. This variant implicitly assumes that 87 * the coercion is performed by the thread owning the object, which is the case when coercing arguments to a 88 * Java method call or when passing an AmbientTalk object as a result. 89 * 90 * Note that the returned coercer object is also an instance of {@link ATObject} and 91 * in the AmbientTalk world the coercer will behave as its coerced AT object. 92 * 93 * @param object the AmbientTalk object to coerce 94 * @param type the class object representing the target type 95 * @return a Java object <tt>o</tt> for which it holds that <tt>type.isInstance(o)</tt> 96 * @throws XTypeMismatch if the coercion fails 97 */ 98 public static final Object coerce(ATObject object, Class type) throws XTypeMismatch { 99 return coerce(object, type, Thread.currentThread()); 100 } 101 102 /** 103 * Try to coerce the given AmbientTalk object into the given Java type, while explicitly providing the thread 104 * which is the owning actor for the object. 105 * <p> 106 * This variant of coerce is provided explicitly to allow coercion to occur from a Java thread which is not the 107 * owning actor of the object. This occurs when the coercion is performed explicitly on the return value of an 108 * evaluation, after the latter has been finalized. 109 * 110 * @param object the AmbientTalk object to coerce 111 * @param type the class object representing the target type 112 * @param owningThread the owning Actor 113 * @return a Java object <tt>o</tt> for which it holds that <tt>type.isInstance(o)</tt> 114 * @throws XTypeMismatch if the coercion fails 115 */ 116 public static final Object coerce(ATObject object, Class type, Thread owningThread) throws XTypeMismatch { 117 if (type.isInstance(object)) { // object instanceof type 118 return object; // no need to coerce 119 } else if (type.isInterface()) { 120 121 // see which class loader is required to load the interface 122 123 // first try this thread's context class loader 124 ClassLoader loader = Thread.currentThread().getContextClassLoader(); 125 try { 126 Class.forName(type.getName(), false, loader); 127 } catch(ClassNotFoundException e) { 128 // if that fails, try the class loader that created the interface type 129 loader = type.getClassLoader(); 130 } 131 132 // note that the proxy implements both the required type 133 // and the Symbiotic object marker interface to identify it as a wrapper 134 return Proxy.newProxyInstance(loader, 135 new Class[] { type, ATObject.class }, 136 new Coercer(object, owningThread)); 137 } else { 138 throw new XTypeMismatch(type, object); 139 } 140 } 141 142 public Object invoke(Object receiver, final Method method, Object[] arguments) throws Throwable { 143 Class methodImplementor = method.getDeclaringClass(); 144 // handle toString, hashCode and equals in a dedicated fashion 145 // similarly, handle any native AT methods by simply forwarding them to the native AT object 146 if (methodImplementor == Object.class || methodImplementor == ATObject.class) { 147 // invoke these methods on the principal rather than on the proxy 148 if (Thread.currentThread() != wrappingThread_) { 149 if (Thread.currentThread() instanceof EventProcessor) { 150 // another event loop has direct access to this object, this means 151 // an AT object has been shared between actors via Java, signal an error 152 throw new XIllegalOperation("Detected illegal invocation of "+method.getName()+": sharing via Java level of object " + principal_); 153 } 154 155 ELActor owningActor = (ELActor) EventLoop.toEventLoop(wrappingThread_); 156 157 // synchronous symbiotic invocation 158 BlockingFuture future = owningActor.sync_event_symbioticForwardInvocation(principal_, method, arguments); 159 return future.get(); 160 } else { 161 // immediate symbiotic invocation 162 try { 163 return method.invoke(principal_, arguments); 164 } catch (InvocationTargetException e) { 165 throw e.getTargetException(); 166 } 167 } 168 } else { 169 170 final ATObject[] symbioticArgs; 171 // support for variable-arity invocations from within AmbientTalk 172 if ((arguments != null) && (arguments.length == 1) && (arguments[0] instanceof ATObject[])) { 173 // no need to convert arguments 174 symbioticArgs = (ATObject[]) arguments[0]; 175 } else { 176 symbioticArgs = new ATObject[(arguments == null) ? 0 : arguments.length]; 177 for (int i = 0; i < symbioticArgs.length; i++) { 178 symbioticArgs[i] = Symbiosis.javaToAmbientTalk(arguments[i]); 179 } 180 } 181 182 // if the current thread is not an actor thread, treat the Java invocation 183 // as a message send instead and enqueue it in my actor's thread 184 185 if (Thread.currentThread() != wrappingThread_) { 186 if (Thread.currentThread() instanceof EventProcessor) { 187 // another event loop has direct access to this object, this means 188 // an AT object has been shared between actors via Java, signal an error 189 throw new XIllegalOperation("Detected illegal invocation of "+method.getName()+": sharing via Java level of object " + principal_); 190 } 191 192 ELActor owningActor = (ELActor) EventLoop.toEventLoop(wrappingThread_); 193 194 // if the invoked method is part of an EventListener interface, treat the 195 // invocation as a pure asynchronous message send, if the returntype is void 196 if (Symbiosis.isEvent(method)) { 197 // asynchronous symbiotic invocation 198 owningActor.event_symbioticInvocation(principal_, method, symbioticArgs); 199 return null; // void return type 200 } else { 201 // because a message send is asynchronous and Java threads work synchronously, 202 // we'll have to make the Java thread wait for the result 203 BlockingFuture future = owningActor.sync_event_symbioticInvocation(principal_, method, symbioticArgs); 204 if (method.getReturnType().equals(BlockingFuture.class)) { 205 // future-type symbiotic invocation 206 return future; 207 } else { 208 // synchronous symbiotic invocation 209 return future.get(); 210 } 211 } 212 } else { 213 // perform an immediate symbiotic invocation 214 ATObject result = Reflection.downInvocation(principal_, method, symbioticArgs); 215 // properly 'cast' the returned object into the appropriate interface 216 return Symbiosis.ambientTalkToJava(result, method.getReturnType()); 217 } 218 } 219 } 220 221 /** 222 * Upon deserialization, re-assign the thread to the actor deserializing this coercer 223 */ 224 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException { 225 in.defaultReadObject(); 226 wrappingThread_ = Thread.currentThread(); 227 } 228 229}