/**
* 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 Kevin Smith, Boundless, Copyright 2015
*
* Based on FloatParameterFilter: 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 Integer value within a threshold.
*/
@ParametersAreNonnullByDefault
@XStreamAlias("integerParameterFilter")
public class IntegerParameterFilter extends ParameterFilter {
private static Integer DEFAULT_THRESHOLD = Integer.valueOf(1);
// These members get set by XStream
private List<Integer> values;
private Integer threshold;
public IntegerParameterFilter() {
super();
values = new ArrayList<Integer>(0);
}
protected IntegerParameterFilter readResolve() {
super.readResolve();
if (values == null) {
values = new ArrayList<Integer>(0);
}
if (threshold == null) {
threshold = getDefaultThreshold();
}
for(Integer value: values) {
Preconditions.checkNotNull(value, "Value list included a null pointer.");
}
return this;
}
/**
* @return
*/
protected Integer 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<Integer> getValues() {
// TODO: apply Collections.unmodifiableList(...)
return values;
}
/**
* Set the values
*/
public void setValues(List<Integer> values) {
Preconditions.checkNotNull(values);
for(Integer f: values) {
Preconditions.checkNotNull(f);
}
this.values = new ArrayList<Integer>(values);
}
/**
* @return the threshold
*/
public Integer getThreshold() {
return threshold;
}
/**
* @param threshold
* the threshold to set
*/
public void setThreshold(@Nullable Integer 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();
}
int val = Integer.parseInt(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);
}
int best = Integer.MIN_VALUE;
int bestMismatch = Integer.MAX_VALUE;
Iterator<Integer> iter = getValues().iterator();
while (iter.hasNext()) {
int fl = iter.next();
int mismatch = Math.abs(fl - val);
if (mismatch < bestMismatch) {
best = fl;
bestMismatch = mismatch;
}
}
if (threshold != null && threshold > 0 && Math.abs(bestMismatch) < threshold) {
return Integer.toString(best);
}
throw new ParameterException("Closest match for " + super.getKey() + "=" + str + " is "
+ Integer.toString(best) + ", but this exceeds the threshold of "
+ Integer.toString(threshold));
}
@Override
public @Nullable List<String> getLegalValues() {
List<String> ret = new LinkedList<String>();
Iterator<Integer> iter = getValues().iterator();
while (iter.hasNext()) {
ret.add(Integer.toString(iter.next()));
}
return ret;
}
@Override
public IntegerParameterFilter clone() {
IntegerParameterFilter clone = new IntegerParameterFilter();
clone.setDefaultValue(getDefaultValue());
clone.setKey(getKey());
if (values != null) {
clone.values = new ArrayList<Integer>(values);
}
clone.setThreshold(this.threshold);
return clone;
}
}