Mercurial > dive4elements > river
view flys-artifacts/src/main/java/de/intevation/flys/artifacts/WINFOArtifact.java @ 1087:22149b0545b9
New NamedDouble class which implements a <String,double>-pair.
flys-artifacts/trunk@2590 c6561f87-3c4e-4783-a992-168aeb5c3f6f
author | Felix Wolfsteller <felix.wolfsteller@intevation.de> |
---|---|
date | Fri, 26 Aug 2011 11:10:31 +0000 |
parents | 61c051e53f9b |
children | f465785ed1ae |
line wrap: on
line source
package de.intevation.flys.artifacts; import de.intevation.artifactdatabase.ProtocolUtils; import de.intevation.artifactdatabase.data.StateData; import de.intevation.artifactdatabase.state.Facet; import de.intevation.artifactdatabase.state.Output; import de.intevation.artifactdatabase.state.State; import de.intevation.artifactdatabase.state.StateEngine; import de.intevation.artifactdatabase.transition.TransitionEngine; import de.intevation.artifacts.CallContext; import de.intevation.artifacts.common.ArtifactNamespaceContext; import de.intevation.artifacts.common.utils.XMLUtils; import de.intevation.flys.artifacts.context.FLYSContext; import de.intevation.flys.artifacts.model.Calculation1; import de.intevation.flys.artifacts.model.Calculation2; import de.intevation.flys.artifacts.model.Calculation3; import de.intevation.flys.artifacts.model.Calculation4; import de.intevation.flys.artifacts.model.Calculation; import de.intevation.flys.artifacts.model.CalculationResult; import de.intevation.flys.artifacts.model.DischargeTables; import de.intevation.flys.artifacts.model.MainValuesFactory; import de.intevation.flys.artifacts.model.RiverFactory; import de.intevation.flys.artifacts.model.Segment; import de.intevation.flys.artifacts.model.WQKms; import de.intevation.flys.artifacts.model.WstValueTable; import de.intevation.flys.artifacts.model.WstValueTableFactory; import de.intevation.flys.artifacts.states.DefaultState; import de.intevation.flys.artifacts.states.LocationDistanceSelect; import de.intevation.flys.model.Gauge; import de.intevation.flys.model.River; import de.intevation.flys.utils.DoubleUtil; import gnu.trove.TDoubleArrayList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; import org.apache.log4j.Logger; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * The default WINFO artifact. * * @author <a href="mailto:ingo.weinzierl@intevation.de">Ingo Weinzierl</a> */ public class WINFOArtifact extends FLYSArtifact { /** The logger for this class. */ private static Logger logger = Logger.getLogger(WINFOArtifact.class); /** The name of the artifact. */ public static final String ARTIFACT_NAME = "winfo"; /** XPath */ public static final String XPATH_STATIC_UI ="/art:result/art:ui/art:static"; /** The default number of steps between the start end end of a selected Q * range.*/ public static final int DEFAULT_Q_STEPS = 30; /** The default step width between the start end end kilometer.*/ public static final double DEFAULT_KM_STEPS = 0.1; /** * The default constructor. */ public WINFOArtifact() { } /** * This method returns a description of this artifact. * * @param data Some data. * @param context The CallContext. * * @return the description of this artifact. */ public Document describe(Document data, CallContext context) { logger.debug("Describe: the current state is: " + getCurrentStateId()); if (logger.isDebugEnabled()) { dumpArtifact(); } FLYSContext flysContext = getFlysContext(context); StateEngine stateEngine = (StateEngine) flysContext.get( FLYSContext.STATE_ENGINE_KEY); TransitionEngine transitionEngine = (TransitionEngine) flysContext.get( FLYSContext.TRANSITION_ENGINE_KEY); List<State> reachable = transitionEngine.getReachableStates( this, getCurrentState(context), stateEngine); Document description = XMLUtils.newDocument(); XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator( description, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); Element root = ProtocolUtils.createRootNode(creator); description.appendChild(root); State current = getCurrentState(context); ProtocolUtils.appendDescribeHeader(creator, root, identifier(), hash()); ProtocolUtils.appendState(creator, root, current); ProtocolUtils.appendReachableStates(creator, root, reachable); Element name = ProtocolUtils.createArtNode( creator, "name", new String[] { "value" }, new String[] { getName() }); Element ui = ProtocolUtils.createArtNode( creator, "ui", null, null); Element staticUI = ProtocolUtils.createArtNode( creator, "static", null, null); Element outs = ProtocolUtils.createArtNode( creator, "outputmodes", null, null); appendOutputModes(description, outs, context, identifier()); appendStaticUI(description, staticUI, context, identifier()); Element dynamic = current.describe( this, description, root, context, identifier()); if (dynamic != null) { ui.appendChild(dynamic); } ui.appendChild(staticUI); root.appendChild(name); root.appendChild(ui); root.appendChild(outs); return description; } /** * Returns the name of the concrete artifact. * * @return the name of the concrete artifact. */ public String getName() { return ARTIFACT_NAME; } protected void appendOutputModes( Document doc, Element outs, CallContext context, String uuid) { List<String> stateIds = getPreviousStateIds(); XMLUtils.ElementCreator creator = new XMLUtils.ElementCreator( doc, ArtifactNamespaceContext.NAMESPACE_URI, ArtifactNamespaceContext.NAMESPACE_PREFIX); FLYSContext flysContext = getFlysContext(context); StateEngine engine = (StateEngine) flysContext.get( FLYSContext.STATE_ENGINE_KEY); for (String stateId: stateIds) { logger.debug("Append output modes for state: " + stateId); DefaultState state = (DefaultState) engine.getState(stateId); List<Output> list = state.getOutputs(); if (list == null || list.size() == 0) { logger.debug("-> No output modes for this state."); continue; } List<Facet> fs = facets.get(stateId); if (fs == null || fs.size() == 0) { logger.debug("No facets found."); continue; } logger.debug("Found " + fs.size() + " facets in previous states."); List<Output> generated = generateOutputs(list, fs); ProtocolUtils.appendOutputModes(doc, outs, generated); } try { DefaultState cur = (DefaultState) getCurrentState(context); if (cur.validate(this)) { List<Output> list = cur.getOutputs(); if (list != null && list.size() > 0) { logger.debug( "Append output modes for state: " + cur.getID()); List<Facet> fs = facets.get(cur.getID()); if (fs != null && fs.size() > 0) { List<Output> generated = generateOutputs(list, fs); logger.debug("Found " + fs.size() + " current facets."); if (!generated.isEmpty()) { ProtocolUtils.appendOutputModes( doc, outs, generated); } } else { logger.debug("No facets found for the current state."); } } } } catch (IllegalArgumentException iae) { // state is not valid, so we do not append its outputs. } } /** * This method appends the static data - that has already been inserted by * the user - to the static node of the DESCRIBE document. * * @param doc The document. * @param ui The root node. * @param context The CallContext. * @param uuid The identifier of the artifact. */ protected void appendStaticUI( Document doc, Node ui, CallContext context, String uuid) { List<String> stateIds = getPreviousStateIds(); FLYSContext flysContext = getFlysContext(context); StateEngine engine = (StateEngine) flysContext.get( FLYSContext.STATE_ENGINE_KEY); for (String stateId: stateIds) { logger.debug("Append static data for state: " + stateId); DefaultState state = (DefaultState) engine.getState(stateId); ui.appendChild(state.describeStatic(this, doc, ui, context, uuid)); } } // // METHODS FOR RETRIEVING COMPUTED DATA FOR DIFFERENT CHART TYPES // /** * Returns the data that is computed by a waterlevel computation. * * @return an array of data triples that consist of W, Q and Kms. */ public CalculationResult getWaterlevelData() { logger.debug("WINFOArtifact.getWaterlevelData"); River river = getRiver(); if (river == null) { return error(new WQKms[0], "No river selected."); } double[] kms = getKms(); if (kms == null) { return error(new WQKms[0], "No Kms selected."); } double[] qs = getQs(); double[] ws = null; boolean qSel = true; if (qs == null) { logger.debug("Determine Q values based on a set of W values."); qSel = false; ws = getWs(); qs = getQsForWs(ws); if (qs == null) { return error(new WQKms[0], "conversion ws to qs failed."); } } WstValueTable wst = WstValueTableFactory.getTable(river); if (wst == null) { return error(new WQKms[0], "No Wst found for selected river."); } double [] range = getDistance(); if (range == null) { return error(new WQKms[0], "No range found"); } double refKm; if (isFreeQ()) { refKm = range[0]; logger.debug("'free' calculation (km " + refKm + ")"); } else { Gauge gauge = river.determineGaugeByPosition(range[0]); if (gauge == null) { return error( new WQKms[0], "No gauge found for km " + range[0]); } refKm = gauge.getStation().doubleValue(); logger.debug( "reference gauge: " + gauge.getName() + " (km " + refKm + ")"); } return computeWaterlevelData(kms, qs, ws, wst, refKm); } /** * Computes the data of a waterlevel computation based on the interpolation * in WstValueTable. * * @param kms The kilometer values. * @param qa The discharge values. * @param wst The WstValueTable used for the interpolation. * * @return an array of data triples that consist of W, Q and Kms. */ public static CalculationResult computeWaterlevelData( double [] kms, double [] qs, double [] ws, WstValueTable wst, double refKm ) { logger.info("WINFOArtifact.computeWaterlevelData"); Calculation1 calc1 = new Calculation1(kms, qs, ws, refKm); return calc1.calculate(wst); } /** * Returns the data that is computed by a duration curve computation. * * @return the data computed by a duration curve computation. */ public CalculationResult getDurationCurveData() { logger.debug("WINFOArtifact.getDurationCurveData"); River r = getRiver(); if (r == null) { return error(null, "Cannot determine river."); } Gauge g = getGauge(); if (g == null) { return error(null, "Cannot determine gauge."); } double[] locations = getLocations(); if (locations == null) { return error(null, "Cannot determine location."); } WstValueTable wst = WstValueTableFactory.getTable(r); if (wst == null) { return error(null, "No Wst found for selected river."); } return computeDurationCurveData(g, wst, locations[0]); } /** * Computes the data used to create duration curves. * * @param gauge The selected gauge. * @param location The selected location. * * @return the computed data. */ public static CalculationResult computeDurationCurveData( Gauge gauge, WstValueTable wst, double location) { logger.info("WINFOArtifact.computeDurationCurveData"); Object[] obj = MainValuesFactory.getDurationCurveData(gauge); int[] days = (int[]) obj[0]; double[] qs = (double[]) obj[1]; Calculation3 calculation = new Calculation3(location, days, qs); return calculation.calculate(wst); } /** * Returns the data that is used to create discharge curves. * */ public CalculationResult getDischargeCurveData() { River river = getRiver(); if (river == null) { return error(new WQKms[0], "no river found"); } double [] distance = getDistance(); if (distance == null) { return error(new WQKms[0], "no range found"); } List<Gauge> gauges = river.determineGauges(distance[0], distance[1]); if (gauges.isEmpty()) { return error(new WQKms[0], "no gauges found"); } String [] names = new String[gauges.size()]; for (int i = 0; i < names.length; ++i) { names[i] = gauges.get(i).getName(); } DischargeTables dt = new DischargeTables(river.getName(), names); Map<String, double [][]> map = dt.getValues(100d); ArrayList<WQKms> res = new ArrayList<WQKms>(); for (Gauge gauge: gauges) { String name = gauge.getName(); double [][] values = map.get(name); if (values == null) { continue; } double [] kms = new double[values[0].length]; Arrays.fill(kms, gauge.getStation().doubleValue()); res.add(new WQKms(kms, values[0], values[1], name)); } return new CalculationResult( res.toArray(new WQKms[res.size()]), new Calculation()); } /** * Returns the data that is computed by a discharge curve computation. * * @return the data computed by a discharge curve computation. */ public CalculationResult getComputedDischargeCurveData() throws NullPointerException { logger.debug("WINFOArtifact.getComputedDischargeCurveData"); River r = getRiver(); if (r == null) { return error(new WQKms[0], "Cannot determine river."); } double[] locations = getLocations(); if (locations == null) { return error(new WQKms[0], "Cannot determine location."); } WstValueTable wst = WstValueTableFactory.getTable(r); if (wst == null) { return error(new WQKms[0], "No Wst found for selected river."); } return computeDischargeCurveData(wst, locations[0]); } /** * Computes the data used to create computed discharge curves. * * @param wst The WstValueTable that is used for the interpolation. * @param location The location where the computation should be based on. * * @return an object that contains tuples of W/Q values at the specified * location. */ public static CalculationResult computeDischargeCurveData( WstValueTable wst, double location) { logger.info("WINFOArtifact.computeDischargeCurveData"); Calculation2 calculation = new Calculation2(location); return calculation.calculate(wst); } protected static final CalculationResult error(Object data, String msg) { return new CalculationResult(data, new Calculation(msg)); } /** * Returns the data computed by the discharge longitudinal section * computation. * * @return an array of WQKms object - one object for each given Q value. */ public CalculationResult getDischargeLongitudinalSectionData() { logger.debug("WINFOArtifact.getDischargeLongitudinalSectionData"); River river = getRiver(); if (river == null) { logger.debug("No river selected."); return error(new WQKms[0], "No river selected."); } WstValueTable table = WstValueTableFactory.getTable(river); if (table == null) { logger.debug("No wst found for selected river."); return error(new WQKms[0], "No wst found for selected river."); } List<Segment> segments = getSegments(); if (segments == null) { logger.debug("Cannot create segments."); return error(new WQKms[0], "Cannot create segments."); } double [] range = getFromToStep(); if (range == null) { logger.debug("Cannot figure out range."); return error(new WQKms[0], "Cannot figure out range."); } Calculation4 calc4 = new Calculation4(segments, river, isQ()); return calc4.calculate(table, range[0], range[1], range[2]); } public List<Segment> getSegments() { StateData wqValues = getData("wq_values"); if (wqValues == null) { logger.warn("no wq_values given"); return Collections.emptyList(); } String input = (String)wqValues.getValue(); if (input == null || (input = input.trim()).length() == 0) { logger.warn("wq_values are empty"); return Collections.emptyList(); } return Segment.parseSegments(input); } /** * Returns the Qs for a number of Ws. This method makes use of * DischargeTables.getQForW(). * * @param ws An array of W values. * * @return an array of Q values. */ public double[] getQsForWs(double[] ws) { boolean debug = logger.isDebugEnabled(); if (debug) { logger.debug("FLYSArtifact.getQsForWs"); } River r = getRiver(); if (r == null) { logger.warn("no river found"); return null; } double [] range = getDistance(); if (range == null) { logger.warn("no ranges found"); return null; } if (debug) { logger.debug("range: " + Arrays.toString(range)); } Gauge g = r.determineGaugeByPosition(range[0]); if (g == null) { logger.warn("no gauge found for km: " + range[0]); return null; } if (debug) { logger.debug("convert w->q with gauge '" + g.getName() + "'"); } DischargeTables dt = new DischargeTables(r.getName(), g.getName()); Map<String, double [][]> tmp = dt.getValues(); double[][] values = tmp.get(g.getName()); double[] qs = new double[ws.length]; for (int i = 0; i < ws.length; i++) { qs[i] = dt.getQForW(values, ws[i]); if (debug) { logger.debug("w: " + ws[i] + " -> q: " + qs[i]); } } return qs; } /** * Returns the selected River object based on the 'river' data that might * have been inserted by the user. * * @return the selected River or null if no river has been chosen yet. */ public River getRiver() { StateData dRiver = getData("river"); return dRiver != null ? RiverFactory.getRiver((String) dRiver.getValue()) : null; } /** * Returns the selected distance of points. * * @return the selected distance or points. */ public double[] getDistance() { StateData dMode = getData("ld_mode"); StateData dFrom = getData("ld_from"); StateData dTo = getData("ld_to"); StateData dLocations = getData("ld_locations"); if (dMode != null) { String mode = (String)dMode.getValue(); if ("location".equals(mode)) { double[] locations = getLocations(); return new double[] { locations[0], locations[locations.length-1] }; } if (dFrom != null && dTo != null) { return getDistanceByRange(dFrom, dTo); } } if (dLocations != null) { double[] locations = getLocations(); return new double[] { locations[0], locations[locations.length-1] }; } if (dFrom != null && dTo != null) { return getDistanceByRange(dFrom, dTo); } logger.warn("No data found for distance determination!"); return null; } /** * Determines the selected mode of distance/range input. * * @return true, if the range mode is selected otherwise false. */ public boolean isRange() { StateData mode = getData("ld_mode"); if (mode == null) { logger.warn("No mode location/range chosen. Defaults to range."); return true; } String value = (String) mode.getValue(); return value.equals("distance"); } /** * Returns the selected locations based on a given array of locations. * * @param locations The StateData that contains the locations. * * @return the selected locations. */ public double[] getLocations() { StateData dLocations = getData("ld_locations"); String locationStr = dLocations != null ? (String) dLocations.getValue() : ""; if (locationStr == null || locationStr.length() == 0) { logger.warn("No valid location string found!"); return null; } String[] tmp = locationStr.split(" "); TDoubleArrayList locations = new TDoubleArrayList(); for (String l: tmp) { try { locations.add(Double.parseDouble(l)); } catch (NumberFormatException nfe) { logger.warn(nfe, nfe); } } locations.sort(); return locations.toNativeArray(); } /** * Returns the selected distance based on a given range (from, to). * * @param dFrom The StateData that contains the lower value. * @param dTo The StateData that contains the upper value. * * @return the selected distance. */ protected double[] getDistanceByRange(StateData dFrom, StateData dTo) { double from = Double.parseDouble((String) dFrom.getValue()); double to = Double.parseDouble((String) dTo.getValue()); return new double[] { from, to }; } /** * Returns the selected Kms. * * @param distance An 2dim array with [lower, upper] values. * * @return the selected Kms. */ public double[] getKms(double[] distance) { StateData dStep = getData("ld_step"); if (dStep == null) { logger.warn("No step width given. Cannot compute Kms."); return null; } double step = Double.parseDouble((String) dStep.getValue()); // transform step from 'm' into 'km' step = step / 1000; if (step == 0d) { step = DEFAULT_KM_STEPS; } return DoubleUtil.explode(distance[0], distance[1], step); } /** * Returns the selected Kms. * * @return the selected kms. */ public double[] getKms() { if (isRange()) { double[] distance = getDistance(); return getKms(distance); } else { return LocationDistanceSelect.getLocations(this); } } public double [] getFromToStep() { if (!isRange()) { return null; } double [] fromTo = getDistance(); if (fromTo == null) { return null; } StateData dStep = getData("ld_step"); if (dStep == null) { return null; } double [] result = new double[3]; result[0] = fromTo[0]; result[1] = fromTo[1]; try { String step = (String)dStep.getValue(); result[2] = DoubleUtil.round(Double.parseDouble(step) / 1000d); } catch (NumberFormatException nfe) { return null; } return result; } /** * Returns the gauge based on the current distance and river. * * @return the gauge. */ public Gauge getGauge() { River river = getRiver(); if (river == null) { logger.debug("no river found"); return null; } double[] dist = getDistance(); if (dist == null) { logger.debug("no range found"); return null; } if (logger.isDebugEnabled()) { logger.debug("Determine gauge for:"); logger.debug("... river: " + river.getName()); logger.debug("... distance: " + dist[0] + " - " + dist[1]); } Gauge gauge = river.determineGauge(dist[0], dist[1]); String name = gauge != null ? gauge.getName() : "'n/a"; logger.debug("Found gauge: " + name); return gauge; } /** * Returns the gauges that match the selected kilometer range. * * @return the gauges based on the selected kilometer range. */ public List<Gauge> getGauges() { River river = getRiver(); if (river == null) { return null; } double [] dist = getDistance(); if (dist == null) { return null; } return river.determineGauges(dist[0], dist[1]); } /** * This method returns the Q values. * * @return the selected Q values or null, if no Q values are selected. */ public double[] getQs() { StateData dMode = getData("wq_mode"); StateData dSelection = getData("wq_selection"); String mode = dMode != null ? (String) dMode.getValue() : ""; String sel = dSelection != null ? (String)dSelection.getValue() : null; if (mode.equals("Q")) { if (sel != null && sel.equals("single")) { return getSingleWQValues(); } else { return getWQTriple(); } } else { logger.warn("You try to get Qs, but W has been inserted."); return null; } } public boolean isQ() { StateData mode = getData("wq_mode"); return mode != null && mode.getValue().equals("Q"); } /** * Returns true, if the parameter is set to compute data on a free range. * Otherwise it returns false, which tells the calculation that it is bound * to a gauge. * * @return true, if the calculation should compute on a free range otherwise * false and the calculation is bound to a gauge. */ public boolean isFreeQ() { StateData mode = getData("wq_free"); String value = mode != null ? (String) mode.getValue() : null; logger.debug("isFreeQ: " + value); if (value == null) { return false; } return Boolean.valueOf(value); } /** * Returns the Q values based on a specified kilometer range. * * @param range A 2dim array with lower and upper kilometer range. * * @return an array of Q values. */ public double[] getQs(double[] range) { StateData dMode = getData("wq_mode"); StateData dValues = getData("wq_values"); String mode = dMode != null ? (String) dMode.getValue() : ""; if (mode.equals("Q")) { return getWQForDist(range); } logger.warn("You try to get Qs, but Ws has been inserted."); return null; } /** * Returns the W values based on a specified kilometer range. * * @param range A 2dim array with lower and upper kilometer range. * * @return an array of W values. */ public double[] getWs(double[] range) { StateData dMode = getData("wq_mode"); StateData dValues = getData("wq_values"); String mode = dMode != null ? (String) dMode.getValue() : ""; if (mode.equals("W")) { return getWQForDist(range); } logger.warn("You try to get Ws, but Qs has been inserted."); return null; } /** * This method returns the W values. * * @return the selected W values or null, if no W values are selected. */ public double[] getWs() { StateData dMode = getData("wq_mode"); StateData dSingle = getData("wq_single"); String mode = dMode != null ? (String) dMode.getValue() : ""; if (mode.equals("W")) { if (dSingle != null) { return getSingleWQValues(); } else { return getWQTriple(); } } else { logger.warn("You try to get Qs, but W has been inserted."); return null; } } /** * This method returns the given W or Q values for a specific range * (inserted in the WQ input panel for discharge longitudinal sections). * * @param dist A 2dim array with lower und upper kilometer values. * * @return an array of W or Q values. */ protected double[] getWQForDist(double[] dist) { logger.debug("Search wq values for range: " + dist[0] + " - " + dist[1]); StateData data = getData("wq_values"); if (data == null) { logger.warn("Missing wq values!"); return null; } String dataString = (String) data.getValue(); String[] ranges = dataString.split(":"); for (String range: ranges) { String[] parts = range.split(";"); double lower = Double.parseDouble(parts[0]); double upper = Double.parseDouble(parts[1]); if (lower <= dist[0] && upper >= dist[1]) { String[] values = parts[2].split(","); int num = values.length; double[] res = new double[num]; for (int i = 0; i < num; i++) { try { res[i] = Double.parseDouble(values[i]); } catch (NumberFormatException nfe) { logger.warn(nfe, nfe); } } return res; } } logger.warn("Specified range for WQ not found!"); return null; } /** * This method returns an array of inserted WQ triples that consist of from, * to and the step width. * * @return an array of from, to and step width. */ protected double[] getWQTriple() { StateData dFrom = getData("wq_from"); StateData dTo = getData("wq_to"); if (dFrom == null || dTo == null) { logger.warn("Missing start or end value for range."); return null; } double from = Double.parseDouble((String) dFrom.getValue()); double to = Double.parseDouble((String) dTo.getValue()); StateData dStep = getData("wq_step"); if (dStep == null) { logger.warn("No step width given. Cannot compute Qs."); return null; } double step = Double.parseDouble((String) dStep.getValue()); // if no width is given, the DEFAULT_Q_STEPS is used to compute the step // width. Maybe, we should round the value to a number of digits. if (step == 0d) { double diff = to - from; step = diff / DEFAULT_Q_STEPS; } return DoubleUtil.explode(from, to, step); } /** * Returns an array of inserted WQ double values stored as whitespace * separated list. * * @return an array of W or Q values. */ protected double[] getSingleWQValues() { StateData dSingle = getData("wq_single"); if (dSingle == null) { logger.warn("Cannot determine single WQ values. No data given."); return null; } String tmp = (String) dSingle.getValue(); String[] strValues = tmp.split(" "); TDoubleArrayList values = new TDoubleArrayList(); for (String strValue: strValues) { try { values.add(Double.parseDouble(strValue)); } catch (NumberFormatException nfe) { logger.warn(nfe, nfe); } } values.sort(); return values.toNativeArray(); } } // vim:set ts=4 sw=4 si et sta sts=4 fenc=utf8 :