/**
 * Simple implementation of a singleton.
 *
 * @author [email protected] (Alex North)
 */
public final class ObservableSingletonImpl<V, I> implements ObservableSingleton<V, I> {
  /** Factory which creates value objects from abstract initial state. */
  interface Factory<V, I> {
    V create(I initialState);
  }

  private final Factory<V, I> factory;
  private final CopyOnWriteSet<Listener<? super V>> listeners = CopyOnWriteSet.create();

  private V value = null;

  public ObservableSingletonImpl(Factory<V, I> factory) {
    this.factory = factory;
  }

  @Override
  public boolean hasValue() {
    return value != null;
  }

  @Override
  public V get() {
    return value;
  }

  @Override
  public V set(I initialState) {
    V oldValue = value;
    value = factory.create(initialState);
    maybeTriggerOnValueChanged(oldValue, value);
    return value;
  }

  @Override
  public void clear() {
    V oldValue = value;
    value = null;
    maybeTriggerOnValueChanged(oldValue, value);
  }

  @Override
  public void addListener(Listener<? super V> listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Listener<? super V> listener) {
    listeners.remove(listener);
  }

  private void maybeTriggerOnValueChanged(V oldValue, V newValue) {
    if (ValueUtils.notEqual(oldValue, newValue)) {
      for (Listener<? super V> l : listeners) {
        l.onValueChanged(oldValue, newValue);
      }
    }
  }
}
/** A wave panel, which is just a view and an event system. */
public final class WavePanelImpl implements WavePanel, Focusable {

  /** Event system on which features install their controllers. */
  private final EventDispatcherPanel panel;

  /** Key handlers for key events that are routed to this panel. */
  private final KeySignalRouter keys = new KeySignalRouter();

  /** Views through which features manipulate the UI. */
  private final DomAsViewProvider views;

  private final CopyOnWriteSet<LifecycleListener> listeners = CopyOnWriteSet.create();

  //
  // Fields referencing the wave rendering are dynamically inserted and removed.
  //

  /** True between {@link #init} and {@link #reset}. */
  private boolean initialized;

  /** Main conversation shown in this panel. */
  private TopConversationView main;

  private WavePanelImpl(DomAsViewProvider views, EventDispatcherPanel panel) {
    this.views = views;
    this.panel = panel;
  }

  /**
   * Creates a wave panel.
   *
   * @param views view bundle
   * @param panelDom element in the DOM on which to build the wave panel
   * @param container panel to adopt the wave panel's widget, or {@code null} for the wave panel to
   *     be a root widget
   */
  public static WavePanelImpl create(
      DomAsViewProvider views, Element panelDom, LogicalPanel container) {
    Preconditions.checkArgument(panelDom != null);
    EventDispatcherPanel events =
        (container != null)
            ? EventDispatcherPanel.inGwtContext(panelDom, container)
            : EventDispatcherPanel.of(panelDom);
    WavePanelImpl panel = new WavePanelImpl(views, events);

    // Existing content?
    Element frameDom = panelDom.getFirstChildElement();
    if (frameDom != null) {
      panel.init(frameDom);
    }
    return panel;
  }

  /** Destroys this wave panel, releasing its resources. */
  public void destroy() {
    panel.removeFromParent();
  }

  @Override
  public DomAsViewProvider getViewProvider() {
    return views;
  }

  @Override
  public EventHandlerRegistry getHandlers() {
    return panel;
  }

  @Override
  public LogicalPanel getGwtPanel() {
    return panel;
  }

  @Override
  public KeySignalRouter getKeyRouter() {
    return keys;
  }

  @Override
  public boolean hasContents() {
    return main != null;
  }

  @Override
  public TopConversationView getContents() {
    Preconditions.checkState(main != null);
    return main;
  }

  //
  // Key plumbing.
  //

  @Override
  public boolean onKeySignal(KeyCombo key) {
    return keys.onKeySignal(key);
  }

  @Override
  public void onFocus() {}

  @Override
  public void onBlur() {}

  //
  // Lifecycle.
  //

  public void init(Element main) {
    Preconditions.checkState(!initialized);

    boolean fireEvent; // true if onInit should be fired before exiting.
    if (main != null) {
      panel.getElement().appendChild(main);
      this.main = views.asTopConversation(main);
      fireEvent = true;
    } else {
      // Render empty message.
      panel.getElement().setInnerHTML("No conversations in this wave.");
      fireEvent = false;
    }
    initialized = true;

    if (fireEvent) {
      fireOnInit();
    }
  }

  public void reset() {
    Preconditions.checkState(initialized);

    boolean fireEvent; // true if onInit should be fired before exiting.
    initialized = false;
    if (main != null) {
      main.remove();
      main = null;
      fireEvent = true;
    } else {
      panel.getElement().setInnerHTML("");
      fireEvent = false;
    }

    if (fireEvent) {
      fireOnReset();
    }
  }

  @Override
  public void addListener(LifecycleListener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(LifecycleListener listener) {
    listeners.remove(listener);
  }

  private void fireOnInit() {
    for (LifecycleListener listener : listeners) {
      listener.onInit();
    }
  }

  private void fireOnReset() {
    for (LifecycleListener listener : listeners) {
      listener.onReset();
    }
  }
}
Example #3
0
public class ListType extends Type implements SourcesEvents<ListType.Listener> {

