/*
* Copyright 2010 The Rabbit Eclipse Plug-in Project
*
* 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 rabbit.ui.internal.util;
import rabbit.ui.IProvider;
import rabbit.ui.internal.viewers.IValueProvider;
import rabbit.ui.internal.viewers.TreePaths;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Predicates.alwaysTrue;
import com.google.common.base.Objects;
import com.google.common.base.Predicate;
import com.google.common.collect.Sets;
import org.eclipse.jface.viewers.TreePath;
import java.util.Collection;
import java.util.Observable;
import java.util.Set;
import javax.annotation.Nullable;
/**
* A value provider for tree paths.
* <p>
* {@link #getValue(Object)} can be used to find out the value of a particular
* tree path, the path doesn't need to be a complete path from the root of the
* tree to a leaf node, it can be a sub path such that it's a parent path of one
* or more child paths, than the value of this parent path will be the sum of
* all its child paths.
* </p>
* <p>
* This value provider also take a {@link IProvider} for getting the paths, the
* paths supplied should be complete paths (all are tree leaves), and the value
* of each of these paths is determined by a {@link IConverter}.
* </p>
* <p>
* This class also accepts a {@link ICategorizer} for getting the category of
* each path, if the category of a given path is equal to
* {@link #getVisualCategory()} then {@link #shouldPaint(Object)} will return
* true on the given path.
* </p>
*/
public final class TreePathValueProvider extends Observable
implements IValueProvider, IVisualProvider {
private final ICategorizer categorizer;
private final IProvider<TreePath> pathProvider;
private final IConverter<TreePath> converter;
private ICategory visual;
private long max;
/**
* Constructor.
* @param categorizer for categorizing tree paths.
* @param treePathProvider for getting the tree leaf paths.
* @param converter for getting value of a tree path supplied by
* {@code treePathProvider}.
* @throws NullPointerException if any argument is null.
*/
public TreePathValueProvider(
ICategorizer categorizer,
IProvider<TreePath> treePathProvider,
IConverter<TreePath> converter) {
this(categorizer, treePathProvider, converter, null);
}
/**
* @param categorizer for categorizing tree paths.
* @param treePathProvider for getting the tree leaf paths.
* @param converter for getting value of a tree path supplied by
* {@code treePathProvider}.
* @param visualCategory the default visual category.
* @throws NullPointerException if any of
* {@code categorizer, treePathProvider, converter} is null.
*/
public TreePathValueProvider(
ICategorizer categorizer,
IProvider<TreePath> treePathProvider,
IConverter<TreePath> converter,
@Nullable ICategory visualCategory) {
this.categorizer = checkNotNull(categorizer, "categorizer");
this.pathProvider = checkNotNull(treePathProvider, "treePathProvider");
this.converter = checkNotNull(converter, "converter");
this.visual = visualCategory;
}
/**
* @return the categorizer for categorizing the elements.
*/
public ICategorizer getCategorizer() {
return categorizer;
}
/**
* @return the converter for converting a tree leaf to a value.
*/
public IConverter<TreePath> getConverter() {
return converter;
}
@Override
public long getMaxValue() {
return max;
}
/**
* @return the provider that provides tree leaves.
*/
public IProvider<TreePath> getProvider() {
return pathProvider;
}
/**
* Gets the value of the given tree path. If the given tree path is a parent
* path of one or more child paths, then the sum of the children values will
* be returned.
* @param element the tree path to get the value for.
* @return the value of the tree path, may be zero. Zero is also returned if
* the given element is not a tree path.
*/
@Override
public long getValue(@Nullable Object element) {
if (!(element instanceof TreePath)) {
return 0;
}
return getValue((TreePath) element, getProvider().get());
}
@Override
public ICategory getVisualCategory() {
return visual;
}
/**
* Sets the max value using a category. If the category of a tree node is
* equal to the given category, and the value of that path (from the root to
* that element) is the highest of all, that the max value will be set to that
* value.
* @param category the category.
*/
public void setMaxValue(ICategory category) {
setMaxValue(category, alwaysTrue());
}
/**
* Sets the max value using a category a predicate. If the category of a tree
* node is equal to the given category, and the
* {@link Predicate#apply(Object)} returns true on the element, and the value
* of that path (from the root to that element) is the highest of all, that
* the max value will be set to that value.
* @param category the category.
* @param predicate the predicate to select wanted elements.
*/
public void setMaxValue(ICategory category,
Predicate<? super Object> predicate) {
Set<TreePath> paths = Sets.newHashSet();
Collection<TreePath> leaves = getProvider().get();
for (TreePath leaf : leaves) {
for (int i = 0; i < leaf.getSegmentCount(); ++i) {
Object segment = leaf.getSegment(i);
ICategory another = getCategorizer().getCategory(segment);
if (Objects.equal(category, another) && predicate.apply(segment)) {
paths.add(TreePaths.headPath(leaf, i + 1));
break;
}
}
}
long max = 0;
for (TreePath path : paths) {
long value = getValue(path, leaves);
if (value > max) {
max = value;
}
}
setMaxValue(max);
}
/**
* Sets the max value representing the highest value.
* @param max the new value.
*/
public void setMaxValue(long max) {
this.max = max;
}
/**
* Sets the visual category. If the category is not supported by the
* categorizer then it will be ignored and this method will return false. If
* {@link #getVisualCategory()} returns a different category after this change
* then observers of this instance will get notified.
* @param category the new category.
* @return true if the category is accepted, false otherwise.
*/
@Override
public boolean setVisualCategory(ICategory category) {
if (getCategorizer().hasCategory(category)) {
ICategory old = visual;
visual = category;
if (!Objects.equal(old, visual)) {
setChanged();
notifyObservers();
}
return true;
}
return false;
}
@Override
public boolean shouldPaint(Object element) {
return Objects.equal(
getVisualCategory(),
getCategorizer().getCategory(element));
}
/**
* Gets the value of the given tree path. If the given tree path is a parent
* path of one or more child paths, then the sum of the children values will
* be returned.
* @return the value of the tree path, may be zero.
*/
private long getValue(TreePath path, Collection<TreePath> leaves) {
long value = 0;
for (TreePath leaf : leaves) {
if (leaf.startsWith(path, null)) {
value += converter.convert(leaf);
}
}
return value;
}
}