/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 org.waveprotocol.wave.model.supplement; import org.waveprotocol.wave.model.adt.ObservableBasicMap; import org.waveprotocol.wave.model.adt.ObservableBasicSet; import org.waveprotocol.wave.model.adt.ObservableBasicValue; import org.waveprotocol.wave.model.adt.ObservableMonotonicMap; import org.waveprotocol.wave.model.adt.docbased.DocumentBasedBasicSet; import org.waveprotocol.wave.model.adt.docbased.DocumentBasedBoolean; import org.waveprotocol.wave.model.adt.docbased.DocumentBasedMonotonicMap; import org.waveprotocol.wave.model.adt.docbased.DocumentBasedMonotonicValue; import org.waveprotocol.wave.model.conversation.InboxState; import org.waveprotocol.wave.model.document.ObservableDocument; import org.waveprotocol.wave.model.document.ObservableMutableDocument; import org.waveprotocol.wave.model.document.util.DefaultDocumentEventRouter; import org.waveprotocol.wave.model.document.util.DocumentEventRouter; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.id.WaveletIdSerializer; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.ReadableStringMap; import org.waveprotocol.wave.model.util.Serializer; import org.waveprotocol.wave.model.util.ValueUtils; import org.waveprotocol.wave.model.version.HashedVersion; import org.waveprotocol.wave.model.version.HashedVersionSerializer; import org.waveprotocol.wave.model.wave.Wavelet; import java.util.Set; /** * Implementation of the supplement ADT that uses a wavelet as the underlying * data structure. * * The wavelet uses separate documents for each of: * <ul> * <li>read-state</li> * <li>folder state</li> * <li>archiving state</li> * <li>muted state</li> * <li>abuse</li> * <li>seen-state and pending-notifications state</li> * </ul> * * The read state is tracked in an element per wavelet. That element tracks blip * read-versions, the participants read-version, and the wavelet-override * version, using the {@link DocumentBasedMonotonicMap} and * {@link DocumentBasedMonotonicValue} embeddings. Below is an example state of * the read-state document. * * <pre> * <data> * <wavelet i="example.org!conv+root"> * <all v="25"/> * <participants v="12"/> * <blip i="8fJd77*2" v="7"/> * <all v="28"/> * <blip i="8fJd77*7" v="38"/> * <blip i="8fJd77*7" v="11"/> * </wavelet> * <wavelet i="conversation/dRwppo8*34"> * <blip i="dRwppo8*35" v="4"/> * <blip i="dRwppo8*36" v="15"/> * </wavelet> * </data> * </pre> * * The interpretation of that state, as provided by * {@link DocumentBasedMonotonicMap} and {@link DocumentBasedMonotonicValue}, * is: * <ul> * <li>wavelet {@code example.org!conv+root} has a wavelet read-version of 28, a * participants read-version of 12, a read version of 7 for blip {@code * 8fJd77*2}, and a read version of 38 for blip {@code 8fJd77*7}.</li> * <li>wavelet {@code conversation/dRwppo8*34} has a read version of 4 for blip * {@code dRwppo8*35} and a read version of 15 for blip {@code dRwppo8*36}.</li> * </ul> * * The folder state is tracked in an element per folder, according to the * {@link DocumentBasedBasicSet} embedding. An example folder state: * * <pre> * <data> * <folder i="3"/> * <folder i="12"/> * <folder i="12"/> * </data> * </pre> * * The interpretation of that state is that the wave is in folders 3 and 12. * * The archiving state is tracked in an element per wavelet, with a map of * wavelet ids and versions. When a wave is archived, all the versions of its * conversation wavelets are saved in the archiving document. * * An example archiving state: * * <pre> * <data> * <archive i="example.org!conv+root" v="48" /> * <archive i="conversation/dRwppo8*34" v="15" /> * </data> * </pre> * * The interpretation of that state is that the wave is * {@link InboxState#ARCHIVE archived} as long as its wavelet versions don't go * above 48 for "root" and 15 for "dRwppo8*34". * * The muted state is reflected by two boolean values, stored in two separate * documents. TODO(hearnden/flopiano): improve the handling of mute and clear to * be less wasteful. * * Example: * * <pre> * <data muted="true" /> * </pre> * * Abuse State is managed by the {@link DocumentBasedAbuseStore} class. * */ public final class WaveletBasedSupplement implements ObservablePrimitiveSupplement { public static final String READSTATE_DOCUMENT = "m/read"; public static final String PRESENTATION_DOCUMENT = "m/presentation"; public static final String FOLDERS_DOCUMENT = "m/folder"; public static final String ARCHIVING_DOCUMENT = "m/archiving"; public static final String MUTED_DOCUMENT = "m/muted"; public static final String CLEARED_DOCUMENT = "m/cleared"; public static final String ABUSE_DOCUMENT = "m/abuse"; public static final String SEEN_DOCUMENT = "m/seen"; public static final String GADGETS_DOCUMENT = "m/gadgets"; public static final String SEEN_VERSION_TAG = "seen"; public static final String NOTIFIED_VERSION_TAG = "notified"; public static final String WAVELET_TAG = "wavelet"; public static final String BLIP_READ_TAG = "blip"; public static final String PARTICIPANTS_READ_TAG = "participants"; public static final String TAGS_READ_TAG = "tags"; public static final String WAVELET_READ_TAG = "all"; public static final String CONVERSATION_TAG = "conversation"; public static final String THREAD_TAG = "thread"; public static final String BLIP_TAG = "blip"; public static final String ARCHIVE_TAG = "archive"; public static final String VERSION_ATTR = "v"; public static final String ID_ATTR = "i"; public static final String FOLDER_TAG = "folder"; public static final String MUTED_TAG = "muted"; public static final String MUTED_ATTR = "muted"; public static final String CLEARED_TAG = "cleared"; public static final String CLEARED_ATTR = "cleared"; public static final String SIGNATURE_ATTR = "signature"; public static final String STATE_ATTR = "state"; public static final String NOTIFICATION_TAG = "notification"; public static final String PENDING_NOTIFICATION_ATTR = "pending"; public static final String GADGET_TAG = "gadget"; public static final String PERMISSIONS_ATTR = "p"; public static final String STATE_TAG = "state"; public static final String NAME_ATTR = "name"; public static final String VALUE_ATTR = "value"; /** Collection of per-wavelet read states. */ private final WaveletReadStateCollection<?> read; /** Collection of per-wavelet collapsed states. */ private final WaveletThreadStateCollection<?> collapsed; /** Folder state, exposed as a set. */ private final ObservableBasicSet<Integer> folders; /** mute state, exposed as a value. */ private final ObservableBasicValue<Boolean> muted; /** Archived state, per wavelet. */ private final ObservableMonotonicMap<WaveletId, Integer> waveletArchiveVersions; /** Last seen version + hash signature of wavelet **/ private final ObservableBasicMap<WaveletId, HashedVersion> seenVersion; /** Last notified version + hash signature of wavelet **/ private final ObservableBasicMap<WaveletId, Integer> notifiedVersion; /** Notification state */ private final ObservableBasicValue<Boolean> pendingNotification; /** Raw wanted evaluation data. */ private final ObservableAbuseStore abuseStore; /** Gadget states. **/ private final GadgetStateCollection<?> gadgetStates; /** * This boolean overrides the waveletArchiveVersions whenever a new archive * version is added, this boolean is set to false. When the method * {@link #clearArchiveState()} is invoked, this value is set to true. This * state is not exposed, instead it is used as a way to override the * monotonicity of {@link #waveletArchiveVersions}. */ private final ObservableBasicValue<Boolean> archiveCleared; private final CopyOnWriteSet<Listener> listeners = CopyOnWriteSet.create(); /** Forwards read-state and thread-state events. */ private final Listener forwardingListener = new Listener() { @Override public void onLastReadBlipVersionChanged(WaveletId wid, String bid, int oldVersion, int newVersion) { triggerOnLastReadBlipVersionChanged(wid, bid, oldVersion, newVersion); } @Override public void onLastReadParticipantsVersionChanged( WaveletId wid, int oldVersion, int newVersion) { triggerOnLastReadParticipantsVersionChanged(wid, oldVersion, newVersion); } @Override public void onLastReadTagsVersionChanged(WaveletId wid, int oldVersion, int newVersion) { triggerOnLastReadTagsVersionChanged(wid, oldVersion, newVersion); } @Override public void onLastReadWaveletVersionChanged(WaveletId wid, int oldVersion, int newVersion) { triggerOnLastReadWaveletVersionChanged(wid, oldVersion, newVersion); } @Override public void onThreadStateChanged(WaveletId waveletId, String threadId, ThreadState oldState, ThreadState newState) { triggerOnThreadStateChanged(waveletId, threadId, oldState, newState); } @Override public void onFollowed() { } @Override public void onUnfollowed() { } @Override public void onFollowCleared() { } @Override public void onArchiveVersionChanged(WaveletId wid, int oldVersion, int newVersion) { } @Override public void onArchiveClearChanged(boolean oldValue, boolean newValue) { } @Override public void onFolderAdded(int newFolder) { } @Override public void onFolderRemoved(int oldFolder) { } @Override public void onWantedEvaluationsChanged(WaveletId waveletId) { } @Override public void onGadgetStateChanged( String gadgetId, String key, String oldValue, String newValue) { triggerOnGadgetStateChanged(gadgetId, key, oldValue, newValue); } }; /** * Creates a supplement. * * @param userData wavelet to hold the supplement state */ private WaveletBasedSupplement(Wavelet userData) { folders = fungeCreateFolders(userData.getDocument(FOLDERS_DOCUMENT)); muted = fungeCreateMuted(userData.getDocument(MUTED_DOCUMENT)); waveletArchiveVersions = fungeCreateWaveletArchiveState( userData.getDocument(ARCHIVING_DOCUMENT)); archiveCleared = fungeCreateCleared(userData.getDocument(CLEARED_DOCUMENT)); ObservableDocument readState = userData.getDocument(READSTATE_DOCUMENT); read = fungeCreateReadState(readState, forwardingListener); collapsed = fungeCreateCollapsedState( userData.getDocument(PRESENTATION_DOCUMENT), forwardingListener); abuseStore = fungeCreateAbuseStore(userData.getDocument(ABUSE_DOCUMENT)); seenVersion = fungeCreateSeenVersion(userData.getDocument(SEEN_DOCUMENT)); notifiedVersion = fungeCreateNotifiedVersion(userData.getDocument(SEEN_DOCUMENT)); pendingNotification = fungeCreatePendingNotification( userData.getDocument(SEEN_DOCUMENT)); gadgetStates = fungeCreateGadgetStates(userData.getDocument(GADGETS_DOCUMENT), forwardingListener); hackCleanup(); // Cleanup before installing listeners, so we start from // clean state. installListeners(); } private void hackCleanup() { // Explicitly remove Inbox and All folder tokens if they are present. folders.remove(1); folders.remove(3); } /** * Installs listeners on the component structures, so that this object can * broadcast events. */ private void installListeners() { muted.addListener(new ObservableBasicValue.Listener<Boolean>() { @Override public void onValueChanged(Boolean oldValue, Boolean newValue) { if (!ValueUtils.equal(oldValue, newValue)) { // Notify based only on new value if (newValue == null) { triggerOnFollowCleared(); } else if (newValue) { triggerOnUnfollowed(); } else { triggerOnFollowed(); } } } }); folders.addListener(new ObservableBasicSet.Listener<Integer>() { @Override public void onValueAdded(Integer newValue) { triggerOnFolderAdded(newValue); } @Override public void onValueRemoved(Integer oldValue) { triggerOnFolderRemoved(oldValue); } }); archiveCleared.addListener(new ObservableBasicValue.Listener<Boolean>() { @Override public void onValueChanged(Boolean oldValue, Boolean newValue) { if (!ValueUtils.equal(oldValue, newValue)) { triggerOnArchiveClearChanged(valueOf(oldValue), valueOf(newValue)); } } }); waveletArchiveVersions.addListener(new ObservableMonotonicMap.Listener<WaveletId, Integer>() { @Override public void onEntrySet(WaveletId waveletId, Integer oldValue, Integer newValue) { triggerOnArchiveVersionChanged(waveletId, valueOf(oldValue), valueOf(newValue)); } }); abuseStore.addListener(new ObservableAbuseStore.Listener() { @Override public void onEvaluationAdded(WantedEvaluation newEvaluation) { triggerOnWantedEvaluationAdded(newEvaluation); } }); } private static int valueOf(Integer version) { return version != null ? version : NO_VERSION; } private static boolean valueOf(Boolean value) { return value != null ? value : false; } /** * Creates a supplement. * * @param userData wavelet to hold the supplement state */ public static WaveletBasedSupplement create(Wavelet userData) { return new WaveletBasedSupplement(userData); } @Override public void setLastReadBlipVersion(WaveletId waveletId, String blipId, int version) { read.setLastReadBlipVersion(waveletId, blipId, version); } @Override public void setLastReadParticipantsVersion(WaveletId waveletId, int version) { read.setLastReadParticipantsVersion(waveletId, version); } @Override public void setLastReadTagsVersion(WaveletId waveletId, int version) { read.setLastReadTagsVersion(waveletId, version); } @Override public void setLastReadWaveletVersion(WaveletId waveletId, int version) { read.setLastReadWaveletVersion(waveletId, version); } @Override public void clearReadState() { read.clear(); } @Override public void clearBlipReadState(WaveletId waveletId, String blipId) { read.clearBlipReadState(waveletId, blipId); } @Override public int getLastReadBlipVersion(WaveletId waveletId, String blipId) { return read.getLastReadBlipVersion(waveletId, blipId); } @Override public int getLastReadParticipantsVersion(WaveletId waveletId) { return read.getLastReadParticipantsVersion(waveletId); } @Override public int getLastReadTagsVersion(WaveletId waveletId) { return read.getLastReadTagsVersion(waveletId); } @Override public int getLastReadWaveletVersion(WaveletId waveletId) { return read.getLastReadWaveletVersion(waveletId); } @Override public Iterable<String> getReadBlips(WaveletId waveletId) { return read.getReadBlips(waveletId); } @Override public Iterable<WaveletId> getReadWavelets() { return read.getReadWavelets(); } // // Thread State concerns // @Override public ThreadState getThreadState(WaveletId wid, String threadId) { return collapsed.getThreadState(wid, threadId); } @Override public void setThreadState(WaveletId wid, String threadId, ThreadState newState) { collapsed.setThreadState(wid, threadId, newState); } @Override public Iterable<String> getStatefulThreads(WaveletId waveletId) { return collapsed.getStatefulThreads(waveletId); } @Override public Iterable<WaveletId> getWaveletsWithThreadState() { return collapsed.getStatefulWavelets(); } // // Folder concerns // @Override public void addFolder(int id) { folders.add(id); } @Override public void removeAllFolders() { folders.clear(); } @Override public void removeFolder(int id) { folders.remove(id); } public Iterable<Integer> getFolders() { return folders.getValues(); } @Override public boolean isInFolder(int id) { return folders.contains(id); } // // Inbox concerns // private boolean isCleared() { Boolean value = archiveCleared.get(); return value != null ? value : false; } @Override public void follow() { muted.set(false); } @Override public void unfollow() { muted.set(true); } @Override public void clearFollow() { muted.set(null); } @Override public Boolean getFollowed() { return inverse(muted.get()); } private static Boolean inverse(Boolean b) { return b != null ? !b : null; } @Override public int getArchiveWaveletVersion(WaveletId waveletId) { if (isCleared()) { return NO_VERSION; } Integer version = waveletArchiveVersions.get(waveletId); return version != null ? version : NO_VERSION; } @Override public void archiveAtVersion(WaveletId waveletId, int waveletVersion) { waveletArchiveVersions.put(waveletId, waveletVersion); if (isCleared()) { archiveCleared.set(false); } } @Override public void clearArchiveState() { // TODO(user) // Currently it is not possible to clear a monotonic map, or to remove a // document. // Instead of actually clearing the archive state, here we set a private // flag to true. if (!isCleared()) { archiveCleared.set(true); } } @Override public Set<WaveletId> getSeenWavelets() { return seenVersion.keySet(); } @Override public Set<WaveletId> getNotifiedWavelets() { return notifiedVersion.keySet(); } @Override public HashedVersion getSeenVersion(WaveletId waveletId) { HashedVersion seenSignature = seenVersion.get(waveletId); if (null == seenSignature) { return HashedVersion.unsigned(0); } return seenSignature; } @Override public void setSeenVersion(WaveletId waveletId, HashedVersion signature) { seenVersion.put(waveletId, signature); } @Override public void clearSeenVersion(WaveletId waveletId) { seenVersion.remove(waveletId); } @Override public Iterable<WaveletId> getArchiveWavelets() { return waveletArchiveVersions.keySet(); } // // Wanted handling - forward to abuse store. // @Override public Set<WantedEvaluation> getWantedEvaluations() { return abuseStore.getWantedEvaluations(); } @Override public void addWantedEvaluation(WantedEvaluation evaluation) { abuseStore.addWantedEvaluation(evaluation); } // Notifications @Override public boolean getPendingNotification() { Boolean pending = pendingNotification.get(); return pending != null && pending; } @Override public int getNotifiedVersion(WaveletId waveletId) { Integer version = notifiedVersion.get(waveletId); return version != null ? version : NO_VERSION; } @Override public void setNotifiedVersion(WaveletId waveletId, int signature) { notifiedVersion.put(waveletId, signature); } @Override public void clearPendingNotification() { pendingNotification.set(null); } // // Gadget states // @Override public ReadableStringMap<String> getGadgetState(String gadgetId) { return gadgetStates.getGadgetState(gadgetId); } @Override public void setGadgetState(String gadgetId, String key, String value) { gadgetStates.setGadgetState(gadgetId, key, value); } // // Observable aspect // @Override public void addListener(Listener listener) { listeners.add(listener); } @Override public void removeListener(Listener listener) { listeners.remove(listener); } private void triggerOnLastReadBlipVersionChanged( WaveletId waveletId, String blipId, int oldVersion, int newVersion) { for (Listener listener : listeners) { listener.onLastReadBlipVersionChanged(waveletId, blipId, oldVersion, newVersion); } } private void triggerOnLastReadParticipantsVersionChanged( WaveletId waveletId, int oldVersion, int newVersion) { for (Listener listener : listeners) { listener.onLastReadParticipantsVersionChanged(waveletId, oldVersion, newVersion); } } private void triggerOnLastReadTagsVersionChanged( WaveletId waveletId, int oldVersion, int newVersion) { for (Listener listener : listeners) { listener.onLastReadTagsVersionChanged(waveletId, oldVersion, newVersion); } } private void triggerOnLastReadWaveletVersionChanged( WaveletId waveletId, int oldVersion, int newVersion) { for (Listener listener : listeners) { listener.onLastReadWaveletVersionChanged(waveletId, oldVersion, newVersion); } } private void triggerOnFollowed() { for (Listener listener : listeners) { listener.onFollowed(); } } private void triggerOnUnfollowed() { for (Listener listener : listeners) { listener.onUnfollowed(); } } private void triggerOnFollowCleared() { for (Listener listener : listeners) { listener.onFollowCleared(); } } private void triggerOnFolderAdded(int newFolder) { for (Listener listener : listeners) { listener.onFolderAdded(newFolder); } } private void triggerOnFolderRemoved(int oldFolder) { for (Listener listener : listeners) { listener.onFolderAdded(oldFolder); } } private void triggerOnArchiveVersionChanged( WaveletId waveletId, int oldVersion, int newVersion) { for (Listener listener : listeners) { listener.onArchiveVersionChanged(waveletId, oldVersion, newVersion); } } private void triggerOnArchiveClearChanged(boolean oldValue, boolean newValue) { for (Listener listener : listeners) { listener.onArchiveClearChanged(oldValue, newValue); } } private void triggerOnWantedEvaluationAdded(WantedEvaluation newEvaluation) { WaveletId waveletId = newEvaluation.getWaveletId(); if (waveletId == null) { return; } for (Listener listener : listeners) { listener.onWantedEvaluationsChanged(waveletId); } } private void triggerOnThreadStateChanged(WaveletId waveletId, String threadId, ThreadState oldState, ThreadState newState) { for (Listener listener : listeners) { listener.onThreadStateChanged(waveletId, threadId, oldState, newState); } } private void triggerOnGadgetStateChanged( String gadgetId, String key, String oldValue, String newValue) { for (Listener listener : listeners) { listener.onGadgetStateChanged(gadgetId, key, oldValue, newValue); } } // // Factory methods for component structures. // /** * Exposes a document as a boolean, suitable for holding muted state. * * @param router router for the muted document * @return muted state. */ private static <E> ObservableBasicValue<Boolean> createMuted( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBoolean.create(router, router.getDocument().getDocumentElement(), MUTED_TAG, MUTED_ATTR); } /** * Exposes a document as a boolean, suitable for holding muted state. * * @param router router for the muted document * @return muted state. */ private static <E> ObservableBasicValue<Boolean> createCleared( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBoolean.create(router, router.getDocument().getDocumentElement(), CLEARED_TAG, CLEARED_ATTR); } /** * Exposes a document as an integer set, suitable for holding folder state. * * @param router router for the folders document * @return folder state. */ private static <E> ObservableBasicSet<Integer> createFolders( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBasicSet.create(router, router.getDocument().getDocumentElement(), Serializer.INTEGER, FOLDER_TAG, ID_ATTR); } /** * Exposes a document as a map from wavelet ids to version numbers. * * @param router router for the archiving document * @return archiving state. */ private static <E> ObservableMonotonicMap<WaveletId, Integer> createWaveletArchiveState( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedMonotonicMap.create(router, router.getDocument().getDocumentElement(), WaveletIdSerializer.INSTANCE, Serializer.INTEGER, ARCHIVE_TAG, ID_ATTR, VERSION_ATTR); } /** * Exposes a document as a boolean, suitable for holding pending-notification state. * * @param router router for the notification document * @return pending notification state. */ private static <E> ObservableBasicValue<Boolean> createPendingNotification( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedBoolean.create( router, router.getDocument().getDocumentElement(), NOTIFICATION_TAG, PENDING_NOTIFICATION_ATTR); } /** * Exposes a document as a collection of per-wavelet read-state objects. * * @param router router for read-state document * @return wavelet read-state. */ private static <E> WaveletReadStateCollection<E> createWaveletReadState( DocumentEventRouter<? super E, E, ?> router, Listener listener) { E container = router.getDocument().getDocumentElement(); return WaveletReadStateCollection.create(router, container, listener); } /** * Exposes a document as a collection of per-wavelet thread-state objects. * * @param router router for the thread-state document * @return wavelet thread state collection. */ private static <E> WaveletThreadStateCollection<E> createWaveletCollapsedState( DocumentEventRouter<? super E, E, ?> router, Listener listener) { return WaveletThreadStateCollection.create(router, router.getDocument().getDocumentElement(), listener); } /** * Exposes a document as a map of per-wavelet seen-version/seen-hash pairs. * * @param router router for the seen-state document * @return wavelet seen version and hash signature. */ private static <E> ObservableBasicMap<WaveletId, HashedVersion> createWaveletSeenVersion( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedMonotonicMap.create(router, router.getDocument().getDocumentElement(), WaveletIdSerializer.INSTANCE, HashedVersionSerializer.INSTANCE, SEEN_VERSION_TAG, ID_ATTR, SIGNATURE_ATTR); } /** * Exposes a document as a map of per-wavelet notified-versions. * * @param router router for the notified-state document * @return wavelet notified version. */ private static <E> ObservableBasicMap<WaveletId, Integer> createWaveletNotifiedVersion( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedMonotonicMap.create(router, router.getDocument().getDocumentElement(), WaveletIdSerializer.INSTANCE, Serializer.INTEGER, NOTIFIED_VERSION_TAG, ID_ATTR, VERSION_ATTR); } /** * Exposes a document as a map of maps of gadget states. * * @param router router for the gadget state document. * @param listener event listener. * @return gadget state collection object to access gadgets states by gadget * ID and state name. */ private static <E> GadgetStateCollection<E> createGadgetStatesDoc( DocumentEventRouter<? super E, E, ?> router, Listener listener) { return GadgetStateCollection.create(router, router.getDocument().getDocumentElement(), listener); } /** * Exposes a document as a set of {@link WantedEvaluation}s. * * @param router abuse document * @return all wanted evaluations for the wave. */ private static <E> ObservableAbuseStore createAbuseStore( DocumentEventRouter<? super E, E, ?> router) { return DocumentBasedAbuseStore.create(router); } // // Funge methods for unfurling generics, required only for Sun JDK compiler. // private static <N> ObservableBasicSet<Integer> fungeCreateFolders( ObservableMutableDocument<N, ?, ?> doc) { return createFolders(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableBasicValue<Boolean> fungeCreateMuted( ObservableMutableDocument<N, ?, ?> doc) { return createMuted(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableBasicValue<Boolean> fungeCreateCleared( ObservableMutableDocument<N, ?, ?> doc) { return createCleared(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableMonotonicMap<WaveletId, Integer> fungeCreateWaveletArchiveState( ObservableMutableDocument<N, ?, ?> doc) { return createWaveletArchiveState(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableBasicValue<Boolean> fungeCreatePendingNotification( ObservableMutableDocument<N, ?, ?> doc) { return createPendingNotification(DefaultDocumentEventRouter.create(doc)); } private static <N> WaveletReadStateCollection<?> fungeCreateReadState( ObservableMutableDocument<N, ?, ?> doc, Listener listener) { return createWaveletReadState(DefaultDocumentEventRouter.create(doc), listener); } private static <N> WaveletThreadStateCollection<?> fungeCreateCollapsedState( ObservableMutableDocument<N, ?, ?> doc, Listener listener) { return createWaveletCollapsedState(DefaultDocumentEventRouter.create(doc), listener); } private static <N> ObservableBasicMap<WaveletId, HashedVersion> fungeCreateSeenVersion( ObservableMutableDocument<N, ?, ?> doc) { return createWaveletSeenVersion(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableBasicMap<WaveletId, Integer> fungeCreateNotifiedVersion( ObservableMutableDocument<N, ?, ?> doc) { return createWaveletNotifiedVersion(DefaultDocumentEventRouter.create(doc)); } private static <N> ObservableAbuseStore fungeCreateAbuseStore( ObservableMutableDocument<N, ?, ?> doc) { return createAbuseStore(DefaultDocumentEventRouter.create(doc)); } private static <N> GadgetStateCollection<?> fungeCreateGadgetStates( ObservableMutableDocument<N, ?, ?> doc, Listener listener) { return createGadgetStatesDoc(DefaultDocumentEventRouter.create(doc), listener); } }