/*
* JABM - Java Agent-Based Modeling Toolkit
* Copyright (C) 2013 Steve Phelps
*
* This program 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; 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.
*/
package net.sourceforge.jabm;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.jabm.util.UntypedDouble;
import net.sourceforge.jabm.util.UntypedLong;
import net.sourceforge.jabm.util.UntypedNumber;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.BeanFactory;
class VariableBindingsIterator implements Iterator<Map<String,String>> {
protected Map<String, VariableRange> variableRanges;
protected ArrayList<String> variables;
protected Bindings bindings;
protected int currentVariableIndex;
protected BeanFactory beanFactory;
protected boolean hasNext;
public static final String RANGE_REGEXP =
"([0-9\\.]+)\\:([0-9\\.]+)\\:([0-9\\.]+)";
static Pattern rangePattern = Pattern.compile(RANGE_REGEXP);
static Logger logger = Logger.getLogger(VariableBindingsIterator.class);
public VariableBindingsIterator(String varFile,
BeanFactory beanFactory) {
try {
this.beanFactory = beanFactory;
parseVarFile(varFile);
bindings = initialBindings();
currentVariableIndex = variables.size() - 1;
hasNext = variables.size() > 0;
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public Bindings initialBindings() {
Bindings result = new Bindings();
for(String variable : variables) {
UntypedNumber initialValue =
variableRanges.get(variable).getMin();
result.set(variable, initialValue);
}
return result;
}
@SuppressWarnings("rawtypes")
public void parseVarFile(String varFile) throws IOException {
Properties properties = new Properties();
variableRanges = new HashMap<String, VariableRange>();
properties.load(new FileInputStream(varFile));
for(Object variable : properties.keySet()) {
Class type = getType(variable.toString());
VariableRange range =
parseVariableRange(properties.get(variable).toString(), type);
logger.debug("range = " + range);
variableRanges.put(variable.toString(), range);
}
variables = new ArrayList<String>(variableRanges.keySet());
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public Class getType(String beanProperty) {
int dot = beanProperty.indexOf('.');
String beanName = beanProperty.substring(0, dot);
String attributeName = beanProperty.substring(dot+1);
Class beanType = beanFactory.getType(beanName);
String getterName = "get" +
Character.toUpperCase(attributeName.charAt(0)) +
attributeName.substring(1);
try {
Method method = beanType.getMethod(getterName, new Class[] {});
Type returnType = method.getGenericReturnType();
if (returnType instanceof Class) {
Class result = (Class) returnType;
return result;
} else {
throw new IllegalArgumentException(beanProperty);
}
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public VariableRange parseVariableRange(String rangeSpecification,
Class type) {
Matcher matcher = rangePattern.matcher(rangeSpecification);
if (matcher.matches()) {
VariableRange result = new VariableRange();
String minStr = matcher.group(1);
String incrStr = matcher.group(2);
String maxStr = matcher.group(3);
if (type.isAssignableFrom(int.class) || type.isAssignableFrom(long.class)) {
result.setMin(new UntypedLong(Long.parseLong(minStr)));
result.setIncrement(new UntypedLong(Long.parseLong(incrStr)));
result.setMax(new UntypedLong(Long.parseLong(maxStr)));
} else if (type.isAssignableFrom(double.class) || type.isAssignableFrom(float.class)) {
result.setMin(new UntypedDouble(Double.parseDouble(minStr)));
result.setIncrement(new UntypedDouble(Double.parseDouble(incrStr)));
result.setMax(new UntypedDouble(Double.parseDouble(maxStr)));
} else {
throw new IllegalArgumentException(type.toString());
}
return result;
}
throw new IllegalArgumentException(
"Could not parse range specification: " +
rangeSpecification);
}
@Override
public boolean hasNext() {
return hasNext;
}
@Override
public Map<String, String> next() {
Map<String, String> result = bindings.getStringBindings();
logger.debug(variableName(currentVariableIndex) + " = "
+ bindings.get(currentVariableIndex));
if (lessThanMaximumValue(currentVariableIndex)) {
increment(currentVariableIndex);
} else {
// Back
while (currentVariableIndex >= 0
&& !lessThanMaximumValue(currentVariableIndex)) {
currentVariableIndex--;
}
if (currentVariableIndex < 0) {
hasNext = false;
return result;
}
increment(currentVariableIndex);
for (int i = currentVariableIndex + 1; i < variables.size(); i++) {
bindings.set(variableName(i), getMin(i));
}
currentVariableIndex = variables.size() - 1;
}
return result;
}
@Override
public void remove() {
throw new RuntimeException("Unsupported method");
}
public String variableName(int variableIndex) {
return variables.get(variableIndex);
}
public void increment(int variableIndex) {
bindings.increment(variableName(variableIndex));
}
public boolean lessThanMaximumValue(int variableIndex) {
String variableName = variableName(variableIndex);
double value = bindings.get(variableName).doubleValue();
double max = variableRanges.get(variableName).getMax().doubleValue();
return value < (max - 10E-9);
}
public UntypedNumber getMin(int variableIndex) {
String variableName = variableName(variableIndex);
return variableRanges.get(variableName).getMin();
}
class VariableRange {
public UntypedNumber min;
public UntypedNumber max;
public UntypedNumber increment;
public VariableRange(UntypedNumber min, UntypedNumber max,
UntypedNumber increment) {
super();
this.min = min;
this.max = max;
this.increment = increment;
}
public VariableRange() {
super();
}
public UntypedNumber getMin() {
return min;
}
public void setMin(UntypedNumber min) {
this.min = min;
}
public UntypedNumber getMax() {
return max;
}
public void setMax(UntypedNumber max) {
this.max = max;
}
public UntypedNumber getIncrement() {
return increment;
}
public void setIncrement(UntypedNumber increment) {
this.increment = increment;
}
public String toString() {
return min + ":" + increment + ":" + max;
}
}
class Bindings {
Map<String, UntypedNumber> variableMap;
public Bindings() {
variableMap = new HashMap<String, UntypedNumber>();
}
public void set(String variable, UntypedNumber value) {
variableMap.put(variable, value);
}
public void increment(String variable) {
UntypedNumber currentValue = variableMap.get(variable);
UntypedNumber newValue =
currentValue.add(variableRanges.get(variable).getIncrement());
set(variable, newValue);
}
public UntypedNumber getNextValue(String variable) {
UntypedNumber currentValue = variableMap.get(variable);
UntypedNumber newValue =
currentValue.add(variableRanges.get(variable).getIncrement());
return newValue;
}
public UntypedNumber get(String variable) {
return variableMap.get(variable);
}
public UntypedNumber get(int variableIndex) {
String variableName = variables.get(variableIndex);
return get(variableName);
}
public Set<String> getVariables() {
return variableMap.keySet();
}
public Map<String, String> getStringBindings() {
HashMap<String, String> result = new HashMap<String, String>();
for(String variable : variableMap.keySet()) {
result.put(variable, variableMap.get(variable).toString());
}
return result;
}
}
}