/** * 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(); } } }
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; } }