// Copyright (c) 2010 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.sdk.internal; import java.util.Collection; import java.util.logging.Level; import java.util.logging.Logger; import org.chromium.sdk.RelayOk; import org.chromium.sdk.SyncCallback; /** * Provides basic command processor functionality: sends/receives commands/events and * supports callbacks for commands. It also supports status reporting for UI. * All operations such as sending/receiving/parsing are implemented by a {@link Handler}. * * @param <SEQ_KEY> type of command sequence number key * @param <OUTGOING> type of outgoing message * @param <INCOMING> type of incoming message * @param <INCOMING_WITH_SEQ> type of incomming message that is a command (has sequence number) */ public class BaseCommandProcessor<SEQ_KEY, OUTGOING, INCOMING, INCOMING_WITH_SEQ> { private static final Logger LOGGER = Logger.getLogger(BaseCommandProcessor.class.getName()); public interface Handler<SEQ_KEY, OUTGOING, INCOMING, INCOMING_WITH_SEQ> { SEQ_KEY getUpdatedSeq(OUTGOING message); String getCommandName(OUTGOING message); void send(OUTGOING message, boolean isImmediate); INCOMING_WITH_SEQ parseWithSeq(INCOMING incoming); SEQ_KEY getSeq(INCOMING_WITH_SEQ incomingWithSeq); void acceptNonSeq(INCOMING incoming); void reportVmStatus(String currentRequest, int numberOfEnqueued); } /** * @param <INCOMING_WITH_SEQ> */ public interface Callback<INCOMING_WITH_SEQ> { void messageReceived(INCOMING_WITH_SEQ response); void failure(String message); } private final CloseableMap<SEQ_KEY, CallbackEntry<INCOMING_WITH_SEQ>> callbackMap = CloseableMap.newLinkedMap(); private final Handler<SEQ_KEY, OUTGOING, INCOMING, INCOMING_WITH_SEQ> handler; public BaseCommandProcessor( Handler<SEQ_KEY, OUTGOING, INCOMING, INCOMING_WITH_SEQ> handler) { this.handler = handler; } public RelayOk send(OUTGOING message, boolean isImmediate, Callback<? super INCOMING_WITH_SEQ> callback, SyncCallback syncCallback) { SEQ_KEY seq = handler.getUpdatedSeq(message); boolean callbackAdded; if (callback != null || syncCallback != null) { String commandName = handler.getCommandName(message); try { callbackMap.put(seq, new CallbackEntry<INCOMING_WITH_SEQ>(callback, syncCallback, commandName)); } catch (IllegalStateException e) { throw new IllegalStateException("Connection is closed", e); } callbackAdded = true; reportVmStatus(); } else { callbackAdded = false; } try { handler.send(message, isImmediate); } catch (RuntimeException e) { if (callbackAdded) { callbackMap.remove(seq); } throw e; } return WE_SENT_IT_RELAY_OK; } public void processIncoming(INCOMING incomingParsed) { final INCOMING_WITH_SEQ commandResponse = handler.parseWithSeq(incomingParsed); if (commandResponse != null) { SEQ_KEY key = handler.getSeq(commandResponse); CallbackEntry<INCOMING_WITH_SEQ> callbackEntry = callbackMap.removeIfContains(key); if (callbackEntry != null) { LOGGER.log( Level.FINE, "Request-response roundtrip: {0}ms", getCurrentMillis() - callbackEntry.commitMillis); reportVmStatus(); CallbackCaller<Callback<? super INCOMING_WITH_SEQ>> caller = new CallbackCaller<Callback<? super INCOMING_WITH_SEQ>>() { @Override void call(Callback<? super INCOMING_WITH_SEQ> handlerCallback) { handlerCallback.messageReceived(commandResponse); } }; try { callThemBack(callbackEntry, caller, key); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Failed to dispatch response to callback", e); } } } else { handler.acceptNonSeq(incomingParsed); } } public void processEos() { // We should call them in the order they have been submitted. Collection<CallbackEntry<INCOMING_WITH_SEQ>> entries = callbackMap.close().values(); for (CallbackEntry<INCOMING_WITH_SEQ> entry : entries) { try { callThemBack(entry, failureCaller, null); } catch (RuntimeException e) { LOGGER.log(Level.SEVERE, "Failed to dispatch response to callback", e); } } } private void callThemBack(CallbackEntry<INCOMING_WITH_SEQ> callbackEntry, CallbackCaller<? super Callback<? super INCOMING_WITH_SEQ>> callbackCaller, SEQ_KEY requestSeq) { RuntimeException callbackException = null; try { if (callbackEntry.callback != null) { LOGGER.log( Level.FINE, "Notified debugger command callback, request_seq={0}", requestSeq); callbackCaller.call(callbackEntry.callback); } } catch (RuntimeException e) { callbackException = e; throw e; } finally { if (callbackEntry.syncCallback != null) { callbackEntry.syncCallback.callbackDone(callbackException); } } } private static abstract class CallbackCaller<CALLBACK> { abstract void call(CALLBACK handlerCallback); } private final CallbackCaller<Callback<?>> failureCaller = new CallbackCaller<Callback<?>>() { @Override void call(Callback<?> handlerCallback) { handlerCallback.failure("Connection closed"); } }; private static class CallbackEntry<INCOMING_WITH_SEQ> { final Callback<? super INCOMING_WITH_SEQ> callback; final SyncCallback syncCallback; final long commitMillis; final String requestName; CallbackEntry(Callback<? super INCOMING_WITH_SEQ> callback, SyncCallback syncCallback, String requestName) { this.callback = callback; this.commitMillis = getCurrentMillis(); this.syncCallback = syncCallback; this.requestName = requestName; } } /** * @return milliseconds since the epoch */ private static long getCurrentMillis() { return System.currentTimeMillis(); } private final Object vmStatusReportMonitor = new Object(); private void reportVmStatus() { // We synchronize, because one thread may be delivering obsolete message while a more // recent message has already been delivered by other thread. synchronized (vmStatusReportMonitor) { int size = callbackMap.size(); CallbackEntry<?> firstEntry = callbackMap.peekFirst(); // Those 2 variables above might be not in synch, so for a brief moment user may see // a wrong message (when size == 0 and firstEntry is null). This is OK. if (firstEntry == null) { handler.reportVmStatus(null, 0); } else { handler.reportVmStatus(firstEntry.requestName, size - 1); } } } private static final RelayOk WE_SENT_IT_RELAY_OK = new RelayOk() {}; }