package com.github.atemerev.hollywood; import com.github.atemerev.hollywood.annotations.Initial; import com.github.atemerev.hollywood.annotations.State; import com.github.atemerev.pms.listeners.dispatch.DispatchListener; import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassWriter; import static org.objectweb.asm.Opcodes.*; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.*; import java.util.concurrent.*; /** * @author Alexander Kuklev, Alexander Temerev * @version $Id$ */ public class Hollywood { // I put on my robe and wizard hat... public static final Executor hollywoodExecutor = new ThreadPoolExecutor(4, 64, 300, TimeUnit.SECONDS, new SynchronousQueue<Runnable>()); private static final HollywoodClassLoader cl = new HollywoodClassLoader(Hollywood.class); private static final Map<Class, Class> actorProxyClassCache = new ConcurrentHashMap<Class, Class>(); private static final Map<Class, Class> stateProxyClassCache = new ConcurrentHashMap<Class, Class>(); private static final Map<Class, RootState> sterileStateCache = new ConcurrentHashMap<Class, RootState>(); private static final Map<Class, ForwardingDispatchListener> stateDispatchListenerCache = new ConcurrentHashMap<Class, ForwardingDispatchListener>(); //创建Actor @SuppressWarnings({"unchecked"}) public static <T extends Actor> T createActor(Class<T> aClass) { try { //生成Actor的代理类 Class actorProxyClass = generateActorProxy(aClass); //创建出Actor实例对象 T actorInstance = (T) actorProxyClass.newInstance(); Class initialState = null; // Ensure everything is OK with states and find the initial state // 确保类的各种状态都定义正确,并找出初始状态 for (Class memberClass : aClass.getDeclaredClasses()) { if (memberClass.isAnnotationPresent(State.class)) { int mod = memberClass.getModifiers(); if (!Modifier.isPublic(mod) || !Modifier.isStatic(mod) || !Modifier.isAbstract(mod)) { throw new IllegalArgumentException("State classes need to have 'public static abstract' signature"); } //aClass是一个Actor:因为调用createActor,参数一定是Actor类型. 要求表示状态的内部类必须继承当前Actor if (!aClass.isAssignableFrom(memberClass)) { throw new IllegalArgumentException("State classes must inherit from the actor class"); } //如果某个类有@Initial注解,则表示创建完Actor后,Actor进入的初始状态 if (memberClass.isAnnotationPresent(Initial.class)) { if (initialState != null) { throw new IllegalArgumentException("Only one state should be marked as @Initial"); } initialState = memberClass; try { Method rootOnEnter = RootState.class.getMethod("onEnter"); for (Class i = memberClass; aClass.isAssignableFrom(i); i = i.getSuperclass()) { if (!i.getMethod("onEnter").equals(rootOnEnter)) { throw new IllegalArgumentException("Initial states are not allowed to have direct or inherited onEnter()"); } } } catch (NoSuchMethodException e) { e.printStackTrace(); // No chance } } } } if (initialState == null) { throw new IllegalArgumentException("Some state should be marked as @Initial"); } //设置Actor实例的初始状态(即创建完Actor的状态) //actorInstance是传入的参数Class<T> aClass中的T实例.比如Secretary实例. //而Secretary中并没有setState方法.它是如何生成这个方法的? 就是通过最开始的生成代理类. actorInstance.setState(initialState); return actorInstance; } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked"}) private static Class generateActorProxy(Class aClass) { //Actor类-->其代理类 Class cachedClass = actorProxyClassCache.get(aClass); if (cachedClass != null) return cachedClass; try { // Prepare proxy class node ClassWriter cw = new ClassWriter(0); ClassNode proxyClassNode = new ClassNode(); String proxyClassName = AsmUtil.asmClassName(aClass) + "_hwproxy"; AsmUtil.fillClassNode(proxyClassNode, proxyClassName, AsmUtil.asmClassName(aClass)); // Create a field containing the current state 添加2个字段 proxyClassNode.fields.add(new FieldNode(ACC_PRIVATE, "$state", "Lcom/github/atemerev/hollywood/RootState;", null, null)); proxyClassNode.fields.add(new FieldNode(ACC_PRIVATE, "$prevState", "Lcom/github/atemerev/hollywood/RootState;", null, null)); // Read actor class node ClassNode actorClassNode = new ClassNode(); ClassReader cr = new ClassReader(aClass.getName()); cr.accept(actorClassNode, 0); // Check that there are no public fields, otherwise throw exception for (FieldNode field : (List<FieldNode>) actorClassNode.fields) { if ((field.access & ACC_PUBLIC) != 0) { throw new IllegalArgumentException("Public fields are not allowed in the class: " + aClass.getName()); } } // Forward all method calls to current state List<MethodNode> methods = actorClassNode.methods; for (MethodNode mn : methods) { if (!mn.name.equals("<init>") && (mn.access & ACC_STATIC) == 0) { createProxyMethod(aClass, proxyClassNode, proxyClassName, mn); } } // Read actor proxy prototype class ClassNode actorProxyPrototypeNode = new ClassNode(); ClassReader cr2 = new ClassReader(ActorProxyPrototype.class.getName()); cr2.accept(actorProxyPrototypeNode, 0); // Hotswap the members form the proxy prototype for (MethodNode mn : (List<MethodNode>) actorProxyPrototypeNode.methods) { // The <init> method shouldn't be overrided if (!mn.name.equals("<init>")) { // Set the right type for "this" argument LocalVariableNode zeroArg = (LocalVariableNode) mn.localVariables.get(0); zeroArg.desc = "L" + proxyClassName + ";"; // Set correct owner type for accessed field for (Iterator i = mn.instructions.iterator(); i.hasNext();) { AbstractInsnNode in = (AbstractInsnNode) i.next(); if (in instanceof FieldInsnNode) { FieldInsnNode fieldInsnNode = (FieldInsnNode) in; if (fieldInsnNode.owner.equals("com/github/atemerev/hollywood/Hollywood$ActorProxyPrototype")) { fieldInsnNode.owner = proxyClassName; } } } proxyClassNode.methods.add(mn); } } // Create the constructor AsmUtil.createDefaultConstructor(aClass, proxyClassNode); // Create a class from the class node proxyClassNode.accept(cw); Class result = cl.defineClass(aClass.getName() + "_hwproxy", cw.toByteArray()); actorProxyClassCache.put(aClass, result); return result; } catch (IOException e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked"}) private static void createProxyMethod(Class aClass, ClassNode proxyClassNode, String proxyClassName, MethodNode mn) { // Clear the abstract flag mn.access &= ~ACC_ABSTRACT; // Check for the disallowed package visibility if (mn.access == 0) { throw new IllegalArgumentException("Package visibility not supported for method: " + aClass.getSimpleName() + "#" + mn.name); } // Check for the disallowed final modifier if ((mn.access & ACC_FINAL) != 0) { throw new IllegalArgumentException("Actions cannot be final (method: " + aClass.getSimpleName() + "#" + mn.name + ")"); } // Preliminary fill method node mn.maxStack = 5; mn.maxLocals = 10; mn.instructions = new InsnList(); mn.tryCatchBlocks = new ArrayList(); mn.localVariables = new ArrayList(); // Insert the forwarding code: InsnList il = mn.instructions; // Load "this" il.add(new VarInsnNode(ALOAD, 0)); il.add(new FieldInsnNode(GETFIELD, proxyClassName, "$state", "Lcom/github/atemerev/hollywood/RootState;")); il.add(new TypeInsnNode(CHECKCAST, AsmUtil.asmClassName(aClass))); // Load other arguments int sp = 1; for (Type argument : Type.getArgumentTypes(mn.desc)) { String desc = argument.getDescriptor(); int loadInstruction = desc.matches("[ZCBSI]") ? ILOAD : desc.equals("J") ? LLOAD : desc.equals("F") ? FLOAD : desc.equals("D") ? DLOAD : ALOAD; il.add(new VarInsnNode(loadInstruction, sp)); int dim = desc.matches("[DF]") ? 2 : 1; sp += dim; mn.maxStack += dim; mn.maxLocals += dim; } // Call the corresponding method form the state il.add(new MethodInsnNode(INVOKEVIRTUAL, AsmUtil.asmClassName(aClass), mn.name, mn.desc)); String desc = Type.getReturnType(mn.desc).getDescriptor(); // Return int returnInstruction = desc.equals("V") ? RETURN : desc.matches("[ZCBSI]") ? IRETURN : desc.equals("J") ? LRETURN : desc.equals("F") ? FRETURN : desc.equals("D") ? DRETURN : ARETURN; il.add(new InsnNode(returnInstruction)); // Save the method proxyClassNode.methods.add(mn); } // sterile = true generates a sterile state instance for instanceof applications @SuppressWarnings({"unchecked"}) public static <T extends RootState> T loadStateInstance(Class<T> aClass, boolean sterile) { // Check if it's a state at all State stateAnnotation = aClass.getAnnotation(State.class); if (stateAnnotation == null) { throw new IllegalArgumentException("Not a state: " + aClass.getName()); } try { if (sterile) { RootState cached = sterileStateCache.get(aClass); if (cached != null) return (T) cached; } Class actorProxyClass = generateStateProxy(aClass, sterile); T result = (T) actorProxyClass.newInstance(); if (sterile) { sterileStateCache.put(aClass, result); } else if (stateDispatchListenerCache.get(aClass) == null) { stateDispatchListenerCache.put(aClass, new ForwardingDispatchListener(result)); } return result; } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked"}) private static Class generateStateProxy(Class aClass, boolean sterile) { if (!sterile) { Class cached = stateProxyClassCache.get(aClass); if (cached != null) return cached; } try { // Prepare proxy class node ClassWriter cw = new ClassWriter(0); ClassNode proxyClassNode = new ClassNode(); AsmUtil.fillClassNode(proxyClassNode, AsmUtil.asmClassName(aClass) + (sterile ? "_hwcproxy" : "_hwproxy"), AsmUtil.asmClassName(aClass)); // Read state class node ClassNode stateClassNode = new ClassNode(); ClassReader cr = new ClassReader(aClass.getName()); cr.accept(stateClassNode, 0); Map<String, MethodNode> methods = new HashMap<String, MethodNode>(); for (MethodNode mn : (List<MethodNode>) stateClassNode.methods) { methods.put(mn.name + ":" + mn.desc, mn); if (!sterile) { if (mn.name.equals("onEnter")) { mn.name += "$" + aClass.getSimpleName(); proxyClassNode.methods.add(mn); } else if (mn.name.equals("onExit")) { mn.name += "$" + aClass.getSimpleName(); proxyClassNode.methods.add(mn); } } } // Aggregate abstract methods and copy onExits/onEnters Class i = aClass; while (!i.getSuperclass().equals(Actor.class)) { i = i.getSuperclass(); ClassNode j = new ClassNode(); cr = new ClassReader(i.getName()); cr.accept(j, 0); for (MethodNode mn : (List<MethodNode>) j.methods) { if (!methods.containsKey(mn.name + ":" + mn.desc)) { methods.put(mn.name + ":" + mn.desc, mn); } if (!sterile) { if (mn.name.equals("onEnter")) { mn.name += "$" + i.getSimpleName(); proxyClassNode.methods.add(mn); } else if (mn.name.equals("onExit")) { mn.name += "$" + i.getSimpleName(); proxyClassNode.methods.add(mn); } } } } // Check the state has no public fields for (FieldNode field : (List<FieldNode>) stateClassNode.fields) { if ((field.access & ACC_PUBLIC) != 0) { throw new IllegalArgumentException("Public fields are not allowed in the class: " + aClass.getName()); } } if (sterile) { sterilizeMethods(proxyClassNode, methods.values()); } else { forwardActorMethods(proxyClassNode, AsmUtil.asmClassName(i) + "_hwproxy"); fillAbstractMethods(proxyClassNode, aClass, methods.values()); } AsmUtil.createDefaultConstructor(aClass, proxyClassNode); // Create a class from the class node proxyClassNode.accept(cw); Class result = cl.defineClass(aClass.getName() + (sterile ? "_hwcproxy" : "_hwproxy"), cw.toByteArray()); if (!sterile) { stateProxyClassCache.put(aClass, result); } return result; } catch (IOException e) { throw new RuntimeException(e); } } @SuppressWarnings({"unchecked"}) private static void fillAbstractMethods(ClassNode resultClassNode, Class sourceClass, Collection<MethodNode> methods) { // Produce stubs for all abstract methods of the state and put them into the proxy for (MethodNode mn : methods) { if ((mn.access & ACC_ABSTRACT) != 0 && (mn.access & ACC_STATIC) == 0) { // Clear abstract flag mn.access &= ~ACC_ABSTRACT; // Check for the unsupported package visibility if (mn.access == 0) { throw new IllegalArgumentException("Package visibility not supported for method: " + sourceClass.getSimpleName() + "#" + mn.name); } // Fill the method with exception throwing code mn.tryCatchBlocks = new ArrayList(); mn.localVariables = new ArrayList(); InsnList il = mn.instructions; il.add(new TypeInsnNode(NEW, "java/lang/UnsupportedOperationException")); il.add(new InsnNode(DUP)); il.add(new LdcInsnNode("Method \"" + mn.name + "\" not supported in the state [" + sourceClass.getSimpleName() + "]")); il.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V")); il.add(new InsnNode(ATHROW)); mn.maxStack = 3; mn.maxLocals = Type.getArgumentTypes(mn.desc).length + 10;//+ 2; // Save it resultClassNode.methods.add(mn); } } } @SuppressWarnings({"unchecked"}) private static void sterilizeMethods(ClassNode resultClassNode, Collection<MethodNode> sourceClassMethods) { for (MethodNode mn : sourceClassMethods) { if (!mn.name.equals("<init>") && !mn.name.equals("toString") && (mn.access != 0) && (mn.access & ACC_STATIC) == 0) { // Clear abstract flag mn.access &= ~ACC_ABSTRACT; // Fill the method with exception throwing code mn.tryCatchBlocks = new ArrayList(); mn.localVariables = new ArrayList(); mn.instructions = new InsnList(); InsnList il = mn.instructions; il.add(new TypeInsnNode(NEW, "java/lang/UnsupportedOperationException")); il.add(new InsnNode(DUP)); il.add(new LdcInsnNode("Method invokation not supported. State instances produced by the actor.state() method can be only used for instanceof checking.")); il.add(new MethodInsnNode(INVOKESPECIAL, "java/lang/UnsupportedOperationException", "<init>", "(Ljava/lang/String;)V")); il.add(new InsnNode(ATHROW)); mn.maxStack = 3; mn.maxLocals = Type.getArgumentTypes(mn.desc).length + 10; //+ 2; // Save it resultClassNode.methods.add(mn); } } } @SuppressWarnings({"unchecked"}) private static void forwardActorMethods(ClassNode classNode, String actorProxyClassName) { try { // Create a field containing the actor proxy classNode.fields.add(new FieldNode(ACC_PUBLIC, "$actor", "Lcom/github/atemerev/hollywood/Actor;", null, null)); // Read actor proxy prototype class ClassNode actorProxyPrototypeNode = new ClassNode(); ClassReader cr2 = new ClassReader(ActorProxyPrototype.class.getName()); cr2.accept(actorProxyPrototypeNode, 0); // Forward methods defined in actor proxy prototype to the actor for (MethodNode mn : (List<MethodNode>) actorProxyPrototypeNode.methods) { // The <init> method shouldn't be overrided if (!mn.name.equals("<init>")) { // Set the right type for "this" argument LocalVariableNode zeroArg = null; for (Object node : mn.localVariables) { LocalVariableNode localVariableNode = (LocalVariableNode) node; if (localVariableNode.index == 0) { zeroArg = localVariableNode; break; } } zeroArg.desc = "L" + classNode.name + ";"; // Preliminary fill method node mn.maxStack = 5; mn.maxLocals = 10; mn.instructions = new InsnList(); mn.tryCatchBlocks = new ArrayList(); mn.localVariables = new ArrayList(); mn.localVariables.add(zeroArg); InsnList il = mn.instructions; // Load "this" il.add(new VarInsnNode(ALOAD, 0)); il.add(new FieldInsnNode(GETFIELD, classNode.name, "$actor", "Lcom/github/atemerev/hollywood/Actor;")); il.add(new TypeInsnNode(CHECKCAST, actorProxyClassName)); // Load other arguments int sp = 1; for (Type argument : Type.getArgumentTypes(mn.desc)) { String desc = argument.getDescriptor(); int loadInstruction = desc.matches("[ZCBSI]") ? ILOAD : desc.equals("J") ? LLOAD : desc.equals("F") ? FLOAD : desc.equals("D") ? DLOAD : ALOAD; il.add(new VarInsnNode(loadInstruction, sp)); int dim = desc.matches("[DF]") ? 2 : 1; sp += dim; mn.maxStack += dim; mn.maxLocals += dim; } // Call the corresponding method form the actor il.add(new MethodInsnNode(INVOKEVIRTUAL, actorProxyClassName, mn.name, mn.desc)); String desc = Type.getReturnType(mn.desc).getDescriptor(); // Return int returnInstruction = desc.equals("V") ? RETURN : desc.matches("[ZCBSI]") ? IRETURN : desc.equals("J") ? LRETURN : desc.equals("F") ? FRETURN : desc.equals("D") ? DRETURN : ARETURN; il.add(new InsnNode(returnInstruction)); // Save the method classNode.methods.add(mn); } } } catch (IOException e) { e.printStackTrace(); // Cannot occur } } public static ForwardingDispatchListener getDispListener(Class aClass) { return stateDispatchListenerCache.get(aClass); } private static class HollywoodClassLoader extends ClassLoader { HollywoodClassLoader(Class aClass) { super(aClass.getClassLoader()); } public Class defineClass(String name, byte[] b) { return defineClass(name, b, 0, b.length); } } // Actor代理的原型. 通过ASM会给具体的Actor修改成满足自己逻辑的实现类 @SuppressWarnings({"unchecked"}) static class ActorProxyPrototype { private RootState $prevState; private RootState $state; public final Actor me() { return (Actor) (Object) this; } public final RootState state() { // Returns sterilized version of the current state. return Hollywood.loadStateInstance((Class<RootState>) $state.getClass().getSuperclass(), true); } public final RootState prevState() { // Returns sterilized version of the current state. return Hollywood.loadStateInstance((Class<RootState>) $prevState.getClass().getSuperclass(), true); } protected final <T extends RootState> T prepareState(Class<T> targetStateClass) { Class actorClass = this.getClass().getSuperclass(); T targetState = Hollywood.loadStateInstance(targetStateClass, false); // Check if it's a state of our actor if (!actorClass.isAssignableFrom(targetStateClass)) { throw new IllegalArgumentException("State is not bound to current actor: " + targetStateClass.getName()); } // Save pointer to the actor try { targetState.getClass().getField("$actor").set(targetState, this); } catch (Exception e) { throw new RuntimeException(e); } if ($state == null) { return targetState; } Class presentStateClass = $state.getClass().getSuperclass(); Class nca; // nearest common ancestor RootState oldState = $state; // Call onExit()s of the state (and all it's ancestors) and determine NCA for (nca = presentStateClass; actorClass.isAssignableFrom(nca) && !(nca.isAssignableFrom(targetStateClass)); nca = nca.getSuperclass()) { try { Method onExit = $state.getClass().getDeclaredMethod("onExit$" + nca.getSimpleName()); onExit.setAccessible(true); onExit.invoke($state); if ($state != oldState) return null; // TODO explain } catch (NoSuchMethodException e) { // No Problem } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); // PANIC! Actor left in inconsistent state! } } // Carry over the fields shared by the old and the new state for (Class i = nca; actorClass.isAssignableFrom(i); i = i.getSuperclass()) { for (Field field : i.getDeclaredFields()) { try { field.setAccessible(true); field.set(targetState, field.get($state)); } catch (IllegalAccessException e) { throw new RuntimeException(e); } } } return targetState; } protected final synchronized <T extends RootState> T setState(T targetState) { Class targetStateClass = targetState.getClass().getSuperclass(); Class actorClass = this.getClass().getSuperclass(); if ($state == null) { $state = targetState; return targetState; } Class presentStateClass = $state.getClass().getSuperclass(); Class nca; // nearest common ancestor // Determine NCA for (nca = presentStateClass; actorClass.isAssignableFrom(nca) && !(nca.isAssignableFrom(targetStateClass)); nca = nca.getSuperclass()); // Generate a list of the new state ancestor list up to NCA LinkedList<Class> ancestorsToEnter = new LinkedList<Class>(); for (Class j = targetState.getClass().getSuperclass(); j != nca; j = j.getSuperclass()) { ancestorsToEnter.addFirst(j); } // Call onEnter()s of the state (and all it's ancestors) we're entering $prevState = $state; $state = targetState; Hollywood.getDispListener($state.getClass().getSuperclass()) .forwardMessage(new StateChangedEvent(), (Actor) $state); for (Class cl : ancestorsToEnter) { try { Method onEnter = cl.getDeclaredMethod("onEnter"); onEnter.setAccessible(true); onEnter.invoke(targetState); if ($state != targetState) return null; } catch (NoSuchMethodException e) { // No Problem } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); // PANIC! Actor left in inconsistent state! } } return targetState; } public final synchronized void processMessage(Object message) { Hollywood.getDispListener($state.getClass().getSuperclass()) .forwardMessage(message, (Actor) $state); } } protected static class ForwardingDispatchListener extends DispatchListener { ForwardingDispatchListener(Object target) { super(target); } public void forwardMessage(Object message, Actor target) { listener = target; setExecutor(target.asyncListenerExecutor()); processMessage(message); } } private static class AsmUtil { @SuppressWarnings({"unchecked"}) static void createDefaultConstructor(Class aClass, ClassNode proxyClassNode) { MethodNode consNode = new MethodNode(ACC_PUBLIC, "<init>", "()V", null, null); consNode.maxStack = 1; consNode.maxLocals = 10; InsnList consInstructions = new InsnList(); consInstructions.add(new VarInsnNode(ALOAD, 0)); consInstructions.add(new MethodInsnNode(INVOKESPECIAL, asmClassName(aClass), "<init>", "()V")); consInstructions.add(new InsnNode(RETURN)); consNode.instructions = consInstructions; proxyClassNode.methods.add(consNode); } static String asmClassName(Class aClass) { return Type.getInternalName(aClass); } static void fillClassNode(ClassNode cn, String name, String superName) { cn.version = V1_5; cn.access = ACC_PUBLIC; cn.name = name; cn.superName = superName; } } }