package com.github.atemerev.hollywood.office; import com.github.atemerev.hollywood.Actor; import com.github.atemerev.hollywood.StateChangedEvent; import com.github.atemerev.hollywood.annotations.AllowedStates; import com.github.atemerev.hollywood.annotations.Initial; import com.github.atemerev.hollywood.annotations.State; import com.github.atemerev.hollywood.future.CompletedEvent; import com.github.atemerev.hollywood.future.Promise; import com.github.atemerev.pms.Listener; import java.util.HashSet; import java.util.LinkedList; import java.util.Queue; import java.util.Set; /** * @author Alexander Temerev * @version $Id$ */ public abstract class Secretary extends Actor { // Public interface 公共接口 //[在家里]> 老婆,我要去上班了 @AllowedStates(AtHome.class) public abstract void goToWork(); //[在工作]> 一天又开始了,看看有什么邮件/文件要处理 @AllowedStates(Working.class) public abstract void acceptLetter(Letter letter); // States 状态 //初始状态: 在家中 @Initial @State public static abstract class AtHome extends Secretary { //要去上班了, 动作发生前允许的状态是在家里, 动作发生后, 将状态设置为工作中 @AllowedStates(AtHome.class) public synchronized void goToWork() { setState(Working.class); } } //工作中 @State public static abstract class Working extends Secretary { //要接电话,也要发传真 protected Phone phone; protected Fax fax; //好多电话要打出去啊: 先来的先处理, 不要让最先打电话的人等最久 protected Queue<Call> callsOnHold = new LinkedList<Call>(); //还有一堆信件要放到邮箱里 protected Set<Letter> letters = new HashSet<Letter>(); // Commands public void acceptLetter(Letter letter) { letters.add(letter); } // Behavior //刚进入工作状态:刚到公司,把电话和传真都准备好了,要不然一天都不要干活了 public void onEnter() { phone = Phone.instance(); fax = Fax.instance(); } //刚好不在工作的前一刻,要准备回家了 public void onExit() { //把电话都处理完了再走 processOnHoldCalls(); //下班前还有最后一件事:把信件都放到邮局门口的信箱里 //调用该方法会减少latch计数器.邮件放入完毕,会触发OfficeTest在latch的等待状态开始往后执行 PostOffice.instance().send(letters); } //一天的工作结束了, 当EndDayEvent事件到来时, 将状态设置为在家中 @Listener public synchronized void $(EndDayEvent e) { setState(AtHome.class); } //有电话打进来 @Listener public synchronized void $(Call call) { //做好接听电话的准备 OnCall newState = prepareState(OnCall.class); //这个call就是要接听的电话 newState.call = call; //将状态设置为OnCall,则自动会触发OnCall的onEnter方法. //因为call参数会设置到OnCall.call中. 这样确保了进入OnCall.onEnter时,call就是要处理的那个电话 setState(newState); } //处理等待通电话的业务: 有好几个电话都打过来了, 赶紧处理下吧 public synchronized void processOnHoldCalls() { if (callsOnHold.size() != 0) { System.out.println("Processing calls on hold..."); me().processMessage(callsOnHold.poll()); } } } // 待命状态: 正在通话中 @State public static abstract class OnCall extends Working { protected Call call; //刚刚通话时:第二个参数是问候信息 //第一个参数call表示一定能取到等待通话的记录. 如果processOnHoldCalls没有需要通话的记录, //则不会进入OnCall状态. 一旦进入OnCall状态,call一定是从processOnHoldCalls取出的记录,一定是有值的. public void onEnter() { phone.respond(call, "- Corporate accounts payable, Nina speaking. Just a moment...", this); } @Listener public synchronized void $(String phrase) { //phrase是Call的参数. 这里的处理逻辑只是简单地把客户的信息打印出来 System.out.println("- " + phrase); //这就算处理完毕了. 通知客户: 我们注意到了你的问题,会及时解决.再见. phone.say("- We aware of your problem and will resolve it ASAP. Goodbye."); //特殊用户有带来特殊的事件 if (phrase.contains("fax")) { //客户打电话要求发传真 FaxMessage message = new FaxMessage("our price list"); //准备发送传真, 做好发送传真的准备 SendingFax newState = prepareState(SendingFax.class); //这个message就是要发送的传真 newState.faxToSend = message; //当设置状态为SendingFax时,首先触发StateChangedEvent事件的调用,打印了事件前后的状态:[OnCall] -> [SendingFax] //因为状态现在变为SendingFax,所以会立即进入SendingFax这个Actor的onEnter方法,开始执行发送传真的动作 setState(newState); } else if (phrase.contains("go home")) { //老板打电话说可以可以回家了 setState(AtHome.class); } else { //这个电话接听完毕,准备接听下一个电话 ((Working) setState(Working.class)).processOnHoldCalls(); } } @Listener public synchronized void $(Call call) { callsOnHold.add(call); System.out.println("Incoming call taken on hold..."); } public void onExit() { phone.hangUp(); } } // 发送传真 @State public static abstract class SendingFax extends Working { FaxMessage faxToSend; public void onEnter() { //传真的发送是一个比较耗时的动作,我们要确保发送一定成功 Promise<Void> sendPromise = fax.send(faxToSend); //发送后,要监听是否成功:把当前事件加入到监听器列表中 sendPromise.listeners().add(me()); } //发送传真的时候, 有电话打进来. 把电话加入到等待处理的列表中 //为什么两者不能并行处理? 因为传真和电话共用一个网络.一次只能处理一种业务. @Listener public synchronized void $(Call call) { callsOnHold.add(call); System.out.println("Incoming call taken on hold..."); } //传真完成后,基础处理其他事情. 首先要把状态改为工作中. //为什么不像OnCall使用onExist方法. 而是传递CompleteEvent事件. //因为发送传真是个异步的过程.onEnter调用完,实际上就已经到达onExist. //而我们要确保传真发送成功,传递CompleteEvent才表示这个操作成功完成. @Listener public synchronized void $(CompletedEvent e) { //发送传真完毕后,要手动调用处理等待通话的事件. ((Working) setState(Working.class)).processOnHoldCalls(); } } @Listener public void $(StateChangedEvent e) { System.out.println(); System.out.println("\t\t\t\t\t\t\t\t[" + prevState() + "] -> [" + state() + "]"); } }