/*
* Copyright (C) 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.examples.fedone.common;
import com.google.common.annotations.VisibleForTesting;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.commons.codec.binary.Hex;
import org.waveprotocol.wave.examples.fedone.model.util.HashedVersionFactoryImpl;
import org.waveprotocol.wave.examples.fedone.waveserver.ByteStringMessage;
import org.waveprotocol.wave.federation.Proto.ProtocolAppliedWaveletDelta;
import org.waveprotocol.wave.federation.Proto.ProtocolWaveletDelta;
import org.waveprotocol.wave.model.id.WaveletName;
import org.waveprotocol.wave.model.operation.wave.WaveletDelta;
import java.util.Arrays;
/**
* A {@link WaveletDelta}'s version along with a hash of the preceding history.
* For fake deltas that do not contain the correct hash, use
* {@link #unsigned(long)}.
*
* This is an immutable value class and therefore thread safe.
*
*
*/
public final class HashedVersion {
/** Same as unsigned(0). */
public static final HashedVersion UNSIGNED_VERSION_0 = unsigned(0);
private static final HashedVersionFactory HASHED_HISTORY_VERSION_FACTORY =
new HashedVersionFactoryImpl();
private final long version;
private final byte[] historyHash;
/**
* Constructs a hashed version with the specified version and historyHash.
*/
public HashedVersion(long version, byte[] historyHash) {
if (historyHash == null) {
throw new NullPointerException("null historyHash");
} else {
this.version = version;
this.historyHash = historyHash;
}
}
/**
* Constructs a HashedVersion representing version 0 of the specified wavelet.
*/
public static HashedVersion versionZero(WaveletName waveletName) {
// TODO: Why not inline that method here and get rid of the class?
return HASHED_HISTORY_VERSION_FACTORY.createVersionZero(waveletName);
}
/**
* Constructs an unhashed (i.e. unsigned) version with only a version number
* and some fake hash. This may be used when we don't rely on hashes
* for authentication and error checking.
*/
@VisibleForTesting
public static HashedVersion unsigned(long version) {
return new HashedVersion(version, new byte[0]);
}
/**
* Get the hashed version an applied delta was applied at.
*/
public static HashedVersion getHashedVersionAppliedAt(
ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta) {
if (appliedDelta.getMessage().hasHashedVersionAppliedAt()) {
return WaveletOperationSerializer.deserialize(appliedDelta.getMessage()
.getHashedVersionAppliedAt());
} else {
try {
ProtocolWaveletDelta innerDelta = ProtocolWaveletDelta.parseFrom(
appliedDelta.getMessage().getSignedOriginalDelta().getDelta());
return WaveletOperationSerializer.deserialize(innerDelta.getHashedVersion());
} catch (InvalidProtocolBufferException e) {
throw new IllegalArgumentException(e);
}
}
}
/**
* Get the hashed version after an applied delta is applied.
*/
public static HashedVersion getHashedVersionAfter(
ByteStringMessage<ProtocolAppliedWaveletDelta> appliedDelta) {
int opsApplied = appliedDelta.getMessage().getOperationsApplied();
HashedVersion versionAppliedAt = getHashedVersionAppliedAt(appliedDelta);
return new HashedVersionFactoryImpl().create(
appliedDelta.getByteArray(), versionAppliedAt, opsApplied);
}
/** The number of ops that lead to this version. */
public long getVersion() {
return version;
}
/** A hash over the entire history of ops up to this version. */
public byte[] getHistoryHash() {
return historyHash;
}
@Override
public int hashCode() {
int result = 17;
result = 31 * result + Long.valueOf(version).hashCode();
result = 31 * result + Arrays.hashCode(historyHash);
return result;
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof HashedVersion)) {
return false;
} else {
HashedVersion that = (HashedVersion) obj;
return version == that.version && Arrays.equals(historyHash, that.historyHash);
}
}
@Override
public String toString() {
return Long.toString(version) + ":" + new String(Hex.encodeHex(historyHash));
}
}