/** * 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.util; import com.google.common.annotations.VisibleForTesting; /** * Utility base class for implementing chained data. * * @author danilatos@google.com (Daniel Danilatos) * * @see DataDomain for details on the type parameters */ public class ChainedData<R, T extends R> { private final DataDomain<R, T> domain; protected final ChainedData<R, T> parent; private boolean frozen = false; private final T overlay; private final T cache; private final R roCache; // 2 billion is probably big enough for everyone, but since on the client everything's // a doublle anyway, this doesn't really hurt. private double version = 0; private double knownParentVersion = 0; public ChainedData(DataDomain<R, T> domain) { this.domain = domain; this.parent = null; this.overlay = domain.empty(); this.cache = overlay; this.roCache = domain.readOnlyView(cache); } public ChainedData(ChainedData<R, T> parent) { this.domain = parent.domain; this.parent = parent; this.overlay = domain.empty(); this.cache = domain.empty(); this.roCache = domain.readOnlyView(cache); } /** * Must be called whenever inspecting the current state, which is returned. * The state must not be modified without calling the {@link #modify()} method. */ public R inspect() { maybeUpdateCache(); return roCache; } /** * Must be called when updating the local overrides, and the changes * are applied to the return value */ public T modify() { version++; knownParentVersion = -1; return overlay; } /** * Freezes the collection so that changes to ancestors, or from calls to * {@link #modify()} do not affect the return value of {@link #inspect()} * * First, updates the cache to get an up-to-date view. */ public void freeze() { if (parent == null) { throw new UnsupportedOperationException("Cannot freeze the root collection"); } maybeUpdateCache(); frozen = true; } /** * Returns to normal mode. */ public void unfreeze() { frozen = false; } private void maybeUpdateCache() { if (frozen) { return; } if (parent != null) { parent.maybeUpdateCache(); if (parent.version != knownParentVersion) { domain.compose(cache, overlay, parent.roCache); knownParentVersion = parent.version; version++; } } } @VisibleForTesting public double debugGetVersion() { return version; } @VisibleForTesting public double debugGetKnownParentVersion() { return knownParentVersion; } }