// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.collide.client.status;
import com.google.collide.client.status.StatusMessage.MessageType;
import com.google.collide.client.util.logging.Log;
import com.google.collide.json.client.JsoArray;
/**
* The Status Manager is responsible for maintaining all status state. It
* coordinates priorities between different types of messages and allows the
* status view to be stateless.
*
* There can be many outstanding messages at any given time, but only one
* message is active. When a message becomes active, the {@link StatusHandler}
* is notified of this change.
*
* A fatal message is unrecoverable and cannot be dismissed or canceled once
* fired.
*/
public class StatusManager {
/**
* The currently active message, or null if there are none
*/
private StatusMessage activeMessage = null;
// TODO: Too bad we don't have real lightweight collections. Might be
// worth making an Ordered Set implementation for wider client use, but for
// now these collections are always very small.
/**
* The set of outstanding confirmation messages.
*/
private final JsoArray<StatusMessage> confirmationMessages = JsoArray.create();
/**
* The set of outstanding error messages.
*/
private final JsoArray<StatusMessage> errorMessages = JsoArray.create();
/**
* If a fatal message fires, it is recorded here.
*/
private StatusMessage firedFatal = null;
/**
* The set of outstanding loading messages.
*/
private final JsoArray<StatusMessage> loadingMessages = JsoArray.create();
/**
* The handler responsible for updating the view when there is a new active
* message.
*/
private StatusHandler statusHandler;
public StatusManager() {
/*
* Create a dummy statusHandler to safely handle events that occur before
* the UI handler is hooked up.
*/
this.statusHandler = new StatusHandler() {
@Override
public void clear() {
}
@Override
public void onStatusMessage(StatusMessage msg) {
}
};
}
StatusManager(StatusHandler statusHandler) {
this.statusHandler = statusHandler;
}
/**
* Cancel a message. Once a message is canceled, all subsequent fires are
* no-ops.
*
* @param msg the message to cancel
*/
void cancel(StatusMessage msg) {
switch (msg.getType()) {
case LOADING:
loadingMessages.remove(msg);
break;
case CONFIRMATION:
confirmationMessages.remove(msg);
break;
case ERROR:
errorMessages.remove(msg);
break;
case FATAL:
// cannot cancel a fatal;
return;
default:
Log.error(getClass(), "Got a status message of unknown type " + msg.getType());
return;
}
possiblyFireHandler();
}
/**
* Clear all outstanding and active messages.
*/
public void clear() {
activeMessage = null;
loadingMessages.clear();
errorMessages.clear();
statusHandler.clear();
}
/**
* A message firing to the StatusManager puts it on the queue of pending
* messages. If this message is the active message, then it fires to the
* handler as well.
*
* @param msg
*/
void fire(StatusMessage msg) {
if ((msg.getType() == MessageType.FATAL) && (firedFatal == null)) {
statusHandler.onStatusMessage(msg);
firedFatal = msg;
} else {
possiblyAddMessage(msg);
possiblyFireHandler();
}
}
/**
* Convenience method for giving the message lists ordered-set like semantics
* where an update will not add another element but will re-fire the handler
* if the given message is the active message.
*
* @param msg
*/
private void possiblyAddMessage(StatusMessage msg) {
JsoArray<StatusMessage> list = null;
switch (msg.getType()) {
case LOADING:
list = loadingMessages;
break;
case CONFIRMATION:
list = confirmationMessages;
break;
case ERROR:
list = errorMessages;
break;
case FATAL:
return;
default:
Log.error(getClass(), "Got a status message of unknown type " + msg.getType());
return;
}
boolean found = false;
for (int i = 0; !found && i < list.size(); i++) {
if (list.get(i) == msg) {
found = true;
}
}
if (found && (msg == activeMessage)) {
// Trigger a re-fire to the handler if the active message fires again.
activeMessage = null;
} else if (!found) {
list.add(msg);
}
}
/**
* If the top message has changed, then notify the handler.
*/
private void possiblyFireHandler() {
if (firedFatal != null) {
// Drop all other messages ones a fatal message has fired.
return;
}
// Normal message handling.
StatusMessage top = null;
if (!errorMessages.isEmpty()) {
top = errorMessages.peek();
} else if (!confirmationMessages.isEmpty()) {
top = confirmationMessages.peek();
} else if (!loadingMessages.isEmpty()) {
top = loadingMessages.peek();
}
if (top == null) {
statusHandler.clear();
} else if (top != activeMessage) {
statusHandler.onStatusMessage(top);
}
activeMessage = top;
}
/**
* Set a new status handler. This will clear the view of the old handler and
* immediately fire the top status message to the new handler.
*
* @param statusHandler the new status handler.
*/
public void setHandler(StatusHandler statusHandler) {
this.statusHandler.clear();
this.statusHandler = statusHandler;
if (firedFatal != null) {
// On a handoff, persist the fatal message
statusHandler.onStatusMessage(firedFatal);
} else if (activeMessage != null) {
activeMessage = null;
possiblyFireHandler();
}
}
}