View Javadoc

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 &quote;step&quote;. 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 &lt;root&gt;/classes subdirectory and that all jars (libraries) are placed in the
655 	 * &lt;root&gt;/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 }