/* * RapidMiner * * Copyright (C) 2001-2014 by RapidMiner and the contributors * * Complete list of developers available at our web site: * * http://rapidminer.com * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see http://www.gnu.org/licenses/. */ package com.rapidminer.tools; import java.io.BufferedInputStream; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.io.StreamTokenizer; import java.net.URL; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.charset.Charset; import java.text.DateFormat; import java.text.DecimalFormatSymbols; import java.text.NumberFormat; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.TimeZone; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Level; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPInputStream; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; import org.apache.commons.lang.StringEscapeUtils; import org.w3c.dom.Document; import org.xml.sax.SAXException; import com.rapidminer.RapidMiner; import com.rapidminer.gui.MainFrame; import com.rapidminer.gui.RapidMinerGUI; import com.rapidminer.gui.tools.VersionNumber; import com.rapidminer.io.process.XMLImporter; import com.rapidminer.operator.Operator; import com.rapidminer.operator.OperatorException; import com.rapidminer.operator.UserError; import com.rapidminer.parameter.UndefinedParameterError; import com.rapidminer.repository.RepositoryException; import com.rapidminer.tools.io.Encoding; import com.rapidminer.tools.plugin.Plugin; /** * Tools for RapidMiner. * * @author Simon Fischer, Ingo Mierswa */ public class Tools { /** Units for sizes in bytes. */ private static final String[] MEMORY_UNITS = { "b", "kB", "MB", "GB", "TB" }; /** The line separator depending on the operating system. */ private static final String LINE_SEPARATOR = System.getProperty("line.separator"); /** Number smaller than this value are considered as zero. */ private static final double IS_ZERO = 1E-6; /** Number of post-comma digits needed to distinguish between display of numbers as integers or doubles. */ private static final double IS_DISPLAY_ZERO = 1E-8; /** Used for formatting values in the {@link #formatTime(Date)} method. */ private static final TimeFormat DURATION_TIME_FORMAT = new TimeFormat(); /** Used for formatting values in the {@link #formatTime(Date)} method. */ private static final DateFormat TIME_FORMAT = DateFormat.getTimeInstance(DateFormat.LONG, Locale.getDefault()); /** Used for formatting values in the {@link #formatDate(Date)} method. */ private static final DateFormat DATE_FORMAT = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault()); /** Used for formatting values in the {@link #formatDateTime(Date)} method. */ private static final DateFormat DATE_TIME_FORMAT = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.LONG, Locale.getDefault()); private static Locale FORMAT_LOCALE = Locale.US; /** Used for formatting values in the {@link #formatNumber(double)} method. */ private static NumberFormat NUMBER_FORMAT = NumberFormat.getInstance(FORMAT_LOCALE); /** Used for formatting values in the {@link #formatNumber(double)} method. */ private static NumberFormat INTEGER_FORMAT = NumberFormat.getIntegerInstance(FORMAT_LOCALE); /** Used for formatting values in the {@link #formatPercent(double)} method. */ private static NumberFormat PERCENT_FORMAT = NumberFormat.getPercentInstance(FORMAT_LOCALE); /** Used for determining the symbols used in decimal formats. */ private static DecimalFormatSymbols FORMAT_SYMBOLS = new DecimalFormatSymbols(FORMAT_LOCALE); private static final LinkedList<ResourceSource> ALL_RESOURCE_SOURCES = new LinkedList<ResourceSource>(); public static final String RESOURCE_PREFIX = "com/rapidminer/resources/"; static { ALL_RESOURCE_SOURCES.add(new ResourceSource(Tools.class.getClassLoader())); } public static String[] availableTimeZoneNames; public static final int SYSTEM_TIME_ZONE = 0; static { String[] allTimeZoneNames = TimeZone.getAvailableIDs(); Arrays.sort(allTimeZoneNames); availableTimeZoneNames = new String[allTimeZoneNames.length + 1]; availableTimeZoneNames[SYSTEM_TIME_ZONE] = "SYSTEM"; System.arraycopy(allTimeZoneNames, 0, availableTimeZoneNames, 1, allTimeZoneNames.length); } public static void setFormatLocale(Locale locale) { FORMAT_LOCALE = locale; NUMBER_FORMAT = NumberFormat.getInstance(locale); INTEGER_FORMAT = NumberFormat.getIntegerInstance(locale); PERCENT_FORMAT = NumberFormat.getPercentInstance(locale); FORMAT_SYMBOLS = new DecimalFormatSymbols(locale); } public static Locale getFormatLocale() { return FORMAT_LOCALE; } public static String[] getAllTimeZones() { return availableTimeZoneNames; } public static TimeZone getTimeZone(int index) { if (index == SYSTEM_TIME_ZONE) return TimeZone.getDefault(); else return TimeZone.getTimeZone(availableTimeZoneNames[index]); } public static TimeZone getPreferredTimeZone() { return getTimeZone(getPreferredTimeZoneIndex()); } public static int getPreferredTimeZoneIndex() { String timeZoneString = ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_TIME_ZONE); int preferredTimeZone = SYSTEM_TIME_ZONE; try { if (timeZoneString != null) preferredTimeZone = Integer.parseInt(timeZoneString); } catch (NumberFormatException e) { int index = 0; boolean found = false; for (String id : availableTimeZoneNames) { if (id.equals(timeZoneString)) { found = true; break; } index++; } if (found) preferredTimeZone = index; } return preferredTimeZone; } public static Calendar getPreferredCalendar() { return Calendar.getInstance(getPreferredTimeZone(), Locale.getDefault()); } /** * Formats the value according to the given valueType. The value must be an object of type String for nominal * values, an object of type Date for date_time values or of type Double for numerical values. * * @param value * @param valueType * @return value as string */ public static String format(Object value, int valueType) { if (value == null) { return "?"; } if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.NOMINAL)) { return (String) value; } if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.NUMERICAL)) { return formatIntegerIfPossible((Double) value); } if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.DATE)) { return formatDate((Date) value); } if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.TIME)) { return formatTime((Date) value); } if (Ontology.ATTRIBUTE_VALUE_TYPE.isA(valueType, Ontology.DATE_TIME)) { return formatDateTime((Date) value); } return "?"; } /** * Returns a formatted string of the given number (percent format with two fraction digits). */ public static String formatPercent(double value) { if (Double.isNaN(value)) return "?"; String percentDigitsString = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_FRACTIONDIGITS_PERCENT); int percentDigits = 2; try { if (percentDigitsString != null) percentDigits = Integer.parseInt(percentDigitsString); } catch (NumberFormatException e) { //LogService.getGlobal().log("Bad integer for property 'rapidminer.gui.fractiondigits.percent', using default number if digits (2).", LogService.WARNING); LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.Tools.bad_integer_for_property"); } PERCENT_FORMAT.setMaximumFractionDigits(percentDigits); PERCENT_FORMAT.setMinimumFractionDigits(percentDigits); return PERCENT_FORMAT.format(value); } /** * Returns a formatted string of the given number (number format with usually three fraction digits). */ public static String formatNumber(double value) { if (Double.isNaN(value)) return "?"; int numberDigits = 3; try { String numberDigitsString = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_FRACTIONDIGITS_NUMBERS); numberDigits = Integer.parseInt(numberDigitsString); } catch (NumberFormatException e) {} // TODO: read property for grouping characters return formatNumber(value, numberDigits, false); } /** * Returns a formatted string of the given number (uses the property rapidminer.gui.fractiondigits.numbers if the * given number of digits is smaller than 0 (usually 3)). */ public static String formatNumber(double value, int numberOfDigits) { // TODO: read property for grouping characters return formatNumber(value, numberOfDigits, false); } /** * Returns a formatted string of the given number (uses the property rapidminer.gui.fractiondigits.numbers if the * given number of digits is smaller than 0 (usually 3)). */ public static String formatNumber(double value, int numberOfDigits, boolean groupingCharacters) { if (Double.isNaN(value)) return "?"; int numberDigits = numberOfDigits; if (numberDigits < 0) { try { String numberDigitsString = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_FRACTIONDIGITS_NUMBERS); numberDigits = Integer.parseInt(numberDigitsString); } catch (NumberFormatException e) { numberDigits = 3; } } NUMBER_FORMAT.setMaximumFractionDigits(numberDigits); NUMBER_FORMAT.setMinimumFractionDigits(numberDigits); NUMBER_FORMAT.setGroupingUsed(groupingCharacters); return NUMBER_FORMAT.format(value); } /** * Returns a number string with no fraction digits if possible. Otherwise the default number of digits will be * returned. */ public static String formatIntegerIfPossible(double value) { int numberDigits = 3; try { String numberDigitsString = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_FRACTIONDIGITS_NUMBERS); numberDigits = Integer.parseInt(numberDigitsString); } catch (NumberFormatException e) {} // TODO: read property for grouping characters return formatIntegerIfPossible(value, numberDigits, false); } /** * Returns a number string with no fraction digits if possible. Otherwise the given number of digits will be * returned. */ public static String formatIntegerIfPossible(double value, int numberOfDigits) { // TODO: read property for grouping characters return formatIntegerIfPossible(value, numberOfDigits, false); } /** * Returns a number string with no fraction digits if possible. Otherwise the given number of digits will be * returned. */ public static String formatIntegerIfPossible(double value, int numberOfDigits, boolean groupingCharacter) { if (Double.isNaN(value)) return "?"; if (Double.isInfinite(value)) { if (value < 0) return "-" + FORMAT_SYMBOLS.getInfinity(); else return FORMAT_SYMBOLS.getInfinity(); } long longValue = Math.round(value); if (Math.abs(longValue - value) < IS_DISPLAY_ZERO) { INTEGER_FORMAT.setGroupingUsed(groupingCharacter); return INTEGER_FORMAT.format(longValue); } else { return formatNumber(value, numberOfDigits, groupingCharacter); } } /** Format date as a short time string. */ public static String formatTime(Date date) { TIME_FORMAT.setTimeZone(getPreferredTimeZone()); return TIME_FORMAT.format(date); } /** Format date as a short time string. */ public static String formatDate(Date date) { DATE_FORMAT.setTimeZone(getPreferredTimeZone()); return DATE_FORMAT.format(date); } /** Format date as a short time string. */ public static String formatDateTime(Date date) { DATE_TIME_FORMAT.setTimeZone(getPreferredTimeZone()); return DATE_TIME_FORMAT.format(date); } public static String formatDateTime(Date date, String pattern) { SimpleDateFormat format = new SimpleDateFormat(pattern); format.setTimeZone(getPreferredTimeZone()); return format.format(date); } /** Format the given amount of milliseconds as a human readable string. */ public static String formatDuration(long milliseconds) { return DURATION_TIME_FORMAT.format(milliseconds); } /** Returns the name for an ordinal number. */ public static String ordinalNumber(int n) { if (n % 10 == 1 && n % 100 != 11) { return n + "st"; } if (n % 10 == 2 && n % 100 != 12) { return n + "nd"; } if (n % 10 == 3 && n % 100 != 13) { return n + "rd"; } return n + "th"; } /** Returns <code>true</code> if the difference between both numbers is smaller than IS_ZERO or both are Double.NaN. * If either d1 or d2 is Double.NaN it will return <code>false</code>. */ public static boolean isEqual(double d1, double d2) { // NaN handling if (Double.isNaN(d1) && Double.isNaN(d2)) { return true; } if (Double.isNaN(d1) || Double.isNaN(d2)) { return false; } // normal handling return Math.abs(d1 - d2) < IS_ZERO; } /** Returns {@link #isEqual(double, double)} for d and 0. */ public static boolean isZero(double d) { return isEqual(d, 0.0d); } /** Returns not {@link #isEqual(double, double)}. */ public static boolean isNotEqual(double d1, double d2) { return !isEqual(d1, d2); } /** Returns <code>false</code> if either d1 or d2 is Double.NaN. * Otherwis returns <code>true</code> if the d1 is greater than d2. */ public static boolean isGreater(double d1, double d2) { if (Double.isNaN(d1) || Double.isNaN(d2)) { return false; } return Double.compare(d1, d2) > 0; } /** Returns <code>false</code> if either d1 or d2 is Double.NaN. * Returns <code>true</code> if the d1 is greater than d2 or both are equal. */ public static boolean isGreaterEqual(double d1, double d2) { if (Double.isNaN(d1) || Double.isNaN(d2)) { return false; } return Double.compare(d1, d2) > 0 || isEqual(d1, d2); } /** Returns <code>false</code> if either d1 or d2 is Double.NaN. * Returns <code>true</code> if the d1 is less than d2. */ public static boolean isLess(double d1, double d2) { if (Double.isNaN(d1) || Double.isNaN(d2)) { return false; } return Double.compare(d1, d2) < 0; } /** Returns <code>false</code> if either d1 or d2 is Double.NaN. * Returns <code>true</code> if the d1 is less than d2 or both are equal. */ public static boolean isLessEqual(double d1, double d2) { if (Double.isNaN(d1) || Double.isNaN(d2)) { return false; } return Double.compare(d1, d2) < 0 || isEqual(d1, d2); } /** Returns <code>true</code> if date d1 is equal to date d2. * Returns <code>false</code> if either d1 or d2 is <code>null</code> or dates are not equal. */ public static boolean isEqual(Date d1, Date d2) { if (d1 == d2) { return true; } if (d1 == null || d2 == null) { return false; } return d1.compareTo(d2) == 0; } /** Returns no {@link #isEqual(Date, Date)}. */ public static boolean isNotEqual(Date d1, Date d2) { return !isEqual(d1, d2); } /** Returns <code>true</code> if the date d1 is greater than date d2. * Returns <code>false</code> if either d1 or d2 are <code>null</code>. */ public static boolean isGreater(Date d1, Date d2) { if (d1 == null || d2 == null) { return false; } return d1.compareTo(d2) > 0; } /** Returns true if the date d1 is greater than date d1 or both are equal */ public static boolean isGreaterEqual(Date d1, Date d2) { return isEqual(d1, d2) || (d1 != null) && d1.compareTo(d2) > 0; } /** Returns <code>true</code> if the date d1 is less than date d2. * Returns <code>false</code> if either d1 or d2 are <code>null</code>. */ public static boolean isLess(Date d1, Date d2) { if (d1 == null || d2 == null) { return false; } return d1 != null && d1.compareTo(d2) < 0; } /** Returns true if the date d1 is less than date d1 or both are equal. */ public static boolean isLessEqual(Date d1, Date d2) { return isEqual(d1, d2) || (d1 != null) && d1.compareTo(d2) < 0; } // ==================================== /** Returns the correct line separator for the current operating system. */ public static String getLineSeparator() { return LINE_SEPARATOR; } /** * Returns the correct line separator for the current operating system concatenated for the given number of times. */ public static String getLineSeparators(int number) { if (number < 0) number = 0; StringBuffer result = new StringBuffer(); for (int i = 0; i < number; i++) result.append(LINE_SEPARATOR); return result.toString(); } /** * Replaces all possible line feed character combinations by "\n". This might be important for GUI * purposes like tool tip texts which do not support carriage return combinations. */ public static String transformAllLineSeparators(String text) { Pattern crlf = Pattern.compile("(\r\n|\r|\n|\n\r)"); Matcher m = crlf.matcher(text); if (m.find()) { text = m.replaceAll("\n"); } return text; } /** * Removes all possible line feed character combinations. This might be important for GUI purposes like tool tip * texts which do not support carriage return combinations. */ public static String removeAllLineSeparators(String text) { Pattern crlf = Pattern.compile("(\r\n|\r|\n|\n\r)"); Matcher m = crlf.matcher(text); if (m.find()) { text = m.replaceAll(" "); } return text; } /** * Returns the class name of the given class without the package information. * * @deprecated Call c.getSimpleName() directly. */ @Deprecated public static String classNameWOPackage(Class c) { return c.getSimpleName(); } // ==================================== /** * Reads the output of the reader and delivers it as string. */ public static String readOutput(BufferedReader in) throws IOException { StringBuffer output = new StringBuffer(); String line = null; while ((line = in.readLine()) != null) { output.append(line); output.append(Tools.getLineSeparator()); } return output.toString(); } /** * Creates a file relative to the given parent if name is not an absolute file name. Returns null if name is null. */ public static File getFile(File parent, String name) { if (name == null) return null; File file = new File(name); if (file.isAbsolute()) return file; else return new File(parent, name); } /** * This method checks if the given file is a Zip file containing one entry (in case of file extension .zip). If this * is the case, a reader based on a ZipInputStream for this entry is returned. Otherwise, this method checks if the * file has the extension .gz. If this applies, a gzipped stream reader is returned. Otherwise, this method just * returns a BufferedReader for the given file (file was not zipped at all). */ public static BufferedReader getReader(File file, Charset encoding) throws IOException { // handle zip files if necessary if (file.getAbsolutePath().endsWith(".zip")) { ZipFile zipFile = new ZipFile(file); if (zipFile.size() == 0) { throw new IOException("Input of Zip file failed: the file archive does not contain any entries."); } if (zipFile.size() > 1) { throw new IOException("Input of Zip file failed: the file archive contains more than one entry."); } Enumeration<? extends ZipEntry> entries = zipFile.entries(); InputStream zipIn = zipFile.getInputStream(entries.nextElement()); return new BufferedReader(new InputStreamReader(zipIn, encoding)); } else if (file.getAbsolutePath().endsWith(".gz")) { return new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file)), encoding)); } else { return new BufferedReader(new InputStreamReader(new FileInputStream(file), encoding)); } } /** * This method tries to identify the encoding if a GUI is running and a process is defined. In this case, the * encoding is taken from the process. Otherwise, the method tries to identify the encoding via the property * {@link RapidMiner#PROPERTY_RAPIDMINER_GENERAL_DEFAULT_ENCODING}. If this is not possible, this method just * returns the default system encoding. */ public static Charset getDefaultEncoding() { Charset result = null; if (RapidMiner.getExecutionMode().hasMainFrame()) { MainFrame mainFrame = RapidMinerGUI.getMainFrame(); if (mainFrame != null) { com.rapidminer.Process process = mainFrame.getProcess(); if (process != null) { Operator rootOperator = process.getRootOperator(); if (rootOperator != null) { try { result = Encoding.getEncoding(rootOperator); } catch (UndefinedParameterError e) { result = Charset.defaultCharset(); } catch (UserError e) { result = Charset.defaultCharset(); } } } } } // try property setting if (result == null) { String encoding = ParameterService .getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_GENERAL_DEFAULT_ENCODING); if (encoding != null && encoding.trim().length() > 0) { if (RapidMiner.SYSTEM_ENCODING_NAME.equals(encoding)) { result = Charset.defaultCharset(); } else { result = Charset.forName(encoding); } } } // still not found? try default charset if (result == null) { result = Charset.defaultCharset(); } return result; } /** Returns the relative path of the first file resolved against the second. */ public static String getRelativePath(File firstFile, File secondFile) throws IOException { String canonicalFirstPath = firstFile.getCanonicalPath(); String canonicalSecondPath = secondFile.getCanonicalPath(); int minLength = Math.min(canonicalFirstPath.length(), canonicalSecondPath.length()); int index = 0; for (index = 0; index < minLength; index++) { if (canonicalFirstPath.charAt(index) != canonicalSecondPath.charAt(index)) { break; } } String relPath = canonicalFirstPath; int lastSeparatorIndex = canonicalFirstPath.substring(0, index).lastIndexOf(File.separator); if (lastSeparatorIndex != -1) { String absRest = canonicalSecondPath.substring(lastSeparatorIndex + 1); StringBuffer relPathBuffer = new StringBuffer(); while (absRest.indexOf(File.separator) >= 0) { relPathBuffer.append(".." + File.separator); absRest = absRest.substring(absRest.indexOf(File.separator) + 1); } relPathBuffer.append(canonicalFirstPath.substring(lastSeparatorIndex + 1)); relPath = relPathBuffer.toString(); } return relPath; } /** * Waits for process to die and writes log messages. Terminates if exit value is not 0. */ public static void waitForProcess(Operator operator, Process process, String name) throws OperatorException { try { //LogService.getGlobal().log("Waiting for process '" + name + "' to die.", LogService.MINIMUM); LogService.getRoot().log(Level.ALL, "com.rapidminer.tools.Tools.waiting_for_process", name); int value = process.waitFor(); if (value == 0) { //LogService.getGlobal().log("Process '" + name + "' terminated successfully.", LogService.STATUS); LogService.getRoot() .log(Level.FINE, "com.rapidminer.tools.Tools.process_terminated_successfully", name); } else { throw new UserError(operator, 306, new Object[] { name, value }); } } catch (InterruptedException e) { throw new RuntimeException("Interrupted waiting for process '" + name + "' to die.", e); } } /** * @deprecated Use {@link MailUtilities#sendEmail(String,String,String)} instead */ @Deprecated public static void sendEmail(String address, String subject, String content) { MailUtilities.sendEmail(address, subject, content); } /** Adds a new resource source. Might be used by plugins etc. */ public static void addResourceSource(ResourceSource source) { ALL_RESOURCE_SOURCES.add(source); } /** Adds a new resource source before the others. Might be used by plugins etc. */ public static void prependResourceSource(ResourceSource source) { ALL_RESOURCE_SOURCES.addFirst(source); } public static URL getResource(ClassLoader loader, String name) { return getResource(loader, RESOURCE_PREFIX, name); } public static URL getResource(ClassLoader loader, String prefix, String name) { return loader.getResource(prefix + name); } /** * Returns the desired resource. Tries first to find a resource in the core RapidMiner resources directory. If no * resource with the given name is found, it is tried to load with help of the ResourceSource which might have been * added by plugins. Please note that resource names are only allowed to use '/' as separator instead of * File.separator! */ public static URL getResource(String name) { Iterator<ResourceSource> i = ALL_RESOURCE_SOURCES.iterator(); while (i.hasNext()) { ResourceSource source = i.next(); URL url = source.getResource(name); if (url != null) { return url; } } URL resourceURL = getResource(Plugin.getMajorClassLoader(), name); if (resourceURL != null) { return resourceURL; } else { return null; } } /** * Return an input stream of the desired resource. Tries first to find a resource in the core RapidMiner resources directory. If no * resource with the given name is found, it is tried to load with help of the ResourceSource which might have been * added by plugins. Please note that resource names are only allowed to use '/' as separator instead of * File.separator! * * @throws IOException if stream cannot be opened * @throws RepositoryException if resource cannot be found */ public static InputStream getResourceInputStream(String name) throws IOException, RepositoryException { URL resourceURL = Tools.getResource(name); if (resourceURL == null) { throw new RepositoryException("Missing resource " + name); } return resourceURL.openStream(); } public static String readTextFile(InputStream in) throws IOException { return readTextFile(new InputStreamReader(in, "UTF-8")); } /** Reads a text file into a single string. * Process files created with RapidMiner 5.2.008 or earlier will be read with * the system encoding (for compatibility reasons); all other files will be read * with UTF-8 encoding. * */ public static String readTextFile(File file) throws IOException { FileInputStream inStream = new FileInputStream(file); // due to a bug in pre-5.2.009, process files were stored in System encoding instead of // UTF-8. So we have to check the process version, and if it's less than 5.2.009 we have // to retrieve the file again with System encoding. // If anything goes wrong while parsing the version number, we continue with the old // method. If something goes wrong, the file is either not utf-8 encoded (so the old // method will probably work), or it is not a valid process file (which will also be // detected by the old method). boolean useFallback = false; try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document processXmlDocument = documentBuilder.parse(inStream); XPathFactory xPathFactory = XPathFactory.newInstance(); XPath xPath = xPathFactory.newXPath(); String versionString = xPath.evaluate("/process/@version", processXmlDocument); VersionNumber version = new VersionNumber(versionString); if (version.isAtMost(5, 2, 8)) { useFallback = true; } } catch (XPathExpressionException e) { useFallback = true; } catch (SAXException e) { useFallback = true; } catch (ParserConfigurationException e) { useFallback = true; } catch (IOException e) { useFallback = true; } catch (NumberFormatException e) { useFallback = true; } InputStreamReader reader = null; try { inStream = new FileInputStream(file); if (useFallback) { // default reader (as in old versions) reader = new InputStreamReader(inStream); } else { // utf8 reader reader = new InputStreamReader(inStream, XMLImporter.PROCESS_FILE_CHARSET); } return readTextFile(reader); } finally { try { inStream.close(); } catch (IOException e) {} if (reader != null) { try { reader.close(); } catch (IOException e) {} } } } public static String readTextFile(Reader r) throws IOException { StringBuilder contents = new StringBuilder(); BufferedReader reader = new BufferedReader(r); String line = ""; try { while ((line = reader.readLine()) != null) { contents.append(line + Tools.getLineSeparator()); } } finally { reader.close(); } return contents.toString(); } public static void writeTextFile(File file, String text) throws IOException { // ! THIS IS TO PREVENT A JAVA PROBLEM (BUG?) ON WINDOWS NTFS FILESYSTEM ! // If the filename is something like C:\path\x:y.z, new FileOutputStream(file) will throw NO error // but the result will be an EMPTY file C:\path\x and you never know it failed // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4645046 if (file == null || !canFileBeStoredOnCurrentFilesystem(file.getName())) { throw new FileNotFoundException(I18N.getMessage(I18N.getErrorBundle(), "repository.illegal_entry_name", file == null ? "null" : file.getName())); } FileOutputStream outStream = new FileOutputStream(file); try { if (text != null) { outStream.write(text.getBytes(XMLImporter.PROCESS_FILE_CHARSET)); } } finally { outStream.close(); } } public static final String[] TRUE_STRINGS = { "true", "on", "yes", "y" }; public static final String[] FALSE_STRINGS = { "false", "off", "no", "n" }; public static boolean booleanValue(String string, boolean deflt) { if (string == null) return deflt; string = string.toLowerCase().trim(); for (String element : TRUE_STRINGS) { if (element.equals(string)) { return true; } } for (String element : FALSE_STRINGS) { if (element.equals(string)) { return false; } } return deflt; } public static File findSourceFile(StackTraceElement e) { try { Class clazz = Class.forName(e.getClassName()); while (clazz.getDeclaringClass() != null) clazz = clazz.getDeclaringClass(); String filename = clazz.getName().replace('.', File.separatorChar); return FileSystemService.getSourceFile(filename + ".java"); } catch (Throwable t) {} String filename = e.getClassName().replace('.', File.separatorChar); return FileSystemService.getSourceFile(filename + ".java"); } public static Process launchFileEditor(File file, int line) throws IOException { String editor = ParameterService.getParameterValue(RapidMiner.PROPERTY_RAPIDMINER_TOOLS_EDITOR); if (editor == null) throw new IOException("Property 'rapidminer.tools.editor' undefined."); editor = editor.replaceAll("%f", file.getAbsolutePath()); editor = editor.replaceAll("%l", line + ""); return Runtime.getRuntime().exec(editor); } /** Replaces angle brackets by html entities. */ public static String escapeXML(String string) { if (string == null) return "null"; return StringEscapeUtils.escapeXml(string); } /** * This method will encode the given string by replacing all forbidden characters by the appropriate HTML entity. */ public static String escapeHTML(String string) { return StringEscapeUtils.escapeHtml(string); } public static void findImplementationsInJar(JarFile jar, Class superClass, List<String> implementations) { findImplementationsInJar(Tools.class.getClassLoader(), jar, superClass, implementations); } public static void findImplementationsInJar(ClassLoader loader, JarFile jar, Class<?> superClass, List<String> implementations) { Enumeration<JarEntry> e = jar.entries(); while (e.hasMoreElements()) { JarEntry entry = e.nextElement(); String name = entry.getName(); int dotClass = name.lastIndexOf(".class"); if (dotClass < 0) continue; name = name.substring(0, dotClass); name = name.replaceAll("/", "\\."); try { Class<?> c = loader.loadClass(name); if (superClass.isAssignableFrom(c)) { if (!java.lang.reflect.Modifier.isAbstract(c.getModifiers())) { implementations.add(name); } } } catch (Throwable t) {} } } /** TODO: Looks like this can be replaced by {@link Plugin#getMajorClassLoader()} */ public static Class classForName(String className) throws ClassNotFoundException { try { return Class.forName(className); } catch (ClassNotFoundException e) {} try { return ClassLoader.getSystemClassLoader().loadClass(className); } catch (ClassNotFoundException e) {} Iterator i = Plugin.getAllPlugins().iterator(); while (i.hasNext()) { Plugin p = (Plugin) i.next(); try { return p.getClassLoader().loadClass(className); } catch (ClassNotFoundException e) { // this wasn't it, so continue } } throw new ClassNotFoundException(className); } /** * Splits the given line according to the given separator pattern while only those separators will be regarded not * lying inside of a quoting string. Please note that quoting characters will not be regarded if they are escaped by * an escaping character. The usual double quote (") is used for quoting and can be escaped by a backslash, * i.e. \". */ public static String[] quotedSplit(String line, Pattern separatorPattern) { return quotedSplit(line, separatorPattern, '"', '\\'); } /** * Splits the given line according to the given separator pattern while only those separators will be regarded not * lying inside of a quoting string. Please note that quoting characters will not be regarded if they are escaped by * an escaping character. */ public static String[] quotedSplit(String line, Pattern separatorPattern, char quotingChar, char escapeChar) { // determine split positions according to non-escaped quotes int[] quoteSplitIndices = new int[line.length()]; char lastChar = '0'; int lastSplitIndex = -1; for (int i = 0; i < line.length(); i++) { char currentChar = line.charAt(i); if (currentChar == quotingChar) { boolean escaped = false; if (i != 0 && lastChar == escapeChar) { escaped = true; } if (!escaped) { quoteSplitIndices[++lastSplitIndex] = i; } } lastChar = currentChar; } // add quote parts to a list and replace escape chars List<String> quotedSplits = new LinkedList<String>(); if (lastSplitIndex < 0) { line = line.replaceAll("\\\\\"", "\""); // remove escape characters quotedSplits.add(line); } else { int start = 0; for (int i = 0; i <= lastSplitIndex; i++) { int end = quoteSplitIndices[i]; String part = ""; if (end > start) { part = line.substring(start, end); } part = part.replaceAll("\\\\\"", "\""); // remove escape characters quotedSplits.add(part); start = end + 1; } if (start < line.length()) { String part = line.substring(start); part = part.replaceAll("\\\\\"", "\""); // remove escape characters quotedSplits.add(part); } } // now handle split and non split parts // ALGORITHM: // *** at Split-Parts: remove empty starts and endings, use empty parts in the middle (as missing), use also // non-empty parts (as non missing) // - Exception: the first and the last split parts. Here also the start and the beginning must be used even if // they are empty (they are missing then) // *** at Non-Split-Parts: simply use the whole value. It is missing if is empty. // IMPORTANT: a negative limit for the split method (here: -1) is very important in order to get empty trailing // values List<String> result = new LinkedList<String>(); boolean isSplitPart = true; int index = 0; for (String part : quotedSplits) { if (index > 0 || part.trim().length() > 0) { // skip first split if part is empty (coming from leading // quotes in the line) if (isSplitPart) { String[] separatedParts = separatorPattern.split(part, -1); // ATTENTION: a negative Limit is very // important to get trailing empty // strings for (int s = 0; s < separatedParts.length; s++) { String currentPart = separatedParts[s].trim(); if (currentPart.length() == 0) { // part is empty -- missing if in the middle or at line start // or end if (s == 0 && index == 0) { result.add(currentPart); } else if (s == separatedParts.length - 1 && index == quotedSplits.size() - 1) { result.add(currentPart); } else if (s > 0 && s < separatedParts.length - 1) { result.add(currentPart); } } else { result.add(currentPart); } } } else { result.add(part); } } isSplitPart = !isSplitPart; index++; } String[] resultArray = new String[result.size()]; result.toArray(resultArray); return resultArray; } /** * This method merges quoted splits, e.g. if a string line should be splitted by comma and commas inside of a quoted * string should not be used as splitting point. * * @param line * the original line * @param splittedTokens * the tokens as they were originally splitted * @param quoteString * the string which should be used as quote indicator, e.g. " or ' * @return the array of strings where the given quoteString was regarded * @throws IOException * if an open quote was not ended * * @deprecated Please use {@link #quotedSplit(String, Pattern, char, char)} or {@link #quotedSplit(String, Pattern)} * instead */ @Deprecated public static String[] mergeQuotedSplits(String line, String[] splittedTokens, String quoteString) throws IOException { int[] tokenStarts = new int[splittedTokens.length]; int currentCounter = 0; int currentIndex = 0; for (String currentToken : splittedTokens) { tokenStarts[currentIndex] = line.indexOf(currentToken, currentCounter); currentCounter = tokenStarts[currentIndex] + currentToken.length() + 1; currentIndex++; } List<String> tokens = new LinkedList<String>(); int start = -1; int end = -1; for (int i = 0; i < splittedTokens.length; i++) { if (splittedTokens[i].trim().startsWith(quoteString)) { start = i; } if (start >= 0) { StringBuffer current = new StringBuffer(); while (end < 0 && i < splittedTokens.length) { if (splittedTokens[i].endsWith(quoteString)) { end = i; break; } i++; } if (end < 0) throw new IOException("Error during reading: open quote \" is not ended!"); String lastToken = null; for (int a = start; a <= end; a++) { String nextToken = splittedTokens[a]; if (nextToken.length() == 0) continue; if (a == start) { nextToken = nextToken.substring(quoteString.length()); } if (a == end) { nextToken = nextToken.substring(0, nextToken.length() - quoteString.length()); } // add correct separator if (lastToken != null) { // int lastIndex = line.indexOf(lastToken, totalCounter - lastToken.length()) + // lastToken.length(); int lastIndex = tokenStarts[a - 1] + lastToken.length(); int thisIndex = tokenStarts[a]; if (lastIndex >= 0 && thisIndex >= lastIndex) { String separator = line.substring(lastIndex, thisIndex); current.append(separator); } } current.append(nextToken); lastToken = splittedTokens[a]; } tokens.add(current.toString()); start = -1; end = -1; } else { tokens.add(splittedTokens[i]); } } String[] quoted = new String[tokens.size()]; tokens.toArray(quoted); return quoted; } /** Delivers the next token and skip empty lines. */ public static void getFirstToken(StreamTokenizer tokenizer) throws IOException { // skip empty lines while (tokenizer.nextToken() == StreamTokenizer.TT_EOL) {} ; if (tokenizer.ttype == '\'' || tokenizer.ttype == '"') { tokenizer.ttype = StreamTokenizer.TT_WORD; } else if (tokenizer.ttype == StreamTokenizer.TT_WORD && tokenizer.sval.equals("?")) { tokenizer.ttype = '?'; } } /** Delivers the next token and checks if its the end of line. */ public static void getLastToken(StreamTokenizer tokenizer, boolean endOfFileOk) throws IOException { if (tokenizer.nextToken() != StreamTokenizer.TT_EOL && (tokenizer.ttype != StreamTokenizer.TT_EOF || !endOfFileOk)) { throw new IOException("expected the end of the line " + tokenizer.lineno()); } } /** Delivers the next token and checks for an unexpected end of line or file. */ public static void getNextToken(StreamTokenizer tokenizer) throws IOException { if (tokenizer.nextToken() == StreamTokenizer.TT_EOL) { throw new IOException("unexpected end of line " + tokenizer.lineno()); } if (tokenizer.ttype == StreamTokenizer.TT_EOF) { throw new IOException("unexpected end of file in line " + tokenizer.lineno()); } else if (tokenizer.ttype == '\'' || tokenizer.ttype == '"') { tokenizer.ttype = StreamTokenizer.TT_WORD; } else if (tokenizer.ttype == StreamTokenizer.TT_WORD && tokenizer.sval.equals("?")) { tokenizer.ttype = '?'; } } /** Skips all tokens before next end of line (EOL). */ public static void waitForEOL(StreamTokenizer tokenizer) throws IOException { // skip everything until EOL while (tokenizer.nextToken() != StreamTokenizer.TT_EOL) {} ; tokenizer.pushBack(); } /** Deletes the file. If it is a directory, deletes recursively. */ public static boolean delete(File file) { if (file.isDirectory()) { boolean success = true; File[] files = file.listFiles(); for (File child : files) { success &= delete(child); if (!success) { return false; } } boolean result = file.delete(); if (!result) { //LogService.getRoot().warning("Unable to delete file " + file); LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.Tools.deleting_file_error", file); return false; } return success; } else { boolean result = file.delete(); if (!result) { //LogService.getRoot().warning("Unable to delete file " + file); LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.Tools.deleting_file_error", file); } return result; } } public static void copy(File srcPath, File dstPath) throws IOException { if (srcPath.isDirectory()) { if (!dstPath.exists()) { boolean result = dstPath.mkdir(); if (!result) throw new IOException("Unable to create directoy: " + dstPath); } String[] files = srcPath.list(); for (String file : files) { copy(new File(srcPath, file), new File(dstPath, file)); } } else { if (srcPath.exists()) { FileChannel in = null; FileChannel out = null; try { in = new FileInputStream(srcPath).getChannel(); out = new FileOutputStream(dstPath).getChannel(); long size = in.size(); MappedByteBuffer buf = in.map(FileChannel.MapMode.READ_ONLY, 0, size); out.write(buf); } finally { if (in != null) in.close(); if (out != null) out.close(); } } } } /** Returns a whitespace with length indent. */ public static String indent(int indent) { StringBuffer s = new StringBuffer(); for (int i = 0; i < indent; i++) s.append(" "); return s.toString(); } public static String formatBytes(long numberOfBytes) { if (numberOfBytes > 1024 * 1024) { long mBytes = numberOfBytes / (1024 * 1024); if (mBytes >= 100) { return mBytes + " MB"; } else { long remainder = (numberOfBytes - mBytes * 1024 * 1024) / 1024; return mBytes + "." + Long.toString(remainder).charAt(0) + " MB"; } } else if (numberOfBytes > 1024) { return numberOfBytes / 1024 + " kB"; } else { return numberOfBytes + " bytes"; } } /** * Copies the contents read from the input stream to the output stream in the current thread. Both streams will be * closed, even in case of a failure. * * @param closeOutputStream */ public static void copyStreamSynchronously(InputStream in, OutputStream out, boolean closeOutputStream) throws IOException { byte[] buffer = new byte[1024 * 20]; try { // in = new BufferedInputStream(in); // out = new BufferedOutputStream(out); int length; while ((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } out.flush(); } finally { if (closeOutputStream && out != null) { try { out.close(); } catch (IOException ex) {} } if (in != null) { try { in.close(); } catch (IOException ex) {} } } } /** Esacapes quotes, newlines, and backslashes. */ public static String escape(String unescaped) { StringBuilder result = new StringBuilder(); for (char c : unescaped.toCharArray()) { switch (c) { case '"': result.append("\\\""); break; case '\\': result.append("\\\\"); break; case '\n': result.append("\\n"); break; default: result.append(c); break; } } return result.toString(); } /** * * Returns the column name of the the n'th column like excel names it. * * @param index * the index of the column * * @return */ public static String getExcelColumnName(int index) { if (index < 0) { return "error"; } final Character[] alphabet = new Character[] { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' }; // index -= 1; // adjust so it matches 0-indexed array rather than // 1-indexed column int quotient = index / 26; if (quotient > 0) { return getExcelColumnName(quotient - 1) + alphabet[index % 26].toString(); } else { return alphabet[index % 26].toString(); } } /** * Replace quote chars in-quote characters by escapeChar+quotingChar * * Example: seperatorPatern = ',' , quotingChar = '"' , escapeCahr = '\\' * * line = '"Charles says: "Some people never go crazy, What truly horrible lives they must live"", 1968, "US"' * return = '"Charles says: \"Some people never go crazy, What truly horrible lives they must live\"", "1968", "US"' */ public static String escapeQuoteCharsInQuotes(String line, Pattern separatorPattern, char quotingChar, char escapeChar, boolean showWarning) { // first remember quoteChar positions which should be escaped: char lastChar = '0'; boolean openedQuote = false; List<Integer> rememberQuotePosition = new LinkedList<Integer>(); for (int i = 0; i < line.length(); i++) { if (lastChar == quotingChar) { if (openedQuote) { boolean matches = Pattern.matches(separatorPattern.pattern() + ".*", line.substring(i)); if (matches) { openedQuote = false; } else { rememberQuotePosition.add(i - 1); } } else { openedQuote = true; } } lastChar = line.charAt(i); } if (openedQuote && lastChar == quotingChar) { openedQuote = false; } // print warning if (showWarning && !rememberQuotePosition.isEmpty()) { StringBuilder positions = new StringBuilder(); int j = 1; for (int i = 0; i < rememberQuotePosition.size(); i++) { if (j % 10 == 0) { positions.append("\n"); } positions.append(rememberQuotePosition.get(i)); if (i + 1 < rememberQuotePosition.size()) positions.append(", "); j++; } String lineBeginning = line; if (line.length() > 20) { lineBeginning = line.substring(0, 20); } String warning = "While reading the line starting with \n\n\t" + lineBeginning + " ...\n\n" + ",an unescaped quote character was substituted by an escaped quote at the position(s) " + positions.toString() + ". " + "In particular der character '" + Character.toString(lastChar) + "' was replaced by '" + Character.toString(escapeChar) + Character.toString(lastChar) + "."; LogService.getGlobal().logWarning(warning); } // then build new line: if (!rememberQuotePosition.isEmpty()) { String newLine = ""; int pos = rememberQuotePosition.remove(0); int i = 0; for (Character c : line.toCharArray()) { if (i == pos) { newLine += Character.toString(escapeChar) + c; if (!rememberQuotePosition.isEmpty()) { pos = rememberQuotePosition.remove(0); } } else { newLine += c; } i++; } line = newLine; } return line; } public static String unescape(String escaped) { StringBuilder result = new StringBuilder(); for (int index = 0; index < escaped.length(); index++) { char c = escaped.charAt(index); switch (c) { case '\\': if (index < escaped.length() - 1) { index++; char next = escaped.charAt(index); switch (next) { case 'n': result.append('\n'); break; case '\\': result.append('\\'); break; case '"': result.append('"'); break; // UGLY: Actually we should throw an exception when encountering undefined escape character default: result.append('\\').append(next); } } else { result.append('\\'); } break; default: result.append(c); break; } } return result.toString(); } /** As {@link #toString(Collection, String)} with ", ". */ public static String toString(Collection<?> collection) { return toString(collection, ", "); } /** * Returns a string containing the toString()-representation of the elements of collection, separated by the given * separator. */ public static String toString(Collection<?> collection, String separator) { boolean first = true; StringBuilder b = new StringBuilder(); for (Object o : collection) { if (first) { first = false; } else { b.append(separator); } b.append(o); } return b.toString(); } public static String toString(Object[] collection) { return toString(collection, ", "); } public static String toString(Object[] collection, String separator) { if (collection == null) { return null; } else { return toString(Arrays.asList(collection), separator); } } public static String formatSizeInBytes(long bytes) { long result = bytes; long rest = 0; int unit = 0; while (result > 1024) { rest = result % 1024; result /= 1024; unit++; if (unit >= Tools.MEMORY_UNITS.length - 1) break; } if (result < 10 && unit > 0) { return result + "." + 10 * rest / 1024 + " " + Tools.MEMORY_UNITS[unit]; } else { return result + " " + Tools.MEMORY_UNITS[unit]; } } /** * This method will return a byte array containing the raw data from the given url. * Please keep in mind that in order to load the data, the data will be stored in memory twice. */ public static byte[] readUrl(URL url) throws IOException { return readInputStream(new BufferedInputStream(WebServiceTools.openStreamFromURL(url))); } /** * This method will return a byte array containing the raw data from the given input stream. * The stream will be closed afterwards in any case. */ public static byte[] readInputStream(InputStream in) throws IOException { try { class Part { byte[] partData; int len; } LinkedList<Part> parts = new LinkedList<Part>(); int len = 1; while (len > 0) { byte[] data = new byte[1024]; len = in.read(data); if (len > 0) { Part part = new Part(); part.partData = data; part.len = len; parts.add(part); } } int length = 0; for (Part part : parts) length += part.len; byte[] result = new byte[length]; int pos = 0; for (Part part : parts) { System.arraycopy(part.partData, 0, result, pos, part.len); pos += part.len; } return result; } finally { if (in != null) { try { in.close(); } catch (IOException e) {} } } } /** Prefixes every occurrence */ public static String escape(String source, char escapeChar, char[] specialCharacters) { if (source == null) { return null; } StringBuilder b = new StringBuilder(); for (char c : source.toCharArray()) { if (c == escapeChar) { b.append(escapeChar); // escape escape character } else { for (char s : specialCharacters) { if (c == s) { // escape escape specials b.append(escapeChar); break; } } } b.append(c); } return b.toString(); } /** Splits the string at every split character unless escaped. */ public static List<String> unescape(String source, char escapeChar, char[] specialCharacters, char splitCharacter) { return unescape(source, escapeChar, specialCharacters, splitCharacter, -1); } /** * Splits the string at every split character unless escaped. If the split limit is not -1, at most so many tokens * will be returned. No more escaping is performed in the last token! */ public static List<String> unescape(String source, char escapeChar, char[] specialCharacters, char splitCharacter, int splitLimit) { List<String> result = new LinkedList<String>(); StringBuilder b = new StringBuilder(); // was the last character read an escape character? boolean readEscape = false; int indexCount = -1; for (char c : source.toCharArray()) { indexCount++; // in escape mode -> just write special character, throw exception if not special? if (readEscape) { boolean found = false; if (c == splitCharacter) { found = true; b.append(c); } else if (c == escapeChar) { found = true; b.append(c); } else { for (char s : specialCharacters) { if (s == c) { found = true; b.append(c); break; } } } if (!found) { throw new IllegalArgumentException("String '" + source + "' contains illegal escaped character '" + c + "'."); } // reset to regular mode readEscape = false; } else if (c == escapeChar) { // not in escape mode and read escape character -> go to escape mode readEscape = true; } else if (c == splitCharacter) { // not in escape mode and read split character -> split readEscape = false; result.add(b.toString()); if (splitLimit != -1) { if (result.size() == splitLimit - 1) { // Only one left? Add to result and terminate. result.add(source.substring(indexCount + 1)); return result; } } b = new StringBuilder(); } else { // not in escape mode and read other character -> just write it readEscape = false; b.append(c); } } result.add(b.toString()); return result; } /** In contrast to o1.equals(o2), this method also works with p1==null. */ public static boolean equals(Object o1, Object o2) { if (o1 != null) return o1.equals(o2); if (o1 == null && o2 == null) return true; return false; } /** * Iterates over a string an replaces all occurrences of charToMask by '%'. * Furthermore all appearing '%' will be escaped by '\' and all '\' will also be escaped by '\'. * To unmask the resulting string again use {@link #unmask(char, String)}.<br> * Examples (charToMask= '|'):<br> * hello|mandy => hello%mandy<br> * hel\lo|mandy => hel\\lo%mandy<br> * h%l\lo|mandy => h\%l\\lo%mandy<br> * @param charToMask the character that should be masked. May not be '%' or '\\' */ public static String mask(char charToMask, String unmasked) { if (charToMask == '%' || charToMask == '\\') { throw new IllegalArgumentException("Parameter charToMask " + charToMask + " is not allowed!"); } StringBuilder maskedStringBuilder = new StringBuilder(); char maskChar = '%'; char escapeChar = '\\'; // this means '\' for (char c : unmasked.toCharArray()) { if (c == charToMask) { maskedStringBuilder.append(maskChar); } else if (c == maskChar || c == escapeChar) { maskedStringBuilder.append(escapeChar); maskedStringBuilder.append(c); } else { maskedStringBuilder.append(c); } } return maskedStringBuilder.toString(); } /** * Unmaskes a masked string. * Examples (charToUnmask= '|'):<br> * hello%mandy => hello|mandy<br> * hel\\lo%mandy => hel\lo|mandy<br> * h\%l\\lo%mandy => h%l\lo|mandy<br> * * @param charToUnmask the char that should be unmasked */ public static String unmask(char charToUnmask, String masked) { if (charToUnmask == '%' || charToUnmask == '\\') { throw new IllegalArgumentException("Parameter charToMask " + charToUnmask + " is not allowed!"); } StringBuilder unmaskedStringBuilder = new StringBuilder(); char maskChar = '%'; char escapeChar = '\\'; boolean escapeCharFound = false; for (char c : masked.toCharArray()) { if (c == maskChar) { if (escapeCharFound) { unmaskedStringBuilder.append(maskChar); escapeCharFound = false; } else { unmaskedStringBuilder.append(charToUnmask); } } else if (c == escapeChar) { if (escapeCharFound) { unmaskedStringBuilder.append(escapeChar); escapeCharFound = false; } else { escapeCharFound = true; } } else { unmaskedStringBuilder.append(c); } } return unmaskedStringBuilder.toString(); } /** * This method tests if a file with the given file name could be stored on the current filesystem * the program is working on. For example, if working on Windows the string <code>foo:bar</code> would return <code>false</code> * (because <code>:</code> is forbidden). The string <code>foo_bar</code> would return <code>true</code>. * @param fileName if <code>null</code>, returns <code>false</code> * @return */ public static boolean canFileBeStoredOnCurrentFilesystem(String fileName) { if (fileName == null) { return false; } // check if file contains a ':', because then on windows machines this would lead to // C:\1:2.rmp becoming C:\1 without error - but writing operations will fail w/o error! // see http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4645046 String osName = System.getProperty("os.name"); boolean checkColon = osName == null ? true : osName.toLowerCase(Locale.ENGLISH).contains("windows") ? true : false; if (checkColon && fileName.contains(":")) { return false; } try { File file = new File(System.getProperty("java.io.tmpdir") + File.separator + fileName); if (!file.exists()) { file.createNewFile(); if (file.exists()) { file.delete(); return true; } else { return false; } } } catch (IOException e) { return false; } catch (SecurityException e) { return false; } catch (Exception e) { LogService.getRoot().log(Level.WARNING, "Failed to check filename for illegal characters.", e); return false; } return true; } }