/*
* Copyright 2009 Thomas Bocek
*
* 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 net.tomp2p.rpc;
import java.util.HashMap;
import java.util.Map;
import net.tomp2p.peers.Number160;
import net.tomp2p.peers.Number480;
/**
* Calculates or sets a global hash. The digest is used in two places: for routing, where a message needs to have a
* predictable size. Thus in this case a global hash is calculated. The second usage is get() for getting a list of
* hashes from peers. Here we don't need to restrict ourself, since we use TCP.
*
* @author Thomas Bocek
*/
public class DigestInfo {
private volatile Number160 keyDigest = null;
private volatile Number160 contentDigest = null;
private volatile int size = -1;
private final Map<Number480, Number160> mapDigests = new HashMap<Number480, Number160>();
/**
* Empty constructor is used to add the hashes to the list.
*/
public DigestInfo() {
}
/**
* Create a digest with the size only.
*
* @param size
* The number of items
*/
public DigestInfo(final int size) {
this.size = size;
}
/**
* If a global hash has already been calculated, then this constructor is used to store those. Note that once a
* global hash is set it cannot be unset.
*
* @param keyDigest
* The digest of all keys
* @param contentDigest
* The digest of all contents
* @param size
* The number of entries
*/
public DigestInfo(final Number160 keyDigest, final Number160 contentDigest, final int size) {
this.keyDigest = keyDigest;
this.contentDigest = contentDigest;
this.size = size;
}
/**
* @return Returns or calculates the global key hash. The global key hash will be calculated if the empty
* constructor is used.
*/
public Number160 getKeyDigest() {
if (keyDigest == null) {
process();
}
return keyDigest;
}
/**
* @return Returns or calculates the global content hash. The global content hash will be calculated if the empty
* constructor is used.
*/
public Number160 getContentDigest() {
if (contentDigest == null) {
process();
}
return contentDigest;
}
/**
* Calculates the digest.
*/
private void process() {
Number160 hashKey = Number160.ZERO;
Number160 hashContent = Number160.ZERO;
for (Map.Entry<Number480, Number160> entry : mapDigests.entrySet()) {
hashKey = hashKey.xor(entry.getKey().getLocationKey());
hashKey = hashKey.xor(entry.getKey().getDomainKey());
hashKey = hashKey.xor(entry.getKey().getContentKey());
hashContent = hashContent.xor(entry.getValue());
}
keyDigest = hashKey;
contentDigest = hashContent;
}
/**
* @param factory
* The bloom filter creator
* @return The bloom filter of the keys that are on this peer
*/
public SimpleBloomFilter<Number160> getLocationKeyBloomFilter(final BloomfilterFactory factory) {
SimpleBloomFilter<Number160> sbf = factory.createLoctationKeyBloomFilter();
for (Map.Entry<Number480, Number160> entry : mapDigests.entrySet()) {
sbf.add(entry.getKey().getLocationKey());
}
return sbf;
}
/**
* @param factory
* The bloom filter creator
* @return The bloom filter of the keys that are on this peer
*/
public SimpleBloomFilter<Number160> getDomainKeyBloomFilter(final BloomfilterFactory factory) {
SimpleBloomFilter<Number160> sbf = factory.createDomainKeyBloomFilter();
for (Map.Entry<Number480, Number160> entry : mapDigests.entrySet()) {
sbf.add(entry.getKey().getDomainKey());
}
return sbf;
}
/**
* @param factory
* The bloom filter creator
* @return The bloom filter of the keys that are on this peer
*/
public SimpleBloomFilter<Number160> getContentKeyBloomFilter(final BloomfilterFactory factory) {
SimpleBloomFilter<Number160> sbf = factory.createContentKeyBloomFilter();
for (Map.Entry<Number480, Number160> entry : mapDigests.entrySet()) {
sbf.add(entry.getKey().getContentKey());
}
return sbf;
}
/**
* @param factory
* The bloom filter creator
* @return The bloom filter of the content keys that are on this peer
*/
public SimpleBloomFilter<Number160> getContentBloomFilter(final BloomfilterFactory factory) {
SimpleBloomFilter<Number160> sbf = factory.createContentBloomFilter();
for (Map.Entry<Number480, Number160> entry : mapDigests.entrySet()) {
sbf.add(entry.getValue());
}
return sbf;
}
/**
* Stores a key and the hash of the content for further processing.
*
* @param key
* The key of the content
* @param content
* The hash of the content
*/
public void put(final Number480 key, final Number160 content) {
mapDigests.put(key, content);
}
/**
* @return The list of hashes
*/
public Map<Number480, Number160> getDigests() {
return mapDigests;
}
/**
* @return The number of hashes
*/
public int getSize() {
if (size == -1) {
size = mapDigests.size();
}
return size;
}
/**
* @return True is the digest information has not been provided.
*/
public boolean isEmpty() {
return size <= 0;
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof DigestInfo)) {
return false;
}
DigestInfo other = (DigestInfo) obj;
return getKeyDigest().equals(other.getKeyDigest()) && getSize() == other.getSize()
&& getContentDigest().equals(other.getContentDigest());
}
@Override
public int hashCode() {
return getKeyDigest().hashCode() ^ getSize() ^ getContentDigest().hashCode();
}
}