  public interface Listener {

    public void onValueAdded(Type entry);

    public void onValueRemoved(Type entry);
  }

  protected static Type createAndAttach(Model model, String id) {

    Preconditions.checkArgument(id.startsWith(PREFIX), "ListType.createAndAttach() not a list id");
    ListType list = new ListType(model);
    list.attach(id);
    return list;
  }

  public static final String TYPE_NAME = "ListType";
  public static final String PREFIX = "list";

  public static final String ROOT_TAG = "list";
  private static final String ITEM_TAG = "item";

  private ObservableElementList<Type, ListElementInitializer> observableList;
  private ObservableElementList.Listener<Type> observableListListener;

  private Model model;

  private String backendDocumentId;
  private ObservableDocument backendDocument;
  private Doc.E backendRootElement;

  private boolean isAttached;

  private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create();

  protected ListType(Model model) {
    this.model = model;
    this.isAttached = false;

    observableListListener =
        new ObservableElementList.Listener<Type>() {

          @Override
          public void onValueAdded(Type entry) {
            for (ListType.Listener l : listeners) l.onValueAdded(entry);
          }

          @Override
          public void onValueRemoved(Type entry) {
            for (ListType.Listener l : listeners) l.onValueRemoved(entry);
          }
        };
  }

  //
  // Type interface
  //

  @Override
  protected String getPrefix() {
    return PREFIX;
  }

  @Override
  protected void attach(String docId) {

    if (docId == null) {

      docId = model.generateDocId(getPrefix());
      backendDocument = model.createDocument(docId);

    } else backendDocument = model.getDocument(docId);

    backendDocumentId = docId;

    // Create a root tag to ensure the document is persisted.
    // If the doc is created empty and it's not populated with data it won't
    // exist when the wavelet is open again.
    backendRootElement = DocHelper.getElementWithTagName(backendDocument, ROOT_TAG);
    if (backendRootElement == null)
      backendRootElement =
          backendDocument.createChildElement(
              backendDocument.getDocumentElement(),
              ROOT_TAG,
              Collections.<String, String>emptyMap());

    DocEventRouter router = DefaultDocEventRouter.create(backendDocument);

    this.observableList =
        DocumentBasedElementList.create(
            router, backendRootElement, ITEM_TAG, new ListElementFactory(model));
    this.observableList.addListener(observableListListener);

    this.isAttached = true;
  }

  protected void deattach() {
    Preconditions.checkArgument(isAttached, "Unable to deattach an unattached MapType");

    // nothing to do. wavelet doesn't provide doc deletion
  }

  @Override
  protected boolean isAttached() {
    return isAttached;
  }

  @Override
  protected String serializeToModel() {
    Preconditions.checkArgument(isAttached, "Unable to serialize an unattached ListType");
    return backendDocumentId;
  }

  @Override
  protected ListElementInitializer getListElementInitializer() {
    return new ListElementInitializer() {

      @Override
      public String getType() {
        return PREFIX;
      }

      @Override
      public String getBackendId() {
        return serializeToModel();
      }
    };
  }

  //
  // Listeners
  //

  @Override
  public void addListener(Listener listener) {
    listeners.add(listener);
  }

  @Override
  public void removeListener(Listener listener) {
    listeners.remove(listener);
  }

  //
  // List operations
  //

  public Type add(Type value) {
    Preconditions.checkArgument(isAttached, "ListType.add(): not attached to model");
    Preconditions.checkArgument(
        !value.isAttached(), "ListType.add(): forbidden to add an already attached Type");

    value.attach(null);
    Type listValue = observableList.add(value.getListElementInitializer());

    // return the value generated from list to double check add() success
    // also it is the cached value in the observable list
    return listValue;
  }

  public Type add(int index, Type value) {

    Preconditions.checkArgument(
        index >= 0 && index <= observableList.size(), "ListType.add(): add to index out of bounds");
    Preconditions.checkArgument(isAttached, "ListType.add(): not attached to model");
    Preconditions.checkArgument(
        !value.isAttached(), "ListType.add(): forbidden to add an already attached Type");

    value.attach(null);
    Type listValue = observableList.add(index, value.getListElementInitializer());

    // return the value generated from list to double check add() success
    // also it is the cached value in the observable list
    return listValue;
  }

  public Type remove(int index) {
    if (observableList == null) return null;
    Type removedInstance = observableList.get(index);
    if (!observableList.remove(removedInstance)) return null;
    return removedInstance;
  }

  public Type get(int index) {
    if (observableList == null) return null;
    Preconditions.checkArgument(
        index >= 0 && index < observableList.size(), "ListType.get(): add to index out of bounds");
    return observableList.get(index);
  }

  public int indexOf(Type type) {
    return observableList != null ? observableList.indexOf(type) : -1;
  }

  public int size() {
    return observableList != null ? observableList.size() : 0;
  }

  public Iterable<Type> getValues() {
    return observableList != null ? observableList.getValues() : Collections.<Type>emptyList();
  }

  @Override
  public String getDocumentId() {
    return backendDocumentId;
  }

  @Override
  public Model getModel() {
    return model;
  }

  @Override
  public String getType() {
    return TYPE_NAME;
  }
}