/** * Copyright 2009 Google Inc. * * 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 org.waveprotocol.wave.model.wave.opbased; import org.waveprotocol.wave.model.wave.ObservableWavelet; import org.waveprotocol.wave.model.wave.WaveViewListener; import org.waveprotocol.wave.model.wave.Wavelet; import org.waveprotocol.wave.model.id.IdGenerator; import org.waveprotocol.wave.model.id.WaveId; import org.waveprotocol.wave.model.id.WaveletId; import org.waveprotocol.wave.model.util.CopyOnWriteSet; import org.waveprotocol.wave.model.util.Preconditions; import org.waveprotocol.wave.model.wave.ParticipantId; import java.util.HashMap; import java.util.Map; /** * Basic implementation of a wave view, parameterized by a factory for creating * individual wavelets in this view. * * @param <T> Type of wavelets in this view */ public final class WaveViewImpl<T extends ObservableWavelet> implements ObservableWaveView { /** * Factory for creating new wavelets. * * @param <T> Wavelet type created by this factory */ public interface WaveletFactory<T extends ObservableWavelet> { T create(WaveId waveId, WaveletId waveletId, ParticipantId creator); } /** * Available strategies for configuring wavelets created by this view. * {@link #ADD_CREATOR} is the default. */ public enum WaveletConfigurator { /** * Adds the creator as a participant. Recommended mode, due to the * underlying access-control semantics. */ ADD_CREATOR { @Override void configure(Wavelet wavelet) { wavelet.addParticipant(wavelet.getCreatorId()); } }, /** * Does nothing. Only to be used by smart agents who know about view-server * access-control mechanisms. */ NONE { @Override void configure(Wavelet wwavelet) { // Do nothing } }, /** * Throws an exception, to indicate that this view is not expected to create * wavelets. */ ERROR { @Override void configure(Wavelet wavelet) { throw new IllegalStateException("Unexpected configuration request on this view"); } }; /** * Performs some initial actions on a wavelet created by this view. * * @param wavelet new wavelet */ abstract void configure(Wavelet wavelet); } // // Internal state. // /** Wave id. */ private final WaveId waveId; /** Wavelets in thie view. */ private final Map<WaveletId, T> wavelets = new HashMap<WaveletId, T>(); private final CopyOnWriteSet<WaveViewListener> listeners = CopyOnWriteSet.create(); // // Helpers and parameterization. // private final ParticipantId viewer; private final IdGenerator idGenerator; private final WaveletConfigurator configurator; private final WaveletFactory<? extends T> factory; // TODO(anorth/hearnden): Move known ids into an application-level object. private final WaveletId rootId; private final WaveletId userDataId; /** * Creates a wave view (dependency injection constructor). */ WaveViewImpl(WaveletFactory<? extends T> factory, WaveId waveId, IdGenerator idGenerator, ParticipantId viewer, WaveletConfigurator configurator) { this.waveId = waveId; this.factory = factory; this.viewer = viewer; this.idGenerator = idGenerator; this.configurator = configurator; this.rootId = idGenerator.newConversationRootWaveletId(); this.userDataId = idGenerator.newUserDataWaveletId(viewer.getAddress()); } public static <T extends ObservableWavelet> WaveViewImpl<T> create( WaveletFactory<? extends T> factory, WaveId waveId, IdGenerator idGenerator, ParticipantId viewer, WaveletConfigurator configurator) { return new WaveViewImpl<T>(factory, waveId, idGenerator, viewer, configurator); } // // Services provided by this implementation. // public void addWavelet(T wavelet) { Preconditions.checkArgument(!wavelets.containsKey(wavelet.getId()), "Added multiple wavelets with same id: " + wavelet); wavelets.put(wavelet.getId(), wavelet); triggerOnWaveletAdded(wavelet); } /** * This method takes the upper bound of T (ObservableWavelet) rather than * T in order to preserve the substitutability principle. It is the same * reason that {@code Collection<T>#remove()} takes Object, rather than T. * * @param wavelet The wavelet to remove. */ public void removeWavelet(ObservableWavelet wavelet) { Preconditions.checkArgument(wavelets.containsKey(wavelet.getId()), "Removed a wavelet that doesn't exist: " + wavelet); wavelets.remove(wavelet.getId()); triggerOnWaveletRemoved(wavelet); } public T createWavelet(WaveletId id) { T wavelet = factory.create(waveId, id, viewer); configurator.configure(wavelet); addWavelet(wavelet); return wavelet; } // // WaveView implementation: // @Override public T createRoot() { try { return createWavelet(rootId); } catch (IllegalArgumentException e) { throw new IllegalStateException("Attempted to create duplicate root wavelet", e); } } @Override public T createWavelet() { // Synthesize new id, and create a new channel for it. return createWavelet(idGenerator.newConversationWaveletId()); } @Override public T createUserData() { return createWavelet(userDataId); } @Override public T getWavelet(WaveletId waveletId) { return wavelets.get(waveletId); } @Override public T getRoot() { return getWavelet(rootId); } @Override public T getUserData() { return getWavelet(userDataId); } @Override public Iterable<? extends T> getWavelets() { return wavelets.values(); } @Override public WaveId getWaveId() { return waveId; } // // Observable extension: // @Override public void addListener(WaveViewListener listener) { listeners.add(listener); } @Override public void removeListener(WaveViewListener listener) { listeners.remove(listener); } private void triggerOnWaveletAdded(ObservableWavelet wavelet) { for (WaveViewListener listener : listeners) { listener.onWaveletAdded(wavelet); } } private void triggerOnWaveletRemoved(ObservableWavelet wavelet) { for (WaveViewListener listener : listeners) { listener.onWaveletRemoved(wavelet); } } }