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@686: import de.intevation.flys.artifacts.model.ComputeCallback; ingo@690: import de.intevation.flys.artifacts.model.ComputeCallback.ComputeType; 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; 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: */ ingo@121: protected 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: ingo@690: compute(context, ComputeType.FEED); 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); ingo@693: compute(context, ComputeType.ADVANCE); 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()) { ingo@696: if (f.getName().equals(name) && f.getIndex() == index) { 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() { ingo@399: River river = getRiver(); ingo@399: double[] dist = getDistance(); 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: 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@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: /** 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: ingo@686: int hash = 0; ingo@686: 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: ingo@686: hash ^= (key.hashCode() << shift) | (value.hashCode() << 2 * 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: */ ingo@690: public Object compute(CallContext context, ComputeType type) { ingo@690: return compute(context, hash(), type); 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: */ ingo@690: public Object compute(CallContext context, String hash, ComputeType type) { ingo@693: DefaultState current = (DefaultState) getCurrentState(context); ingo@689: ingo@693: logger.debug("Create ComputeCallback for state: " + current.getID()); ingo@689: ingo@689: ComputeCallback callback = current.createComputeCallback(hash(), this); ingo@689: ingo@689: if (callback == null) { ingo@689: logger.info("There is no data which needs to be computed."); ingo@689: return null; ingo@689: } ingo@689: ingo@692: return compute(context, hash, callback, type); ingo@689: } ingo@689: ingo@689: ingo@690: public Object compute( ingo@692: CallContext context, ingo@690: String key, ingo@690: ComputeCallback callback, ingo@690: ComputeType type) ingo@690: { ingo@691: String stateId = getCurrentStateId(); ingo@691: List fs = facets.get(stateId); ingo@691: if (fs == null) { ingo@691: fs = new ArrayList(); ingo@691: facets.put(stateId, fs); ingo@691: } ingo@691: ingo@686: Cache cache = CacheFactory.getCache(COMPUTING_CACHE); ingo@686: if (cache == null) { ingo@690: switch (type) { ingo@690: case FEED: ingo@692: return callback.computeFeed(context, fs); ingo@690: ingo@690: case ADVANCE: ingo@692: return callback.computeAdvance(context, fs); ingo@690: } ingo@686: } ingo@686: ingo@686: net.sf.ehcache.Element element = cache.get(key); ingo@686: if (element != null) { ingo@686: logger.debug("Got computation values from cache."); ingo@686: return element.getValue(); ingo@686: } ingo@686: ingo@690: Object result = null; ingo@690: switch (type) { ingo@690: case FEED: ingo@692: result = callback.computeFeed(context, fs); ingo@690: break; ingo@690: ingo@690: case ADVANCE: ingo@692: result = callback.computeAdvance(context, fs); ingo@690: } ingo@686: ingo@686: if (result != null) { ingo@686: cache.put(new net.sf.ehcache.Element(key, result)); ingo@686: } ingo@686: ingo@686: return result; 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 :