/*
* Copyright 2004 - 2008 Christian Sprajc, Dennis Waldherr. All rights reserved.
*
* This file is part of PowerFolder.
*
* PowerFolder is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation.
*
* PowerFolder is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
*
* $Id$
*/
package de.dal33t.powerfolder.util;
import java.io.Serializable;
import java.util.logging.Logger;
/**
* Helper class representing a set of ranges containing values.
* This class provides the functionality to store and find ranges containing the same data.
* For example: It can (and does) manage the availability of data stored in a file. Ranges containing data or free ranges can
* be searched for.
*
* @author Dennis "Dante" Waldherr
* @version $Revision: $
*
*/
public class Partitions<T> implements Serializable {
private static final Logger log = Logger.getLogger(Partitions.class.getName());
private static final long serialVersionUID = 1L;
private Partitions<T> parent;
private Partitions<T> a;
private Partitions<T> b;
private Range range;
private T content;
/**
* Creates a new Partition with the given range containing the given value.
* @param r
* @param base
*/
public Partitions(Range r, T base) {
range = r;
content = base;
}
private Partitions(Partitions<T> p, Range range, T val) {
parent = p;
this.range = range;
content = val;
}
private boolean isLeaf() {
return a == null;
}
private boolean isRoot() {
return parent == null;
}
/**
* The partitions are stored in a binary tree, this method returns the depth of the tree.
* @return
*/
public int depth() {
if (isLeaf()) {
return 0;
}
return Math.max(a.depth(), b.depth()) + 1;
}
/**
* Returns the range in which partitions can lie.
* @return
*/
public Range getPartionedRange() {
return range;
}
/**
* Inserts the value on the given range.
* @param r
* @param value
*/
public void insert(Range r, T value) {
if (!r.intersects(range)) {
return;
}
if (r.contains(range)) {
content = value;
a = b = null;
if (!isRoot()) {
parent.checkMergeChildren();
}
return;
}
// Don't create children unless necessary
if (isLeaf() && sameValue(value, content)) {
return;
}
// If this is a leaf, create children first and then propagate insert (if necessary)
if (isLeaf()) {
long m = (range.getStart() + range.getEnd()) / 2;
a = new Partitions<T>(this, Range.getRangeByNumbers(range.getStart(), m), content);
b = new Partitions<T>(this, Range.getRangeByNumbers(m + 1, range.getEnd()), content);
}
a.insert(r, value);
// If b is null then a and b were succesfully merged
if (b != null) {
b.insert(r, value);
}
}
/**
* Searches for the given value in the given range r.
* It searches for the first range with the given value which intersects with the given range.
* The intersection between that range and the given one is returned.
* If no qualified range is found, this method returns null.
* @param r
* @param val
* @return
*/
public Range search(Range r, T val) {
if (!range.intersects(r)) {
return null;
}
if (isLeaf()) {
if (sameValue(content, val)) {
return Range.getRangeByNumbers(
Math.max(r.getStart(), range.getStart()),
Math.min(r.getEnd(), range.getEnd()));
} else {
return null;
}
}
Range ra = a.search(r, val);
Range rb = b.search(r, val);
if (ra == null) {
return rb;
}
if (rb == null) {
return ra;
}
if (ra.getEnd() + 1 == rb.getStart()) {
return Range.getRangeByNumbers(ra.getStart(), rb.getEnd());
}
return ra;
}
private void checkMergeChildren() {
if (a.isLeaf() && b.isLeaf() && sameValue(a.content, b.content)) {
content = a.content;
a = null;
b = null;
if (!isRoot()) {
parent.checkMergeChildren();
}
}
}
private boolean sameValue(T a, T b) {
return a == b || (a != null && a.equals(b));
}
public void logRanges(Class clazz) {
if (isLeaf()) {
log.info(clazz.getName() + ' ' + getPartionedRange() + " with value " + content);
return;
}
a.logRanges(clazz);
b.logRanges(clazz);
}
/**
* Counts the number of occurrences of the given value in the given range.
* @param r
* @param val
* @return
*/
public long count(Range r, T val) {
if (isLeaf()) {
if (!sameValue(content, val)) {
return 0;
}
return range.intersectionLength(r);
}
return a.count(r, val) + b.count(r, val);
}
}