1 /* ============================================================
2 * JRobin : Pure java implementation of RRDTool's functionality
3 * ============================================================
4 *
5 * Project Info: http://www.jrobin.org
6 * Project Lead: Sasa Markovic (saxon@jrobin.org);
7 *
8 * (C) Copyright 2003-2005, by Sasa Markovic.
9 *
10 * Developers: Sasa Markovic (saxon@jrobin.org)
11 *
12 *
13 * This library is free software; you can redistribute it and/or modify it under the terms
14 * of the GNU Lesser General Public License as published by the Free Software Foundation;
15 * either version 2.1 of the License, or (at your option) any later version.
16 *
17 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
18 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
19 * See the GNU Lesser General Public License for more details.
20 *
21 * You should have received a copy of the GNU Lesser General Public License along with this
22 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
23 * Boston, MA 02111-1307, USA.
24 */
25
26 package org.jrobin.core;
27
28 import org.jrobin.core.timespec.TimeParser;
29 import org.jrobin.core.timespec.TimeSpec;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.Node;
33 import org.w3c.dom.NodeList;
34 import org.xml.sax.InputSource;
35 import org.xml.sax.SAXException;
36
37 import javax.xml.parsers.DocumentBuilder;
38 import javax.xml.parsers.DocumentBuilderFactory;
39 import javax.xml.parsers.ParserConfigurationException;
40 import java.awt.*;
41 import java.io.*;
42 import java.text.DecimalFormat;
43 import java.text.NumberFormat;
44 import java.text.ParseException;
45 import java.text.SimpleDateFormat;
46 import java.util.ArrayList;
47 import java.util.Calendar;
48 import java.util.Date;
49 import java.util.Locale;
50
51 /**
52 * Class defines various utility functions used in JRobin.
53 *
54 * @author <a href="mailto:saxon@jrobin.org">Sasa Markovic</a>
55 */
56 public class Util {
57
58 public static final long MAX_LONG = Long.MAX_VALUE;
59 public static final long MIN_LONG = -Long.MAX_VALUE;
60
61 public static final double MAX_DOUBLE = Double.MAX_VALUE;
62 public static final double MIN_DOUBLE = -Double.MAX_VALUE;
63
64 // pattern RRDTool uses to format doubles in XML files
65 static final String PATTERN = "0.0000000000E00";
66 // directory under $USER_HOME used for demo graphs storing
67 static final String JROBIN_DIR = "jrobin-demo";
68
69 static final DecimalFormat df;
70
71 static {
72 df = (DecimalFormat) NumberFormat.getNumberInstance(Locale.ENGLISH);
73 df.applyPattern(PATTERN);
74 df.setPositivePrefix("+");
75 }
76
77 /**
78 * Converts an array of long primitives to an array of doubles.
79 *
80 * @return Same array but with all values as double.
81 */
82 public static double[] toDoubleArray(final long[] array) {
83 double[] values = new double[ array.length ];
84 for (int i = 0; i < array.length; i++) {
85 values[i] = array[i];
86 }
87 return values;
88 }
89
90 /**
91 * Returns current timestamp in seconds (without milliseconds). Returned timestamp
92 * is obtained with the following expression: <p>
93 * <p/>
94 * <code>(System.currentTimeMillis() + 500L) / 1000L</code>
95 *
96 * @return Current timestamp
97 */
98 public static long getTime() {
99 return (System.currentTimeMillis() + 500L) / 1000L;
100 }
101
102 /**
103 * Just an alias for {@link #getTime()} method.
104 *
105 * @return Current timestamp (without milliseconds)
106 */
107 public static long getTimestamp() {
108 return getTime();
109 }
110
111 /**
112 * Rounds the given timestamp to the nearest whole "e;step"e;. Rounded value is obtained
113 * from the following expression:<p>
114 * <code>timestamp - timestamp % step;</code><p>
115 *
116 * @param timestamp Timestamp in seconds
117 * @param step Step in seconds
118 * @return "Rounded" timestamp
119 */
120 public static long normalize(long timestamp, long step) {
121 return timestamp - timestamp % step;
122 }
123
124 /**
125 * Returns the greater of two double values, but treats NaN as the smallest possible
126 * value. Note that <code>Math.max()</code> behaves differently for NaN arguments.
127 *
128 * @param x an argument
129 * @param y another argument
130 * @return the lager of arguments
131 */
132 public static double max(double x, double y) {
133 return Double.isNaN(x) ? y : Double.isNaN(y) ? x : Math.max(x, y);
134 }
135
136 /**
137 * Returns the smaller of two double values, but treats NaN as the greatest possible
138 * value. Note that <code>Math.min()</code> behaves differently for NaN arguments.
139 *
140 * @param x an argument
141 * @param y another argument
142 * @return the smaller of arguments
143 */
144 public static double min(double x, double y) {
145 return Double.isNaN(x) ? y : Double.isNaN(y) ? x : Math.min(x, y);
146 }
147
148 /**
149 * Calculates sum of two doubles, but treats NaNs as zeros.
150 *
151 * @param x First double
152 * @param y Second double
153 * @return Sum(x,y) calculated as <code>Double.isNaN(x)? y: Double.isNaN(y)? x: x + y;</code>
154 */
155 public static double sum(double x, double y) {
156 return Double.isNaN(x) ? y : Double.isNaN(y) ? x : x + y;
157 }
158
159 static String formatDouble(double x, String nanString, boolean forceExponents) {
160 if (Double.isNaN(x)) {
161 return nanString;
162 }
163 if (forceExponents) {
164 return df.format(x);
165 }
166 return "" + x;
167 }
168
169 static String formatDouble(double x, boolean forceExponents) {
170 return formatDouble(x, "" + Double.NaN, forceExponents);
171 }
172
173 /**
174 * Formats double as a string using exponential notation (RRDTool like). Used for debugging
175 * throught the project.
176 *
177 * @param x value to be formatted
178 * @return string like "+1.234567E+02"
179 */
180 public static String formatDouble(double x) {
181 return formatDouble(x, true);
182 }
183
184 /**
185 * Returns <code>Date</code> object for the given timestamp (in seconds, without
186 * milliseconds)
187 *
188 * @param timestamp Timestamp in seconds.
189 * @return Corresponding Date object.
190 */
191 public static Date getDate(long timestamp) {
192 return new Date(timestamp * 1000L);
193 }
194
195 /**
196 * Returns <code>Calendar</code> object for the given timestamp
197 * (in seconds, without milliseconds)
198 *
199 * @param timestamp Timestamp in seconds.
200 * @return Corresponding Calendar object.
201 */
202 public static Calendar getCalendar(long timestamp) {
203 Calendar calendar = Calendar.getInstance();
204 calendar.setTimeInMillis(timestamp * 1000L);
205 return calendar;
206 }
207
208 /**
209 * Returns <code>Calendar</code> object for the given Date object
210 *
211 * @param date Date object
212 * @return Corresponding Calendar object.
213 */
214 public static Calendar getCalendar(Date date) {
215 Calendar calendar = Calendar.getInstance();
216 calendar.setTime(date);
217 return calendar;
218 }
219
220 /**
221 * Returns timestamp (unix epoch) for the given Date object
222 *
223 * @param date Date object
224 * @return Corresponding timestamp (without milliseconds)
225 */
226 public static long getTimestamp(Date date) {
227 // round to whole seconds, ignore milliseconds
228 return (date.getTime() + 499L) / 1000L;
229 }
230
231 /**
232 * Returns timestamp (unix epoch) for the given Calendar object
233 *
234 * @param gc Calendar object
235 * @return Corresponding timestamp (without milliseconds)
236 */
237 public static long getTimestamp(Calendar gc) {
238 return getTimestamp(gc.getTime());
239 }
240
241 /**
242 * Returns timestamp (unix epoch) for the given year, month, day, hour and minute.
243 *
244 * @param year Year
245 * @param month Month (zero-based)
246 * @param day Day in month
247 * @param hour Hour
248 * @param min Minute
249 * @return Corresponding timestamp
250 */
251 public static long getTimestamp(int year, int month, int day, int hour, int min) {
252 Calendar calendar = Calendar.getInstance();
253 calendar.clear();
254 calendar.set(year, month, day, hour, min);
255 return Util.getTimestamp(calendar);
256 }
257
258 /**
259 * Returns timestamp (unix epoch) for the given year, month and day.
260 *
261 * @param year Year
262 * @param month Month (zero-based)
263 * @param day Day in month
264 * @return Corresponding timestamp
265 */
266 public static long getTimestamp(int year, int month, int day) {
267 return Util.getTimestamp(year, month, day, 0, 0);
268 }
269
270 /**
271 * Parses at-style time specification and returns the corresponding timestamp. For example:<p>
272 * <pre>
273 * long t = Util.getTimestamp("now-1d");
274 * </pre>
275 *
276 * @param atStyleTimeSpec at-style time specification. For the complete explanation of the syntax
277 * allowed see RRDTool's <code>rrdfetch</code> man page.<p>
278 * @return timestamp in seconds since epoch.
279 * @throws RrdException Thrown if invalid time specification is supplied.
280 */
281 public static long getTimestamp(String atStyleTimeSpec) throws RrdException {
282 TimeSpec timeSpec = new TimeParser(atStyleTimeSpec).parse();
283 return timeSpec.getTimestamp();
284 }
285
286 /**
287 * Parses two related at-style time specifications and returns corresponding timestamps. For example:<p>
288 * <pre>
289 * long[] t = Util.getTimestamps("end-1d","now");
290 * </pre>
291 *
292 * @param atStyleTimeSpec1 Starting at-style time specification. For the complete explanation of the syntax
293 * allowed see RRDTool's <code>rrdfetch</code> man page.<p>
294 * @param atStyleTimeSpec2 Ending at-style time specification. For the complete explanation of the syntax
295 * allowed see RRDTool's <code>rrdfetch</code> man page.<p>
296 * @return An array of two longs representing starting and ending timestamp in seconds since epoch.
297 * @throws RrdException Thrown if any input time specification is invalid.
298 */
299 public static long[] getTimestamps(String atStyleTimeSpec1, String atStyleTimeSpec2) throws RrdException {
300 TimeSpec timeSpec1 = new TimeParser(atStyleTimeSpec1).parse();
301 TimeSpec timeSpec2 = new TimeParser(atStyleTimeSpec2).parse();
302 return TimeSpec.getTimestamps(timeSpec1, timeSpec2);
303 }
304
305 /**
306 * Parses input string as a double value. If the value cannot be parsed, Double.NaN
307 * is returned (NumberFormatException is never thrown).
308 *
309 * @param valueStr String representing double value
310 * @return a double corresponding to the input string
311 */
312 public static double parseDouble(String valueStr) {
313 double value;
314 try {
315 value = Double.parseDouble(valueStr);
316 }
317 catch (NumberFormatException nfe) {
318 value = Double.NaN;
319 }
320 return value;
321 }
322
323 /**
324 * Checks if a string can be parsed as double.
325 *
326 * @param s Input string
327 * @return <code>true</code> if the string can be parsed as double, <code>false</code> otherwise
328 */
329 public static boolean isDouble(String s) {
330 try {
331 Double.parseDouble(s);
332 return true;
333 }
334 catch (NumberFormatException nfe) {
335 return false;
336 }
337 }
338
339 /**
340 * Parses input string as a boolean value. The parser is case insensitive.
341 *
342 * @param valueStr String representing boolean value
343 * @return <code>true</code>, if valueStr equals to 'true', 'on', 'yes', 'y' or '1';
344 * <code>false</code> in all other cases.
345 */
346 public static boolean parseBoolean(String valueStr) {
347 return valueStr.equalsIgnoreCase("true") ||
348 valueStr.equalsIgnoreCase("on") ||
349 valueStr.equalsIgnoreCase("yes") ||
350 valueStr.equalsIgnoreCase("y") ||
351 valueStr.equalsIgnoreCase("1");
352 }
353
354 /**
355 * Parses input string as color. The color string should be of the form #RRGGBB (no alpha specified,
356 * opaque color) or #RRGGBBAA (alpa specified, transparent colors). Leading character '#' is
357 * optional.
358 *
359 * @param valueStr Input string, for example #FFAA24, #AABBCC33, 010203 or ABC13E4F
360 * @return Paint object
361 * @throws RrdException If the input string is not 6 or 8 characters long (without optional '#')
362 */
363 public static Paint parseColor(String valueStr) throws RrdException {
364 String c = valueStr.startsWith("#") ? valueStr.substring(1) : valueStr;
365 if (c.length() != 6 && c.length() != 8) {
366 throw new RrdException("Invalid color specification: " + valueStr);
367 }
368 String r = c.substring(0, 2), g = c.substring(2, 4), b = c.substring(4, 6);
369 if (c.length() == 6) {
370 return new Color(Integer.parseInt(r, 16), Integer.parseInt(g, 16), Integer.parseInt(b, 16));
371 }
372 else {
373 String a = c.substring(6);
374 return new Color(Integer.parseInt(r, 16), Integer.parseInt(g, 16),
375 Integer.parseInt(b, 16), Integer.parseInt(a, 16));
376 }
377 }
378
379 /**
380 * Returns file system separator string.
381 *
382 * @return File system separator ("/" on Unix, "\" on Windows)
383 */
384 public static String getFileSeparator() {
385 return System.getProperty("file.separator");
386 }
387
388 /**
389 * Returns path to user's home directory.
390 *
391 * @return Path to users home directory, with file separator appended.
392 */
393 public static String getUserHomeDirectory() {
394 return System.getProperty("user.home") + getFileSeparator();
395 }
396
397 /**
398 * Returns path to directory used for placement of JRobin demo graphs and creates it
399 * if necessary.
400 *
401 * @return Path to demo directory (defaults to $HOME/jrobin/) if directory exists or
402 * was successfully created. Null if such directory could not be created.
403 */
404 public static String getJRobinDemoDirectory() {
405 String homeDirPath = getUserHomeDirectory() + JROBIN_DIR + getFileSeparator();
406 File homeDirFile = new File(homeDirPath);
407 return (homeDirFile.exists() || homeDirFile.mkdirs()) ? homeDirPath : null;
408 }
409
410 /**
411 * Returns full path to the file stored in the demo directory of JRobin
412 *
413 * @param filename Partial path to the file stored in the demo directory of JRobin
414 * (just name and extension, without parent directories)
415 * @return Full path to the file
416 */
417 public static String getJRobinDemoPath(String filename) {
418 String demoDir = getJRobinDemoDirectory();
419 if (demoDir != null) {
420 return demoDir + filename;
421 }
422 else {
423 return null;
424 }
425 }
426
427 static boolean sameFilePath(String path1, String path2) throws IOException {
428 File file1 = new File(path1);
429 File file2 = new File(path2);
430 return file1.getCanonicalPath().equals(file2.getCanonicalPath());
431 }
432
433 static int getMatchingDatasourceIndex(RrdDb rrd1, int dsIndex, RrdDb rrd2) throws IOException {
434 String dsName = rrd1.getDatasource(dsIndex).getDsName();
435 try {
436 return rrd2.getDsIndex(dsName);
437 }
438 catch (RrdException e) {
439 return -1;
440 }
441 }
442
443 static int getMatchingArchiveIndex(RrdDb rrd1, int arcIndex, RrdDb rrd2)
444 throws IOException {
445 Archive archive = rrd1.getArchive(arcIndex);
446 String consolFun = archive.getConsolFun();
447 int steps = archive.getSteps();
448 try {
449 return rrd2.getArcIndex(consolFun, steps);
450 }
451 catch (RrdException e) {
452 return -1;
453 }
454 }
455
456 static String getTmpFilename() throws IOException {
457 return File.createTempFile("JROBIN_", ".tmp").getCanonicalPath();
458 }
459
460 static final String ISO_DATE_FORMAT = "yyyy-MM-dd HH:mm:ss"; // ISO
461
462 /**
463 * Creates Calendar object from a string. The string should represent
464 * either a long integer (UNIX timestamp in seconds without milliseconds,
465 * like "1002354657") or a human readable date string in the format "yyyy-MM-dd HH:mm:ss"
466 * (like "2004-02-25 12:23:45").
467 *
468 * @param timeStr Input string
469 * @return Calendar object
470 */
471 public static Calendar getCalendar(String timeStr) {
472 // try to parse it as long
473 try {
474 long timestamp = Long.parseLong(timeStr);
475 return Util.getCalendar(timestamp);
476 }
477 catch (NumberFormatException nfe) {
478 // not a long timestamp, try to parse it as data
479 SimpleDateFormat df = new SimpleDateFormat(ISO_DATE_FORMAT);
480 df.setLenient(false);
481 try {
482 Date date = df.parse(timeStr);
483 return Util.getCalendar(date);
484 }
485 catch (ParseException pe) {
486 throw new IllegalArgumentException("Time/date not in " + ISO_DATE_FORMAT +
487 " format: " + timeStr);
488 }
489 }
490 }
491
492 /**
493 * Various DOM utility functions
494 */
495 public static class Xml {
496 public static Node[] getChildNodes(Node parentNode) {
497 return getChildNodes(parentNode, null);
498 }
499
500 public static Node[] getChildNodes(Node parentNode, String childName) {
501 ArrayList<Node> nodes = new ArrayList<Node>();
502 NodeList nodeList = parentNode.getChildNodes();
503 for (int i = 0; i < nodeList.getLength(); i++) {
504 Node node = nodeList.item(i);
505 if (childName == null || node.getNodeName().equals(childName)) {
506 nodes.add(node);
507 }
508 }
509 return nodes.toArray(new Node[0]);
510 }
511
512 public static Node getFirstChildNode(Node parentNode, String childName) throws RrdException {
513 Node[] childs = getChildNodes(parentNode, childName);
514 if (childs.length > 0) {
515 return childs[0];
516 }
517 throw new RrdException("XML Error, no such child: " + childName);
518 }
519
520 public static boolean hasChildNode(Node parentNode, String childName) {
521 Node[] childs = getChildNodes(parentNode, childName);
522 return childs.length > 0;
523 }
524
525 // -- Wrapper around getChildValue with trim
526 public static String getChildValue(Node parentNode, String childName) throws RrdException {
527 return getChildValue(parentNode, childName, true);
528 }
529
530 public static String getChildValue(Node parentNode, String childName, boolean trim) throws RrdException {
531 NodeList children = parentNode.getChildNodes();
532 for (int i = 0; i < children.getLength(); i++) {
533 Node child = children.item(i);
534 if (child.getNodeName().equals(childName)) {
535 return getValue(child, trim);
536 }
537 }
538 throw new RrdException("XML Error, no such child: " + childName);
539 }
540
541 // -- Wrapper around getValue with trim
542 public static String getValue(Node node) {
543 return getValue(node, true);
544 }
545
546 public static String getValue(Node node, boolean trimValue) {
547 String value = null;
548 Node child = node.getFirstChild();
549 if (child != null) {
550 value = child.getNodeValue();
551 if (value != null && trimValue) {
552 value = value.trim();
553 }
554 }
555 return value;
556 }
557
558 public static int getChildValueAsInt(Node parentNode, String childName) throws RrdException {
559 String valueStr = getChildValue(parentNode, childName);
560 return Integer.parseInt(valueStr);
561 }
562
563 public static int getValueAsInt(Node node) {
564 String valueStr = getValue(node);
565 return Integer.parseInt(valueStr);
566 }
567
568 public static long getChildValueAsLong(Node parentNode, String childName) throws RrdException {
569 String valueStr = getChildValue(parentNode, childName);
570 return Long.parseLong(valueStr);
571 }
572
573 public static long getValueAsLong(Node node) {
574 String valueStr = getValue(node);
575 return Long.parseLong(valueStr);
576 }
577
578 public static double getChildValueAsDouble(Node parentNode, String childName) throws RrdException {
579 String valueStr = getChildValue(parentNode, childName);
580 return Util.parseDouble(valueStr);
581 }
582
583 public static double getValueAsDouble(Node node) {
584 String valueStr = getValue(node);
585 return Util.parseDouble(valueStr);
586 }
587
588 public static boolean getChildValueAsBoolean(Node parentNode, String childName) throws RrdException {
589 String valueStr = getChildValue(parentNode, childName);
590 return Util.parseBoolean(valueStr);
591 }
592
593 public static boolean getValueAsBoolean(Node node) {
594 String valueStr = getValue(node);
595 return Util.parseBoolean(valueStr);
596 }
597
598 public static Element getRootElement(InputSource inputSource) throws RrdException, IOException {
599 DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
600 factory.setValidating(false);
601 factory.setNamespaceAware(false);
602 try {
603 DocumentBuilder builder = factory.newDocumentBuilder();
604 Document doc = builder.parse(inputSource);
605 return doc.getDocumentElement();
606 }
607 catch (ParserConfigurationException e) {
608 throw new RrdException(e);
609 }
610 catch (SAXException e) {
611 throw new RrdException(e);
612 }
613 }
614
615 public static Element getRootElement(String xmlString) throws RrdException, IOException {
616 return getRootElement(new InputSource(new StringReader(xmlString)));
617 }
618
619 public static Element getRootElement(File xmlFile) throws RrdException, IOException {
620 Reader reader = null;
621 try {
622 reader = new FileReader(xmlFile);
623 return getRootElement(new InputSource(reader));
624 }
625 finally {
626 if (reader != null) {
627 reader.close();
628 }
629 }
630 }
631 }
632
633 private static long lastLap = System.currentTimeMillis();
634
635 /**
636 * Function used for debugging purposes and performance bottlenecks detection.
637 * Probably of no use for end users of JRobin.
638 *
639 * @return String representing time in seconds since last
640 * <code>getLapTime()</code> method call.
641 */
642 public static String getLapTime() {
643 long newLap = System.currentTimeMillis();
644 double seconds = (newLap - lastLap) / 1000.0;
645 lastLap = newLap;
646 return "[" + seconds + " sec]";
647 }
648
649 /**
650 * Returns the root directory of the JRobin distribution. Useful in some demo applications,
651 * probably of no use anywhere else.<p>
652 * <p/>
653 * The function assumes that all JRobin .class files are placed under
654 * the <root>/classes subdirectory and that all jars (libraries) are placed in the
655 * <root>/lib subdirectory (the original JRobin directory structure).<p>
656 *
657 * @return absolute path to JRobin's home directory
658 */
659 public static String getJRobinHomeDirectory() {
660 String className = Util.class.getName().replace('.', '/');
661 String uri = Util.class.getResource("/" + className + ".class").toString();
662 //System.out.println(uri);
663 if (uri.startsWith("file:/")) {
664 uri = uri.substring(6);
665 File file = new File(uri);
666 // let's go 5 steps backwards
667 for (int i = 0; i < 5; i++) {
668 file = file.getParentFile();
669 }
670 uri = file.getAbsolutePath();
671 }
672 else if (uri.startsWith("jar:file:/")) {
673 uri = uri.substring(9, uri.lastIndexOf('!'));
674 File file = new File(uri);
675 // let's go 2 steps backwards
676 for (int i = 0; i < 2; i++) {
677 file = file.getParentFile();
678 }
679 uri = file.getAbsolutePath();
680 }
681 else {
682 uri = null;
683 }
684 return uri;
685 }
686
687 /**
688 * Compares two doubles but treats all NaNs as equal.
689 * In Java (by default) Double.NaN == Double.NaN always returns <code>false</code>
690 *
691 * @param x the first value
692 * @param y the second value
693 * @return <code>true</code> if x and y are both equal to Double.NaN, or if x == y. <code>false</code> otherwise
694 */
695 public static boolean equal(double x, double y) {
696 return (Double.isNaN(x) && Double.isNaN(y)) || (x == y);
697 }
698
699 /**
700 * Returns canonical file path for the given file path
701 *
702 * @param path Absolute or relative file path
703 * @return Canonical file path
704 * @throws IOException Thrown if canonical file path could not be resolved
705 */
706 public static String getCanonicalPath(String path) throws IOException {
707 return new File(path).getCanonicalPath();
708 }
709
710 /**
711 * Returns last modification time for the given file.
712 *
713 * @param file File object representing file on the disk
714 * @return Last modification time in seconds (without milliseconds)
715 */
716 public static long getLastModified(String file) {
717 return (new File(file).lastModified() + 500L) / 1000L;
718 }
719
720 /**
721 * Checks if the file with the given file name exists
722 *
723 * @param filename File name
724 * @return <code>true</code> if file exists, <code>false</code> otherwise
725 */
726 public static boolean fileExists(String filename) {
727 return new File(filename).exists();
728 }
729
730 /**
731 * Finds max value for an array of doubles (NaNs are ignored). If all values in the array
732 * are NaNs, NaN is returned.
733 *
734 * @param values Array of double values
735 * @return max value in the array (NaNs are ignored)
736 */
737 public static double max(double[] values) {
738 double max = Double.NaN;
739 for (double value : values) {
740 max = Util.max(max, value);
741 }
742 return max;
743 }
744
745 /**
746 * Finds min value for an array of doubles (NaNs are ignored). If all values in the array
747 * are NaNs, NaN is returned.
748 *
749 * @param values Array of double values
750 * @return min value in the array (NaNs are ignored)
751 */
752 public static double min(double[] values) {
753 double min = Double.NaN;
754 for (double value : values) {
755 min = Util.min(min, value);
756 }
757 return min;
758 }
759
760 /**
761 * Equivalent of the C-style sprintf function. Sorry, it works only in Java5.
762 *
763 * @param format Format string
764 * @param args Arbitrary list of arguments
765 * @return Formatted string
766 */
767 public static String sprintf(String format, Object ... args) {
768 String fmt = format.replaceAll("([^%]|^)%([^a-zA-Z%]*)l(f|g|e)", "$1%$2$3");
769 return String.format(fmt, args);
770 }
771 }