/**
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* @author Arne Kepp, The Open Planning Project, Copyright 2009
*/
package org.geowebcache.filter.parameters;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import com.google.common.base.Preconditions;
import com.thoughtworks.xstream.annotations.XStreamAlias;
/**
* Filter to select the closest floating point value within a threshold.
*/
@ParametersAreNonnullByDefault
@XStreamAlias("floatParameterFilter")
public class FloatParameterFilter extends ParameterFilter {
private static final long serialVersionUID = 4186888723396139208L;
private static Float DEFAULT_THRESHOLD = Float.valueOf(1E-6f);
// These members get set by XStream
private List<Float> values;
private Float threshold;
public FloatParameterFilter() {
super();
values = new ArrayList<Float>(0);
}
protected FloatParameterFilter readResolve() {
super.readResolve();
if (values == null) {
values = new ArrayList<Float>(0);
}
if (threshold == null) {
threshold = getDefaultThreshold();
}
for(Float value: values) {
Preconditions.checkNotNull(value, "Value list included a null pointer.");
}
return this;
}
/**
* @return
*/
protected Float getDefaultThreshold() {
return DEFAULT_THRESHOLD;
}
/**
* @return the values the parameter can take. Altering this list is deprecated and in future
* it will be unmodifiable; use {@link setValues} instead.
*/
public List<Float> getValues() {
// TODO: apply Collections.unmodifiableList(...)
return values;
}
/**
* Set the values
*/
public void setValues(List<Float> values) {
Preconditions.checkNotNull(values);
for(Float f: values) {
Preconditions.checkNotNull(f);
}
this.values = new ArrayList<Float>(values);
}
/**
* @return the threshold
*/
public Float getThreshold() {
return threshold;
}
/**
* @param threshold
* the threshold to set
*/
public void setThreshold(@Nullable Float threshold) {
if(threshold==null) threshold = getDefaultThreshold();
this.threshold = threshold;
}
@Override
public String apply(@Nullable String str) throws ParameterException {
if (str == null || str.length() == 0) {
return getDefaultValue();
}
float val = Float.parseFloat(str);
if (values.isEmpty()) {
// this is an accept all filter. At least it will match 2.0 and 2.000 as the same value
return String.valueOf(val);
}
float best = Float.MIN_VALUE;
float bestMismatch = Float.MAX_VALUE;
Iterator<Float> iter = getValues().iterator();
while (iter.hasNext()) {
Float fl = iter.next();
float mismatch = Math.abs(fl - val);
if (mismatch < bestMismatch) {
best = fl;
bestMismatch = mismatch;
}
}
if (threshold != null && threshold > 0 && Math.abs(bestMismatch) < threshold) {
return Float.toString(best);
}
throw new ParameterException("Closest match for " + super.getKey() + "=" + str + " is "
+ Float.toString(best) + ", but this exceeds the threshold of "
+ Float.toString(threshold));
}
@Override
public @Nullable List<String> getLegalValues() {
List<String> ret = new LinkedList<String>();
Iterator<Float> iter = getValues().iterator();
while (iter.hasNext()) {
ret.add(Float.toString(iter.next()));
}
return ret;
}
@Override
public FloatParameterFilter clone() {
FloatParameterFilter clone = new FloatParameterFilter();
clone.setDefaultValue(getDefaultValue());
clone.setKey(getKey());
if (values != null) {
clone.values = new ArrayList<Float>(values);
}
clone.setThreshold(this.threshold);
return clone;
}
}