Java tutorial
/** This file is part of the Zeidon Java Object Engine (Zeidon JOE). Zeidon JOE is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. Zeidon JOE 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Zeidon JOE. If not, see <http://www.gnu.org/licenses/>. Copyright 2009-2015 QuinSoft */ package com.quinsoft.zeidon.utils; import java.awt.Dialog.ModalityType; import java.io.File; import java.io.InputStream; import java.io.StringWriter; import java.lang.management.ManagementFactory; import java.net.URL; import java.util.Enumeration; import java.util.regex.Pattern; import javax.management.InstanceNotFoundException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.swing.JDialog; import javax.swing.JOptionPane; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOCase; import org.apache.commons.io.IOUtils; import org.apache.commons.io.filefilter.NameFileFilter; import org.apache.commons.lang3.StringUtils; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import org.joda.time.format.DateTimeFormatterBuilder; import org.joda.time.format.DateTimeParser; import org.joda.time.format.DateTimePrinter; import org.joda.time.format.ISODateTimeFormat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import com.quinsoft.zeidon.ObjectEngine; import com.quinsoft.zeidon.Task; import com.quinsoft.zeidon.View; import com.quinsoft.zeidon.ZeidonException; /** * A collection of static utility methods. * */ public class JoeUtils { private static final Logger LOG = LoggerFactory.getLogger(JoeUtils.class); private final static Pattern PIPE_DELIMITER = Pattern.compile("\\|"); /** * Returns an input stream for a resource/filename. Logic will first attempt to find * a filename that matches the resourceName (search is case-insensitive). If a file * is found, the stream is for the file. * * If a file is not found, an attempt is made to find the resource on the classpath. * * @param task - If not null then the ZEIDON_HOME directory will be searched if all * other attempts fail. * @param resourceName - Name of resource to open. * @param classLoader - ClassLoader used to find resources. If null then the system * class loader is used. * * @return the InputStream or null if it wasn't found. */ public static ZeidonInputStream getInputStream(Task task, String resourceName, ClassLoader classLoader) { // If the resourceName contains a '|' then it is a list of resources. We'll return the first // one that is valid. String[] resourceList = PIPE_DELIMITER.split(resourceName); if (resourceList.length > 1) { for (String resource : resourceList) { ZeidonInputStream stream = getInputStream(task, resource.trim(), classLoader); if (stream != null) return stream; } // If we get here then none of the resources in the list were found so return null. return null; } try { // // Default is to assume resourceName is a filename. // File file = getFile(resourceName); if (file.exists()) return ZeidonInputStream.create(file); if (classLoader == null) { if (task != null) classLoader = task.getClass().getClassLoader(); if (classLoader == null) classLoader = new JoeUtils().getClass().getClassLoader(); if (classLoader == null) classLoader = resourceName.getClass().getClassLoader(); if (classLoader == null) classLoader = ClassLoader.getSystemClassLoader(); } // // Try loading as a resource (e.g. from a .jar). // int count = 0; ZeidonInputStream stream = null; URL prevUrl = null; String md5hash = null; for (Enumeration<URL> url = classLoader.getResources(resourceName); url.hasMoreElements();) { URL element = url.nextElement(); if (task != null) task.log().debug("Found resource at " + element); else LOG.debug("--Found resource at " + element); count++; if (count > 1) { // We'll allow duplicate resources if they have the same MD5 hash. if (md5hash == null) md5hash = computeHash(prevUrl); if (!md5hash.equals(computeHash(element))) throw new ZeidonException("Found multiple different resources that match resourceName %s", resourceName); if (task != null) task.log().warn( "Multiple, identical resources found of %s. This usually means your classpath has duplicates", resourceName); else LOG.warn("Multiple, identical resources found of " + resourceName + " This usually means your classpath has duplicates"); } stream = ZeidonInputStream.create(element); prevUrl = element; } if (stream != null) return stream; // // Try loading as a lower-case resource name. // String name = FilenameUtils.getName(resourceName); if (StringUtils.isBlank(name)) return null; String path = FilenameUtils.getPath(resourceName); String newName; if (StringUtils.isBlank(path)) newName = name.toLowerCase(); else newName = path + name.toLowerCase(); stream = ZeidonInputStream.create(classLoader, newName); if (stream != null) return stream; // If task is null then we don't know anything else to try. if (task == null) return null; // // Try loading with ZEIDON_HOME prefix. // newName = task.getObjectEngine().getHomeDirectory() + "/" + resourceName; file = getFile(newName); if (file.exists()) return ZeidonInputStream.create(file); return null; } catch (Exception e) { throw ZeidonException.wrapException(e).prependFilename(resourceName); } } private static String computeHash(URL url) throws Exception { InputStream stream = url.openStream(); try { return DigestUtils.md5Hex(stream); } finally { IOUtils.closeQuietly(stream); } } public static ZeidonInputStream getInputStream(String resourceName, ClassLoader classLoader) { return getInputStream(null, resourceName, classLoader); } public static ZeidonInputStream getInputStream(Task task, String resourceName) { return getInputStream(task, resourceName, null); } public static ZeidonInputStream getInputStream(String resourceName) { return getInputStream(null, resourceName, null); } /** * Returns java.io.File for a file matching path using CASE-INSENSITIVE file * matching. This makes it easier for us to run on Windows. * * @param path * @return */ public static File getFile(String path) { try { File file = new File(path); if (file.exists()) return file; // Try searching for the file without regard to case. String parentPath = file.getParent(); if (StringUtils.isBlank(parentPath)) return file; // No parent path so just return. File dir = new File(parentPath); NameFileFilter filter = new NameFileFilter(file.getName(), IOCase.INSENSITIVE); String[] files = dir.list(filter); if (files == null) return file; // We probably didn't find a directory so just return the unknown file. if (files.length == 1) return new File(parentPath + File.separator + files[0]); if (files.length > 1) throw new ZeidonException("Found multiple matching entries for %s", path); // We didn't find an exact match so just return. return file; } catch (Exception e) { throw ZeidonException.wrapException(e).prependFilename(path); } } public static String getEnvProperty(String name) { return getEnvProperty(name, false); } public static String getEnvProperty(String name, boolean required) { String value = System.getProperty(name); if (value == null) value = System.getenv(name); if (required && value == null) throw new ZeidonException("Required environment variable '%s' is null", name); return value; } /** * Takes the filename and replaces control characters. * * . - If filename starts with '.' then it is prefixed with the CWD. * %var% - Replaces %var% with the environment variable value of 'var'. * * @param filename * @return */ public static String parseFilename(String filename) { String returnString = filename; String[] arr = filename.split("%"); // There must be an even-numbered number of % signs. If there's not then // there will be an even-numbered number of strings in the array. if (arr.length % 2 == 0) throw new ZeidonException("Filename '%s' has an un-paired percent sign", filename); if (arr.length > 0) { returnString = ""; for (int i = 0; i < arr.length; i++) returnString += (i % 2) == 0 ? arr[i] : getEnvProperty(arr[i], true); } if (returnString.startsWith("..")) returnString = System.getProperty("user.dir") + File.separator + returnString; else if (returnString.startsWith(".")) returnString = System.getProperty("user.dir") + File.separator + returnString.substring(1); return returnString; } /** * A simple wrapper around String.format. If 'strings' is an empty list then we return 'format', otherwise * we call String.format using 'format' and 'strings'. * * @param format * @param strings * @return */ public static String format(String format, Object... strings) { if (strings.length == 0) return format; //TODO: String.format() creates a new Formatter each time. Is there a faster // formatter available or should we create a ThreadLocal cache? return String.format(format, strings); } /** * Returns true if str consists only of digits. For performance sake we don't check * for null or empty strings and assume the caller has already done it. This is faster * than using regex. * * @param string Non-null string. * @return False if the string contains any non-digit. */ public static boolean onlyDigits(String str) { int length = str.length(); for (int i = 0; i < length; i++) { char c = str.charAt(i); if (c <= '/' || c >= ':') return false; } return true; } /** * Returns a DateTimeFormatter that can parse and print dates in the format of * editString. There can be multiple edit strings which are separated by a "|" * character. If there are more than one then the first one is considered to * be the "print" format. * * NOTE: Automatically adds ISO 8601 parser: 2016-11-29T05:41:02+00:00 * * @param editString * @return */ public static DateTimeFormatter createDateFormatterFromEditString(String editString) { String[] strings = editString.split("\\|"); DateTimeParser list[] = new DateTimeParser[strings.length + 1]; DateTimePrinter printer = null; for (int i = 0; i < strings.length; i++) { try { DateTimeFormatter f = DateTimeFormat.forPattern(strings[i]); if (printer == null) printer = f.getPrinter(); list[i] = f.getParser(); } catch (Exception e) { throw ZeidonException.wrapException(e).appendMessage("Format string = %s", strings[i]); } } list[strings.length] = ISODateTimeFormat.dateTimeParser().getParser(); DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder(); builder.append(printer, list); DateTimeFormatter formatter = builder.toFormatter(); return formatter; } /** * Converts a string in the standard Zeidon internal format (yyyyMMddHHmmssSSS) into a * DateTime. * * NOTE: For sake of speed this method does no validation on the input string. * * @param str * @return */ public static DateTime parseStandardDateString(String str) { assert str.length() >= ObjectEngine.INTERNAL_DATE_STRING_FORMAT.length(); String t; t = str.substring(0, 4); int year = Integer.parseInt(t); t = str.substring(4, 6); int month = Integer.parseInt(t); t = str.substring(6, 8); int day = Integer.parseInt(t); if (str.length() == ObjectEngine.INTERNAL_DATE_STRING_FORMAT.length()) return new DateTime(year, month, day, 0, 0, 0, 0); int hour = 0, minute = 0, seconds = 0, millis = 0; switch (str.length()) { case 15: case 16: case 17: case 18: t = str.substring(14); millis = Integer.parseInt(t); case 13: case 14: t = str.substring(12, 14); seconds = Integer.parseInt(t); case 11: case 12: t = str.substring(10, 12); minute = Integer.parseInt(t); case 9: case 10: t = str.substring(8, 10); hour = Integer.parseInt(t); break; // Yes, break here. default: throw new ZeidonException("Invalid length for date string"); } return new DateTime(year, month, day, hour, minute, seconds, millis); } /** * A map to convert Zeidon date formats to Java date formats. */ static final ImmutableMap<String, String> DATE_FORMAT_CONVERTER = new ImmutableMap.Builder<String, String>() .put("M", "M").build(); /** * Converts the old Zeidon date format specification to the Java specification so * that we can use Java conversion. Zeidon specification is: * // Characters Replaced By // // M/m A one or two digit number that represents the // month. // // MM/mm A two digit number that represents the month. // // MON A three character abbreviation for the month. // // mmm Month's three letter abbreviation. This symbol // is case sensative; You can specify capitalization // in the formatted value as follows: // mmm jan // Mmm Jan // MMM JAN // // mmmm Month's full name. This symbol is case sensative; // you can specify capitalization in the formatted // value as follows: // // mmmm january // Mmmm January // MMMM JANUARY // // D/d A one or two digit number that represents the day // of the month. // // DD/dd A two digit number that represents the day // of the month. // ddd Day of week, three letter abbreviation. This // symbol is case sensative; You can specify // capitalization is the formatted value as follows: // ddd sun // Ddd Sun // DDD SUN // // dddd Day of week, full name. This symbol is case // sensative; You can specify capitalization is the // formatted value as follows: // dddd sunday // Dddd Sunday // DDDD SUNDAY // // jjj Julian day. // // YY The last two digits of the year. // // YYYY The four digits of the year. // // HH A two digit number that represents hours based // on a 24 hour clock, unless AM or PM is present // in the Edit String, in which case the base is a // 12 hour clock // // MI A two digit number that represents minutes. // // SS A two digit number that represents seconds. // // AM or PM Two characters that represents AM or PM. Either // of these will cause any HH in the Edit String // to be based on a 12 hour clock. // // 9 A number with 1 to 3 digits // 99 that represents // 999 fractions of a second. // // The following Edit String characters are moved from the Edit // String to the Return String as is: // // ( left paren // ) right paren // : colon // , comma // - dash // / slash // . period // space // // The following delimiters may be used to put string constants in // the returned string. // " quote // ' Apostrophe * @param zeidonFormat * @return */ public static String convertZeidonDateFormatToJavaFormat(String zeidonFormat) { // We'll use a map to quickly convert common usage. String javaFormat = DATE_FORMAT_CONVERTER.get(zeidonFormat); if (javaFormat != null) return javaFormat; // TODO: Implement full conversion. throw new ZeidonException( "Unsupported Zeidon date string. You may need to update JoeUtils.DATE_FORMAT_CONVERTER."); } public static final void sysMessageBox(String msgTitle, String msgText) { //JOptionPane.showMessageDialog( null, msgText, msgTitle, JOptionPane.PLAIN_MESSAGE ); JOptionPane pane = new JOptionPane(msgText, JOptionPane.INFORMATION_MESSAGE); JDialog dialog = pane.createDialog(msgTitle); dialog.setModalityType(ModalityType.MODELESS); dialog.setAlwaysOnTop(true); dialog.setVisible(true); } /** * Write an OI to a string in portable file format. * * @param view * @return */ public static String serializeView(View view) { StringWriter writer = new StringWriter(); view.writeOi(writer); return writer.toString(); } public static void RegisterJmxBean(Object bean, String beanName, String jmxAppName) { try { MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); if (!StringUtils.isBlank(jmxAppName)) beanName += ",app=" + jmxAppName; ObjectName name = new ObjectName(beanName); // Make sure the bean doesn't already exist. If it does, unregister it. try { mbs.getMBeanInfo(name); // Throws InstanceNotFoundException if not found. mbs.unregisterMBean(name); // Unregister a bean if it exists. } catch (InstanceNotFoundException e) { // If we get here then the mbean isn't currently registered. This is valid // so we'll ignore it. } mbs.registerMBean(bean, name); } catch (Exception e) { throw ZeidonException.wrapException(e).appendMessage("Bean Name = %s, app=%s", beanName, jmxAppName) .appendMessage("Bean class = %s", bean.getClass().getName()); } } }