ingo@119: package de.intevation.flys.artifacts;
ingo@119:
sascha@637: import java.util.ArrayList;
ingo@623: import java.util.Collection;
sascha@650: import java.util.Collections;
ingo@691: import java.util.HashMap;
ingo@686: import java.util.TreeMap;
ingo@119: import java.util.List;
ingo@121: import java.util.Map;
ingo@122: import java.util.Set;
ingo@121:
ingo@121: import javax.xml.xpath.XPathConstants;
ingo@119:
ingo@379: import gnu.trove.TDoubleArrayList;
ingo@379:
ingo@686: import net.sf.ehcache.Cache;
ingo@686:
ingo@119: import org.apache.log4j.Logger;
ingo@119:
ingo@119: import org.w3c.dom.Document;
ingo@121: import org.w3c.dom.Element;
ingo@121: import org.w3c.dom.Node;
ingo@121: import org.w3c.dom.NodeList;
ingo@119:
ingo@119: import de.intevation.artifacts.ArtifactFactory;
ingo@119: import de.intevation.artifacts.CallContext;
ingo@119:
ingo@119: import de.intevation.artifacts.common.ArtifactNamespaceContext;
ingo@119: import de.intevation.artifacts.common.utils.XMLUtils;
ingo@119:
ingo@119: import de.intevation.artifactdatabase.DefaultArtifact;
ingo@121: import de.intevation.artifactdatabase.data.DefaultStateData;
ingo@119: import de.intevation.artifactdatabase.data.StateData;
ingo@687: import de.intevation.artifactdatabase.state.Facet;
ingo@119: import de.intevation.artifactdatabase.state.State;
ingo@119: import de.intevation.artifactdatabase.state.StateEngine;
ingo@122: import de.intevation.artifactdatabase.transition.TransitionEngine;
ingo@119:
sascha@655: import de.intevation.flys.utils.DoubleUtil;
sascha@655:
ingo@317: import de.intevation.flys.model.Gauge;
ingo@402: import de.intevation.flys.model.Range;
ingo@317: import de.intevation.flys.model.River;
ingo@317:
ingo@119: import de.intevation.flys.artifacts.context.FLYSContext;
sascha@650:
ingo@686: import de.intevation.flys.artifacts.cache.CacheFactory;
ingo@686:
ingo@377: import de.intevation.flys.artifacts.model.DischargeTables;
ingo@317: import de.intevation.flys.artifacts.model.RiverFactory;
sascha@650: import de.intevation.flys.artifacts.model.Segment;
sascha@650:
ingo@322: import de.intevation.flys.artifacts.states.DefaultState;
sascha@697: import de.intevation.flys.artifacts.states.DefaultState.ComputeType;
ingo@628: import de.intevation.flys.artifacts.states.LocationDistanceSelect;
ingo@119:
ingo@119:
ingo@119: /**
ingo@119: * The defaul FLYS artifact.
ingo@119: *
ingo@119: * @author Ingo Weinzierl
ingo@119: */
ingo@119: public abstract class FLYSArtifact extends DefaultArtifact {
ingo@119:
ingo@119: /** The logger that is used in this artifact.*/
ingo@119: private static Logger logger = Logger.getLogger(FLYSArtifact.class);
ingo@119:
ingo@119:
ingo@686: public static final String COMPUTING_CACHE = "computed.values";
ingo@686:
ingo@121: /** The XPath that points to the input data elements of the FEED document.*/
ingo@121: public static final String XPATH_FEED_INPUT =
ingo@121: "/art:action/art:data/art:input";
ingo@119:
ingo@122: /** The XPath that points to the name of the target state of ADVANCE.*/
ingo@122: public static final String XPATH_ADVANCE_TARGET =
ingo@122: "/art:action/art:target/@art:name";
ingo@122:
ingo@122: /** The constant string that shows that an operation was successful.*/
ingo@122: public static final String OPERATION_SUCCESSFUL = "SUCCESS";
ingo@122:
ingo@122: /** The constant string that shows that an operation failed.*/
ingo@122: public static final String OPERATION_FAILED = "FAILURE";
ingo@122:
ingo@362: /** The default number of steps between the start end end of a selected Q
ingo@362: * range.*/
ingo@362: public static final int DEFAULT_Q_STEPS = 30;
ingo@362:
ingo@398: /** The default step width between the start end end kilometer.*/
ingo@398: public static final double DEFAULT_KM_STEPS = 0.1;
ingo@398:
ingo@119:
ingo@119: /** The identifier of the current state. */
ingo@119: protected String currentStateId;
ingo@119:
ingo@122: /** The identifiers of previous states on a stack.*/
sascha@661: protected List previousStateIds;
ingo@122:
ingo@119: /** The name of the artifact.*/
ingo@119: protected String name;
ingo@119:
ingo@121: /** The data that have been inserted into this artifact.*/
ingo@121: protected Map data;
ingo@121:
ingo@687: /** The list of facets supported by this artifact.*/
ingo@691: protected Map> facets;
ingo@687:
ingo@121:
ingo@121: /**
ingo@121: * The default constructor that creates an empty FLYSArtifact.
ingo@121: */
ingo@121: public FLYSArtifact() {
ingo@686: data = new TreeMap();
sascha@661: previousStateIds = new ArrayList();
ingo@691: facets = new HashMap>();
ingo@121: }
ingo@121:
ingo@121:
ingo@121: /**
ingo@121: * Returns the name of the concrete artifact.
ingo@121: *
ingo@121: * @return the name of the concrete artifact.
ingo@121: */
ingo@121: public abstract String getName();
ingo@121:
ingo@121:
ingo@121: /**
ingo@121: * Returns the FLYSContext from context object.
ingo@121: *
ingo@121: * @param context The CallContext or the FLYSContext.
ingo@121: *
ingo@121: * @return the FLYSContext.
ingo@121: */
sascha@706: protected static FLYSContext getFlysContext(Object context) {
ingo@121: return context instanceof FLYSContext
ingo@121: ? (FLYSContext) context
ingo@121: : (FLYSContext) ((CallContext) context).globalContext();
ingo@121: }
ingo@121:
ingo@119:
ingo@119: /**
ingo@119: * Initialize the artifact and insert new data if data
contains
ingo@119: * information necessary for this artifact.
ingo@119: *
ingo@119: * @param identifier The UUID.
ingo@119: * @param factory The factory that is used to create this artifact.
ingo@119: * @param context The CallContext.
ingo@119: * @param data Some optional data.
ingo@119: */
ingo@119: @Override
ingo@119: public void setup(
ingo@119: String identifier,
ingo@119: ArtifactFactory factory,
ingo@119: Object context,
ingo@119: Document data)
ingo@119: {
ingo@119: logger.debug("Setup this artifact with the uuid: " + identifier);
ingo@119:
ingo@119: super.setup(identifier, factory, context, data);
ingo@119:
ingo@119: FLYSContext flysContext = (FLYSContext) context;
ingo@119: StateEngine engine = (StateEngine) flysContext.get(
ingo@119: FLYSContext.STATE_ENGINE_KEY);
ingo@119:
ingo@121: String name = getName();
ingo@121:
ingo@121: logger.debug("Set initial state for artifact '" + name + "'");
ingo@119: List states = engine.getStates(name);
ingo@119:
ingo@119: setCurrentState(states.get(0));
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@119: * Insert new data included in input
into the current state.
ingo@119: *
ingo@119: * @param target XML document that contains new data.
ingo@119: * @param context The CallContext.
ingo@119: *
ingo@119: * @return a document that contains a SUCCESS or FAILURE message.
ingo@119: */
ingo@119: @Override
ingo@119: public Document feed(Document target, CallContext context) {
ingo@123: logger.info("FLYSArtifact.feed()");
ingo@123:
ingo@121: Document doc = XMLUtils.newDocument();
ingo@119:
ingo@121: XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator(
ingo@121: doc,
ingo@121: ArtifactNamespaceContext.NAMESPACE_URI,
ingo@121: ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@119:
ingo@121: Element result = creator.create("result");
ingo@121: doc.appendChild(result);
ingo@119:
ingo@121: try {
ingo@322: saveData(target, XPATH_FEED_INPUT, context);
ingo@689:
sascha@705: compute(context, ComputeType.FEED, true);
ingo@689:
ingo@123: return describe(target, context);
ingo@121: }
ingo@121: catch (IllegalArgumentException iae) {
sascha@658: // do not store state if validation fails.
sascha@658: context.afterCall(CallContext.NOTHING);
ingo@122: creator.addAttr(result, "type", OPERATION_FAILED, true);
ingo@121:
ingo@121: result.setTextContent(iae.getMessage());
ingo@121: }
ingo@121:
ingo@121: return doc;
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@122: * This method handles request for changing the current state of an
ingo@122: * artifact. It is possible to step forward or backward.
ingo@122: *
ingo@122: * @param target The incoming ADVANCE document.
ingo@122: * @param context The CallContext.
ingo@122: *
ingo@122: * @return a document that contains a SUCCESS or FAILURE message.
ingo@122: */
ingo@122: public Document advance(Document target, CallContext context) {
ingo@122: Document doc = XMLUtils.newDocument();
ingo@122:
ingo@122: XMLUtils.ElementCreator ec = new XMLUtils.ElementCreator(
ingo@122: doc,
ingo@122: ArtifactNamespaceContext.NAMESPACE_URI,
ingo@122: ArtifactNamespaceContext.NAMESPACE_PREFIX);
ingo@122:
ingo@122: Element result = ec.create("result");
ingo@122:
ingo@122: String targetState = XMLUtils.xpathString(
ingo@122: target, XPATH_ADVANCE_TARGET, ArtifactNamespaceContext.INSTANCE);
ingo@122:
ingo@140: logger.info("FLYSArtifact.advance() to '" + targetState + "'");
ingo@140:
ingo@122: if (isStateReachable(targetState, context)) {
ingo@140: logger.info("Advance: Step forward");
ingo@122:
sascha@661: List prev = getPreviousStateIds();
ingo@122: prev.add(getCurrentStateId());
ingo@122:
ingo@122: setCurrentStateId(targetState);
ingo@122:
ingo@693: logger.debug("Compute data for state: " + targetState);
sascha@705: compute(context, ComputeType.ADVANCE, true);
ingo@693:
ingo@123: return describe(target, context);
ingo@122: }
ingo@140: else if (isPreviousState(targetState, context)) {
ingo@140: logger.info("Advance: Step back to");
ingo@122:
sascha@661: List prevs = getPreviousStateIds();
ingo@140: int targetIdx = prevs.indexOf(targetState);
ingo@140: int start = prevs.size() - 1;
ingo@140:
ingo@140: for (int i = start; i >= targetIdx; i--) {
ingo@140: String prev = prevs.get(i);
ingo@140: logger.debug("Remove state id '" + prev + "'");
ingo@140: prevs.remove(prev);
ingo@691: facets.remove(prev);
ingo@140: }
ingo@140:
ingo@140: setCurrentStateId(targetState);
ingo@140:
ingo@140: return describe(target, context);
ingo@140: }
ingo@122:
ingo@122: logger.warn("Advance: Cannot advance to '" + targetState + "'");
ingo@123: ec.addAttr(result, "type", OPERATION_FAILED, true);
ingo@122:
ingo@123: doc.appendChild(result);
ingo@122:
ingo@122: return doc;
ingo@122: }
ingo@122:
ingo@122:
ingo@122: /**
ingo@119: * Returns the identifier of the current state.
ingo@119: *
ingo@119: * @return the identifier of the current state.
ingo@119: */
ingo@119: protected String getCurrentStateId() {
ingo@119: return currentStateId;
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@119: * Sets the identifier of the current state.
ingo@119: *
ingo@119: * @param id the identifier of a state.
ingo@119: */
ingo@119: protected void setCurrentStateId(String id) {
ingo@119: currentStateId = id;
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@119: * Set the current state of this artifact. NOTEWe don't store the
ingo@119: * State object itself - which is not necessary - but its identifier. So
ingo@119: * this method will just call the setCurrentStateId() method with the
ingo@119: * identifier of state.
ingo@119: *
ingo@119: * @param state The new current state.
ingo@119: */
ingo@119: protected void setCurrentState(State state) {
ingo@119: setCurrentStateId(state.getID());
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@119: * Returns the current state of the artifact.
ingo@119: *
ingo@119: * @return the current State of the artifact.
ingo@119: */
ingo@119: protected State getCurrentState(Object context) {
ingo@121: FLYSContext flysContext = getFlysContext(context);
ingo@119: StateEngine engine = (StateEngine) flysContext.get(
ingo@119: FLYSContext.STATE_ENGINE_KEY);
ingo@119:
ingo@119: return engine.getState(getCurrentStateId());
ingo@119: }
ingo@119:
ingo@119:
ingo@119: /**
ingo@122: * Returns the vector of previous state identifiers.
ingo@122: *
ingo@122: * @return the vector of previous state identifiers.
ingo@122: */
sascha@661: protected List getPreviousStateIds() {
ingo@122: return previousStateIds;
ingo@122: }
ingo@122:
ingo@122:
ingo@122: /**
ingo@121: * Adds a new StateData item to the data pool of this artifact.
ingo@121: *
ingo@121: * @param name the name of the data object.
ingo@121: * @param data the data object itself.
ingo@121: */
ingo@121: protected void addData(String name, StateData data) {
ingo@121: this.data.put(name, data);
ingo@121: }
ingo@121:
ingo@121:
ingo@121: /**
ingo@122: * This method returns a specific StateData object that is stored in the
ingo@122: * data pool of this artifact.
ingo@122: *
ingo@122: * @param name The name of the data object.
ingo@122: *
ingo@122: * @return the StateData object if existing, otherwise null.
ingo@122: */
ingo@298: public StateData getData(String name) {
ingo@122: return data.get(name);
ingo@122: }
ingo@122:
ingo@122:
ingo@696: public Facet getNativeFacet(Facet facet) {
ingo@696: String name = facet.getName();
ingo@696: int index = facet.getIndex();
ingo@696:
ingo@696: for (Map.Entry> entry: facets.entrySet()) {
ingo@696: for (Facet f: entry.getValue()) {
sascha@699: if (f.getIndex() == index && f.getName().equals(name)) {
ingo@696: return f;
ingo@696: }
ingo@696: }
ingo@696: }
ingo@696:
ingo@696: logger.warn("Could not find facet: " + name + " at " + index);
ingo@696: return null;
ingo@696: }
ingo@696:
ingo@696:
ingo@122: /**
ingo@121: * This method stores the data that is contained in the FEED document.
ingo@119: *
ingo@119: * @param feed The FEED document.
ingo@119: * @param xpath The XPath that points to the data nodes.
ingo@119: */
ingo@322: public void saveData(Document feed, String xpath, CallContext context)
ingo@121: throws IllegalArgumentException
ingo@121: {
ingo@121: if (feed == null || xpath == null || xpath.length() == 0) {
ingo@325: throw new IllegalArgumentException("error_feed_no_data");
ingo@121: }
ingo@119:
ingo@121: NodeList nodes = (NodeList) XMLUtils.xpath(
ingo@121: feed,
ingo@121: xpath,
ingo@121: XPathConstants.NODESET,
ingo@121: ArtifactNamespaceContext.INSTANCE);
ingo@121:
ingo@121: if (nodes == null || nodes.getLength() == 0) {
ingo@325: throw new IllegalArgumentException("error_feed_no_data");
ingo@121: }
ingo@121:
ingo@121: int count = nodes.getLength();
ingo@121: logger.debug("Try to save " + count + " data items.");
ingo@121:
ingo@121: for (int i = 0; i < count; i++) {
ingo@121: Node node = nodes.item(i);
ingo@121:
ingo@121: String name = XMLUtils.xpathString(
ingo@121: node, "@art:name", ArtifactNamespaceContext.INSTANCE);
ingo@121: String value = XMLUtils.xpathString(
ingo@121: node, "@art:value", ArtifactNamespaceContext.INSTANCE);
ingo@121:
ingo@121: if (name != null && value != null) {
ingo@121: logger.debug("Save data item for '" + name + "' : " + value);
ingo@121:
ingo@121: addData(name, new DefaultStateData(name, null, null, value));
ingo@121: }
ingo@121: }
ingo@322:
ingo@624: DefaultState current = (DefaultState) getCurrentState(context);
ingo@624: current.validate(this, context);
ingo@122: }
ingo@122:
ingo@122:
ingo@122: /**
ingo@122: * Determines if the state with the identifier stateId is reachable
ingo@122: * from the current state. The determination itself takes place in the
ingo@122: * TransitionEngine.
ingo@122: *
ingo@122: * @param stateId The identifier of a state.
ingo@122: * @param context The context object.
ingo@122: *
ingo@122: * @return true, if the state specified by stateId is reacahble,
ingo@122: * otherwise false.
ingo@122: */
ingo@122: protected boolean isStateReachable(String stateId, Object context) {
ingo@122: logger.debug("Determine if the state '" + stateId + "' is reachable.");
ingo@122:
ingo@122: FLYSContext flysContext = getFlysContext(context);
ingo@122:
ingo@624: State currentState = getCurrentState(context);
ingo@122: StateEngine sEngine = (StateEngine) flysContext.get(
ingo@122: FLYSContext.STATE_ENGINE_KEY);
ingo@122:
ingo@122: TransitionEngine tEngine = (TransitionEngine) flysContext.get(
ingo@122: FLYSContext.TRANSITION_ENGINE_KEY);
ingo@122:
ingo@355: return tEngine.isStateReachable(this, stateId, currentState, sEngine);
ingo@122: }
ingo@140:
ingo@140:
ingo@140: /**
ingo@140: * Determines if the state with the identifier stateId is a previous
ingo@140: * state of the current state.
ingo@140: *
ingo@140: * @param stateId The target state identifier.
ingo@140: * @param context The context object.
ingo@140: */
ingo@140: protected boolean isPreviousState(String stateId, Object context) {
ingo@140: logger.debug("Determine if the state '" + stateId + "' is old.");
ingo@140:
sascha@661: List prevs = getPreviousStateIds();
ingo@140: if (prevs.contains(stateId)) {
ingo@140: return true;
ingo@140: }
ingo@140:
ingo@140: return false;
ingo@140: }
ingo@317:
ingo@317:
ingo@317: /**
ingo@317: * Returns the selected River object based on the 'river' data that might
ingo@317: * have been inserted by the user.
ingo@317: *
ingo@317: * @return the selected River or null if no river has been chosen yet.
ingo@317: */
ingo@317: public River getRiver() {
ingo@317: StateData dRiver = getData("river");
ingo@317:
ingo@317: return dRiver != null
ingo@317: ? RiverFactory.getRiver((String) dRiver.getValue())
ingo@317: : null;
ingo@317: }
ingo@317:
ingo@317:
ingo@317: /**
ingo@317: * Returns the selected distance of points.
ingo@317: *
ingo@317: * @return the selected distance or points.
ingo@317: */
ingo@317: public double[] getDistance() {
ingo@383: StateData dFrom = getData("ld_from");
ingo@383: StateData dTo = getData("ld_to");
ingo@383: StateData dLocations = getData("ld_locations");
ingo@317:
ingo@383: if (dFrom != null && dTo != null) {
ingo@383: return getDistanceByRange(dFrom, dTo);
ingo@383: }
ingo@383: else if (dLocations != null) {
ingo@383: double[] locations = getLocations();
ingo@383: return new double[] { locations[0], locations[locations.length-1] };
ingo@383: }
ingo@383:
ingo@383: logger.warn("No data found for distance determination!");
ingo@383:
ingo@383: return null;
ingo@383: }
ingo@383:
ingo@383:
ingo@383: /**
ingo@627: * Determines the selected mode of distance/range input.
ingo@627: *
ingo@627: * @return true, if the range mode is selected otherwise false.
ingo@627: */
ingo@627: public boolean isRange() {
ingo@627: StateData mode = getData("ld_mode");
ingo@627:
ingo@627: if (mode == null) {
ingo@627: logger.warn("No mode location/range chosen. Defaults to range.");
ingo@627: return true;
ingo@627: }
ingo@627:
ingo@627: String value = (String) mode.getValue();
ingo@627:
ingo@628: return value.equals("distance");
ingo@627: }
ingo@627:
ingo@627:
ingo@627: /**
ingo@402: * This method returns the given distance
ingo@402: *
ingo@402: * @return an array with lower and upper kilometer range for each
ingo@402: * intersected gauge.
ingo@402: */
ingo@402: public double[][] getSplittedDistance() {
ingo@402: double[] dist = getDistance();
ingo@402: List gauges = getGauges();
ingo@402:
ingo@402: int num = gauges != null ? gauges.size() : 0;
ingo@402:
ingo@402: double[][] res = new double[num][2];
ingo@402:
ingo@402: for (int i = 0; i < num; i++) {
ingo@402: Range range = gauges.get(i).getRange();
ingo@402:
ingo@402: double lower = range.getA().doubleValue();
ingo@402: double upper = range.getB().doubleValue();
ingo@402:
ingo@402: res[i][0] = dist[0] < lower ? lower : dist[0];
ingo@402: res[i][1] = dist[1] > upper ? upper : dist[1];
ingo@402: }
ingo@402:
ingo@402: return res;
ingo@402: }
ingo@402:
ingo@402:
ingo@402: /**
ingo@383: * Returns the selected locations based on a given array of locations.
ingo@383: *
ingo@383: * @param locations The StateData that contains the locations.
ingo@383: *
ingo@383: * @return the selected locations.
ingo@383: */
ingo@383: public double[] getLocations() {
ingo@383: StateData dLocations = getData("ld_locations");
ingo@383: String locationStr = dLocations != null
ingo@383: ? (String) dLocations.getValue()
ingo@383: : "";
ingo@383:
ingo@383: if (locationStr == null || locationStr.length() == 0) {
ingo@383: logger.warn("No valid location string found!");
ingo@383: return null;
ingo@383: }
ingo@383:
ingo@383: String[] tmp = locationStr.split(" ");
ingo@383: TDoubleArrayList locations = new TDoubleArrayList();
ingo@383:
ingo@383: for (String l: tmp) {
ingo@383: try {
ingo@383: locations.add(Double.parseDouble(l));
ingo@383: }
ingo@383: catch (NumberFormatException nfe) {
ingo@383: logger.warn(nfe, nfe);
ingo@383: }
ingo@383: }
ingo@383:
ingo@383: locations.sort();
ingo@383:
ingo@383: return locations.toNativeArray();
ingo@383: }
ingo@383:
ingo@383:
ingo@383: /**
ingo@383: * Returns the selected distance based on a given range (from, to).
ingo@383: *
ingo@383: * @param dFrom The StateData that contains the lower value.
ingo@383: * @param dTo The StateData that contains the upper value.
ingo@383: *
ingo@383: * @return the selected distance.
ingo@383: */
ingo@383: protected double[] getDistanceByRange(StateData dFrom, StateData dTo) {
ingo@317: double from = Double.parseDouble((String) dFrom.getValue());
ingo@317: double to = Double.parseDouble((String) dTo.getValue());
ingo@317:
ingo@317: return new double[] { from, to };
ingo@317: }
ingo@317:
ingo@317:
ingo@317: /**
ingo@362: * Returns the selected Kms.
ingo@362: *
ingo@402: * @param distance An 2dim array with [lower, upper] values.
ingo@402: *
ingo@362: * @return the selected Kms.
ingo@362: */
ingo@402: public double[] getKms(double[] distance) {
ingo@362: StateData dStep = getData("ld_step");
ingo@362:
ingo@362: if (dStep == null) {
ingo@362: logger.warn("No step width given. Cannot compute Kms.");
ingo@362: return null;
ingo@362: }
ingo@362:
ingo@402: double step = Double.parseDouble((String) dStep.getValue());
ingo@362:
ingo@362: // transform step from 'm' into 'km'
ingo@362: step = step / 1000;
ingo@362:
ingo@398: if (step == 0d) {
ingo@398: step = DEFAULT_KM_STEPS;
ingo@398: }
ingo@398:
ingo@362: return getExplodedValues(distance[0], distance[1], step);
ingo@362: }
ingo@362:
ingo@362:
ingo@362: /**
ingo@402: * Returns the selected Kms.
ingo@402: *
ingo@402: * @return the selected kms.
ingo@402: */
ingo@402: public double[] getKms() {
ingo@628: if (isRange()) {
ingo@628: double[] distance = getDistance();
ingo@628: return getKms(distance);
ingo@628:
ingo@628: }
ingo@628: else {
ingo@628: return LocationDistanceSelect.getLocations(this);
ingo@628: }
ingo@402: }
ingo@402:
sascha@655: public double [] getFromToStep() {
sascha@655: if (!isRange()) {
sascha@655: return null;
sascha@655: }
sascha@655: double [] fromTo = getDistance();
sascha@655:
sascha@655: if (fromTo == null) {
sascha@655: return null;
sascha@655: }
sascha@655:
sascha@655: StateData dStep = getData("ld_step");
sascha@655: if (dStep == null) {
sascha@655: return null;
sascha@655: }
sascha@655:
sascha@655: double [] result = new double[3];
sascha@655: result[0] = fromTo[0];
sascha@655: result[1] = fromTo[1];
sascha@655:
sascha@655: try {
sascha@655: String step = (String)dStep.getValue();
sascha@655: result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d);
sascha@655: }
sascha@655: catch (NumberFormatException nfe) {
sascha@655: return null;
sascha@655: }
sascha@655:
sascha@655: return result;
sascha@655: }
sascha@655:
ingo@402:
ingo@402: /**
ingo@317: * Returns the gauge based on the current distance and river.
ingo@317: *
ingo@317: * @return the gauge.
ingo@317: */
ingo@317: public Gauge getGauge() {
ingo@317: River river = getRiver();
ingo@317: double[] dist = getDistance();
ingo@317:
ingo@317: if (logger.isDebugEnabled()) {
ingo@317: logger.debug("Determine gauge for:");
ingo@317: logger.debug("... river: " + river.getName());
ingo@317: logger.debug("... distance: " + dist[0] + " - " + dist[1]);
ingo@317: }
ingo@317:
ingo@320: Gauge gauge = river.determineGauge(dist[0], dist[1]);
ingo@317:
ingo@320: String name = gauge != null ? gauge.getName() : "'n/a";
ingo@320: logger.debug("Found gauge: " + name);
ingo@320:
ingo@320: return gauge;
ingo@317: }
ingo@362:
ingo@362:
ingo@362: /**
ingo@399: * Returns the gauges that match the selected kilometer range.
ingo@399: *
ingo@399: * @return the gauges based on the selected kilometer range.
ingo@399: */
ingo@399: public List getGauges() {
sascha@698:
sascha@698: River river = getRiver();
sascha@698: if (river == null) {
sascha@698: return null;
sascha@698: }
sascha@698:
sascha@698: double [] dist = getDistance();
sascha@698: if (dist == null) {
sascha@698: return null;
sascha@698: }
ingo@399:
ingo@399: return river.determineGauges(dist[0], dist[1]);
ingo@399: }
ingo@399:
ingo@399:
ingo@399: /**
ingo@362: * This method returns the Q values.
ingo@362: *
ingo@362: * @return the selected Q values or null, if no Q values are selected.
ingo@362: */
ingo@362: public double[] getQs() {
ingo@631: StateData dMode = getData("wq_mode");
ingo@631: StateData dSelection = getData("wq_selection");
ingo@377:
ingo@377: String mode = dMode != null ? (String) dMode.getValue() : "";
ingo@631: String sel = dSelection != null ? (String)dSelection.getValue() : null;
ingo@377:
ingo@377: if (mode.equals("Q")) {
ingo@631: if (sel != null && sel.equals("single")) {
ingo@379: return getSingleWQValues();
ingo@379: }
ingo@379: else {
ingo@379: return getWQTriple();
ingo@379: }
ingo@377: }
ingo@377: else {
ingo@377: logger.warn("You try to get Qs, but W has been inserted.");
ingo@377: return null;
ingo@377: }
ingo@377: }
ingo@377:
ingo@685:
sascha@455: public boolean isQ() {
sascha@455: StateData mode = getData("wq_mode");
sascha@455: return mode != null && mode.getValue().equals("Q");
sascha@455: }
sascha@455:
ingo@377:
ingo@377: /**
ingo@685: * Returns true, if the parameter is set to compute data on a free range.
ingo@685: * Otherwise it returns false, which tells the calculation that it is bound
ingo@685: * to a gauge.
ingo@685: *
ingo@685: * @return true, if the calculation should compute on a free range otherwise
ingo@685: * false and the calculation is bound to a gauge.
ingo@685: */
ingo@685: public boolean isFreeQ() {
ingo@685: StateData mode = getData("wq_free");
ingo@685: String value = mode != null ? (String) mode.getValue() : null;
ingo@685:
ingo@685: if (mode == null) {
ingo@685: return false;
ingo@685: }
ingo@685:
ingo@685: return Boolean.valueOf(value);
ingo@685: }
ingo@685:
ingo@685:
ingo@685: /**
ingo@402: * Returns the Q values based on a specified kilometer range.
ingo@402: *
ingo@402: * @param range A 2dim array with lower and upper kilometer range.
ingo@402: *
ingo@402: * @return an array of Q values.
ingo@402: */
ingo@402: public double[] getQs(double[] range) {
ingo@402: StateData dMode = getData("wq_mode");
ingo@402: StateData dValues = getData("wq_values");
ingo@402:
ingo@402: String mode = dMode != null ? (String) dMode.getValue() : "";
ingo@402:
ingo@402: if (mode.equals("Q")) {
ingo@402: return getWQForDist(range);
ingo@402: }
ingo@402:
ingo@402: logger.warn("You try to get Qs, but Ws has been inserted.");
ingo@402: return null;
ingo@402: }
ingo@402:
ingo@402:
ingo@402: /**
ingo@402: * Returns the W values based on a specified kilometer range.
ingo@402: *
ingo@402: * @param range A 2dim array with lower and upper kilometer range.
ingo@402: *
ingo@402: * @return an array of W values.
ingo@402: */
ingo@402: public double[] getWs(double[] range) {
ingo@402: StateData dMode = getData("wq_mode");
ingo@402: StateData dValues = getData("wq_values");
ingo@402:
ingo@402: String mode = dMode != null ? (String) dMode.getValue() : "";
ingo@402:
ingo@402: if (mode.equals("W")) {
ingo@402: return getWQForDist(range);
ingo@402: }
ingo@402:
ingo@402: logger.warn("You try to get Ws, but Qs has been inserted.");
ingo@402: return null;
ingo@402: }
ingo@402:
ingo@402:
ingo@402: /**
ingo@377: * This method returns the W values.
ingo@377: *
ingo@377: * @return the selected W values or null, if no W values are selected.
ingo@377: */
ingo@377: public double[] getWs() {
ingo@379: StateData dMode = getData("wq_mode");
ingo@379: StateData dSingle = getData("wq_single");
ingo@377:
ingo@377: String mode = dMode != null ? (String) dMode.getValue() : "";
ingo@377:
ingo@377: if (mode.equals("W")) {
ingo@379: if (dSingle != null) {
ingo@379: return getSingleWQValues();
ingo@379: }
ingo@379: else {
ingo@379: return getWQTriple();
ingo@379: }
ingo@377: }
ingo@377: else {
ingo@377: logger.warn("You try to get Qs, but W has been inserted.");
ingo@377: return null;
ingo@377: }
ingo@377: }
ingo@377:
sascha@650: public List getSegments() {
sascha@650: StateData wqValues = getData("wq_values");
sascha@650: if (wqValues == null) {
sascha@650: logger.warn("no wq_values given");
sascha@650: return Collections.emptyList();
sascha@650: }
sascha@650: String input = (String)wqValues.getValue();
sascha@650: if (input == null || (input = input.trim()).length() == 0) {
sascha@650: logger.warn("wq_values are empty");
sascha@650: return Collections.emptyList();
sascha@650: }
sascha@650: return Segment.parseSegments(input);
sascha@650: }
sascha@650:
ingo@377:
ingo@377: /**
ingo@377: * Returns the Qs for a number of Ws. This method makes use of
ingo@377: * DischargeTables.getQForW().
ingo@377: *
ingo@377: * @param ws An array of W values.
ingo@377: *
ingo@377: * @return an array of Q values.
ingo@377: */
ingo@377: public double[] getQsForWs(double[] ws) {
ingo@377: logger.debug("FLYSArtifact.getQsForWs");
ingo@377:
ingo@377: River r = getRiver();
ingo@377: Gauge g = getGauge();
ingo@377:
ingo@377: DischargeTables dt = new DischargeTables(r.getName(), g.getName());
ingo@377: Map tmp = dt.getValues();
ingo@377:
ingo@377: double[][] values = tmp.get(g.getName());
ingo@377: double[] qs = new double[ws.length];
ingo@377:
ingo@377: for (int i = 0; i < ws.length; i++) {
ingo@377: qs[i] = dt.getQForW(values, ws[i]);
ingo@377: }
ingo@377:
ingo@377: return qs;
ingo@377: }
ingo@377:
sascha@637: protected double [][] getRanges() {
sascha@637: logger.debug("getRanges");
sascha@637:
sascha@637: StateData data = getData("wq_values");
sascha@637:
sascha@637: if (data == null) {
sascha@637: logger.warn("Missing wq values!");
sascha@637: return new double [0][0];
sascha@637: }
sascha@637:
sascha@637: String dataString = (String)data.getValue();
sascha@637: String [] ranges = dataString.split(":");
sascha@637:
sascha@637: ArrayList rs = new ArrayList();
sascha@637:
sascha@637: for (String range: ranges) {
sascha@637: String [] parts = range.split(";");
sascha@637:
sascha@637: if (parts.length < 2) {
sascha@637: logger.warn("invalid number of parts in range line");
sascha@637: continue;
sascha@637: }
sascha@637: try {
sascha@637: double from = Double.parseDouble(parts[0]);
sascha@637: double to = Double.parseDouble(parts[1]);
sascha@637: rs.add(new double [] { from, to });
sascha@637: }
sascha@637: catch (NumberFormatException nfe) {
sascha@637: logger.warn("invalid double values in range line");
sascha@637: }
sascha@637: }
sascha@637:
sascha@637: return rs.toArray(new double [rs.size()][]);
sascha@637: }
sascha@637:
ingo@377:
ingo@377: /**
ingo@402: * This method returns the given W or Q values for a specific range
ingo@402: * (inserted in the WQ input panel for discharge longitudinal sections).
ingo@402: *
ingo@402: * @param dist A 2dim array with lower und upper kilometer values.
ingo@402: *
ingo@402: * @return an array of W or Q values.
ingo@402: */
ingo@402: protected double[] getWQForDist(double[] dist) {
ingo@402: logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]);
ingo@402: StateData data = getData("wq_values");
ingo@402:
ingo@402: if (data == null) {
ingo@402: logger.warn("Missing wq values!");
ingo@402: return null;
ingo@402: }
ingo@402:
ingo@402: String dataString = (String) data.getValue();
ingo@402: String[] ranges = dataString.split(":");
ingo@402:
ingo@402: for (String range: ranges) {
ingo@402: String[] parts = range.split(";");
ingo@402:
ingo@402: double lower = Double.parseDouble(parts[0]);
ingo@402: double upper = Double.parseDouble(parts[1]);
ingo@402:
ingo@402: if (lower <= dist[0] && upper >= dist[1]) {
ingo@402: String[] values = parts[2].split(",");
ingo@402:
ingo@402: int num = values.length;
ingo@402: double[] res = new double[num];
ingo@402:
ingo@402: for (int i = 0; i < num; i++) {
ingo@402: try {
ingo@402: res[i] = Double.parseDouble(values[i]);
ingo@402: }
ingo@402: catch (NumberFormatException nfe) {
ingo@402: logger.warn(nfe, nfe);
ingo@402: }
ingo@402: }
ingo@402:
ingo@402: return res;
ingo@402: }
ingo@402: }
ingo@402:
ingo@402: logger.warn("Specified range for WQ not found!");
ingo@402:
ingo@402: return null;
ingo@402: }
ingo@402:
ingo@402:
ingo@402: /**
ingo@377: * This method returns an array of inserted WQ triples that consist of from,
ingo@377: * to and the step width.
ingo@377: *
ingo@377: * @return an array of from, to and step width.
ingo@377: */
ingo@377: protected double[] getWQTriple() {
ingo@362: StateData dFrom = getData("wq_from");
ingo@362: StateData dTo = getData("wq_to");
ingo@362:
ingo@362: if (dFrom == null || dTo == null) {
ingo@379: logger.warn("Missing start or end value for range.");
ingo@362: return null;
ingo@362: }
ingo@362:
ingo@362: double from = Double.parseDouble((String) dFrom.getValue());
ingo@362: double to = Double.parseDouble((String) dTo.getValue());
ingo@362:
ingo@362: StateData dStep = getData("wq_step");
ingo@362:
ingo@362: if (dStep == null) {
ingo@362: logger.warn("No step width given. Cannot compute Qs.");
ingo@362: return null;
ingo@362: }
ingo@362:
ingo@362: double step = Double.parseDouble((String) dStep.getValue());
ingo@362:
ingo@362: // if no width is given, the DEFAULT_Q_STEPS is used to compute the step
ingo@362: // width. Maybe, we should round the value to a number of digits.
ingo@362: if (step == 0d) {
ingo@362: double diff = to - from;
ingo@362: step = diff / DEFAULT_Q_STEPS;
ingo@362: }
ingo@362:
ingo@362: return getExplodedValues(from, to, step);
ingo@362: }
ingo@362:
ingo@362:
ingo@362: /**
ingo@379: * Returns an array of inserted WQ double values stored as whitespace
ingo@379: * separated list.
ingo@379: *
ingo@379: * @return an array of W or Q values.
ingo@379: */
ingo@379: protected double[] getSingleWQValues() {
ingo@379: StateData dSingle = getData("wq_single");
ingo@379:
ingo@379: if (dSingle == null) {
ingo@379: logger.warn("Cannot determine single WQ values. No data given.");
ingo@379: return null;
ingo@379: }
ingo@379:
ingo@379: String tmp = (String) dSingle.getValue();
ingo@379: String[] strValues = tmp.split(" ");
ingo@379:
ingo@379: TDoubleArrayList values = new TDoubleArrayList();
ingo@379:
ingo@379: for (String strValue: strValues) {
ingo@379: try {
ingo@379: values.add(Double.parseDouble(strValue));
ingo@379: }
ingo@379: catch (NumberFormatException nfe) {
ingo@379: logger.warn(nfe, nfe);
ingo@379: }
ingo@379: }
ingo@379:
ingo@379: values.sort();
ingo@379:
ingo@379: return values.toNativeArray();
ingo@379: }
ingo@379:
ingo@379:
ingo@379: /**
ingo@362: * Returns an array of double values. The values contained in this array
ingo@362: * begin with the value from and end with the value to. The
ingo@362: * number of values in the result array depends on the step width.
ingo@362: *
ingo@362: * @param from The lower value.
ingo@362: * @param to The upper value.
ingo@362: * @param step The step width between two values in the result array.
ingo@362: *
ingo@362: * @return an array of double values.
ingo@362: */
sascha@634: public static double[] getExplodedValues(
sascha@634: double from,
sascha@634: double to,
sascha@634: double step
sascha@634: ) {
sascha@655: return DoubleUtil.explode(from, to, step);
sascha@634: }
sascha@634:
ingo@686: /**
ingo@686: * Computes the hash code of the entered values.
ingo@686: *
ingo@686: * @return a hash code.
ingo@686: */
ingo@686: @Override
ingo@686: public String hash() {
ingo@686: Set> entries = data.entrySet();
ingo@686:
sascha@705: long hash = 0L;
sascha@705: int shift = 3;
ingo@686:
ingo@686: for (Map.Entry entry: entries) {
ingo@686: String key = entry.getKey();
ingo@686: Object value = entry.getValue().getValue();
ingo@686:
sascha@705: hash ^= ((long)key.hashCode() << shift)
sascha@705: | ((long)value.hashCode() << (shift + shift));
ingo@686: shift += 2;
ingo@686: }
ingo@686:
ingo@686: return getCurrentStateId() + hash;
ingo@686: }
ingo@686:
ingo@686:
ingo@689: /**
ingo@689: * Dispatches the computation request to compute(CallContext context, String
ingo@689: * hash) with the current hash value of the artifact which is provided by
ingo@689: * hash().
ingo@689: *
ingo@689: * @param context The CallContext.
ingo@689: */
sascha@705: public Object compute(
sascha@705: CallContext context,
sascha@705: ComputeType type,
sascha@705: boolean generateFacets
sascha@705: ) {
sascha@705: return compute(context, hash(), type, generateFacets);
ingo@689: }
ingo@689:
ingo@689:
ingo@689: /**
ingo@689: * Dispatches computation requests to the current state which needs to
ingo@689: * implement a createComputeCallback(String hash, FLYSArtifact artifact)
ingo@689: * method.
ingo@689: *
ingo@689: * @param context The CallContext.
ingo@689: * @param hash The hash value which is used to fetch computed data from
ingo@689: * cache.
ingo@689: *
ingo@689: * @return the computed data.
ingo@689: */
sascha@705: public Object compute(
sascha@705: CallContext context,
sascha@705: String hash,
sascha@705: ComputeType type,
sascha@705: boolean generateFacets
sascha@705: ) {
ingo@693: DefaultState current = (DefaultState) getCurrentState(context);
ingo@689:
ingo@693: logger.debug("Create ComputeCallback for state: " + current.getID());
ingo@689:
sascha@705: return compute(context, hash, current, type, generateFacets);
ingo@689: }
ingo@689:
ingo@689:
ingo@690: public Object compute(
sascha@697: CallContext context,
sascha@697: String key,
sascha@697: DefaultState state,
sascha@705: ComputeType type,
sascha@705: boolean generateFacets
sascha@697: ) {
sascha@697: String stateID = state.getID();
ingo@691:
sascha@705: List fs = generateFacets ? new ArrayList() : null;
sascha@697:
sascha@697: try {
sascha@697: Cache cache = CacheFactory.getCache(COMPUTING_CACHE);
sascha@697:
sascha@697: Object old = null;
sascha@697:
sascha@697: if (cache != null) {
sascha@697: net.sf.ehcache.Element element = cache.get(key);
sascha@697: if (element != null) {
sascha@697: logger.debug("Got computation result from cache.");
sascha@697: old = element.getValue();
sascha@697: }
sascha@697: }
sascha@697:
sascha@697: Object res;
ingo@690: switch (type) {
ingo@690: case FEED:
sascha@697: res = state.computeFeed(this, key, context, fs, old);
sascha@697: break;
sascha@697: case ADVANCE:
sascha@700: res = state.computeAdvance(this, key, context, fs, old);
sascha@697: break;
sascha@697: default:
sascha@697: res = null;
sascha@697: }
ingo@690:
sascha@697: if (cache != null && old != res && res != null) {
sascha@697: logger.debug("Store computation result to cache.");
sascha@697: net.sf.ehcache.Element element =
sascha@697: new net.sf.ehcache.Element(key, res);
sascha@697: cache.put(element);
sascha@697: }
sascha@697:
sascha@697: return res;
sascha@697: }
sascha@697: finally {
sascha@705: if (generateFacets) {
sascha@705: if (fs.isEmpty()) {
sascha@705: facets.remove(stateID);
sascha@705: }
sascha@705: else {
sascha@705: facets.put(stateID, fs);
sascha@705: }
ingo@690: }
ingo@686: }
ingo@686: }
ingo@686:
ingo@686:
ingo@623: /**
ingo@623: * Method to dump the artifacts state/data.
ingo@623: */
ingo@623: protected void dumpArtifact() {
ingo@623: if (logger.isDebugEnabled()) {
ingo@623: logger.debug("++++++++++++++ DUMP ARTIFACT DATA +++++++++++++++++");
ingo@623:
ingo@623: logger.debug("------ DUMP DATA ------");
ingo@623: Collection allData = data.values();
ingo@623:
ingo@623: for (StateData d: allData) {
ingo@623: String name = d.getName();
ingo@623: String value = (String) d.getValue();
ingo@623:
ingo@623: logger.debug("- " + name + ": " + value);
ingo@623: }
ingo@623:
ingo@623: logger.debug("------ DUMP PREVIOUS STATES ------");
sascha@661: List stateIds = getPreviousStateIds();
ingo@623:
ingo@623: for (String id: stateIds) {
ingo@623: logger.debug("- State: " + id);
ingo@623: }
ingo@623:
ingo@623: logger.debug("CURRENT STATE: " + getCurrentStateId());
ingo@623:
ingo@623: logger.debug("++++++++++++++ END ARTIFACT DUMP +++++++++++++++++");
ingo@623: }
ingo@623: }
ingo@119: }
ingo@119: // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :