Java tutorial
package io.github.tavernaextras.biocatalogue.model; /* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import java.awt.Color; import java.awt.Component; import java.awt.Font; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.ObjectStreamClass; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.net.URLDecoder; import java.net.URLEncoder; import java.util.Calendar; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.BorderFactory; import javax.swing.JLabel; import net.sf.taverna.raven.appconfig.ApplicationRuntime; import io.github.tavernaextras.biocatalogue.BioCataloguePerspective; import org.apache.commons.lang.StringEscapeUtils; import org.apache.log4j.Logger; /** * Class containing various reusable helper methods. * * @author Sergejs Aleksejevs */ public class Util { private static Logger logger = Logger.getLogger(Util.class); /** * Makes sure that one component (for example, a window) is centered horizontally and vertically * relatively to the other component. This is achieved by aligning centers of the two components. * * This method can be used even if the 'dependentComponent' is larger than the 'mainComponent'. In * this case it is probably only useful for centering windows against each other rather than * components inside a container. * * Method also makes sure that the dependent component will not be placed above the screen's upper * edge and to the left of the left edge. */ public static void centerComponentWithinAnother(Component mainComponent, Component dependentComponent) { int iMainComponentCenterX = (int) Math .round(mainComponent.getLocationOnScreen().getX() + (mainComponent.getWidth() / 2)); int iPosX = iMainComponentCenterX - (dependentComponent.getWidth() / 2); if (iPosX < 0) iPosX = 0; int iMainComponentCenterY = (int) Math .round(mainComponent.getLocationOnScreen().getY() + (mainComponent.getHeight() / 2)); int iPosY = iMainComponentCenterY - (dependentComponent.getHeight() / 2); if (iPosY < 0) iPosY = 0; dependentComponent.setLocation(iPosX, iPosY); } /** * The parameter is the class name to be processed; class name is likely to be in * the form <class_name>$<integer_value>, where the trailing part starting with * the $ sign indicates the anonymous inner class within the base class. This will * strip out that part of the class name to get the base class name. */ public static String getBaseClassName(String strClassName) { // strip out the class name part after the $ sign; return // the original value if the dollar sign wasn't found String strResult = strClassName; int iDollarIdx = strResult.indexOf("$"); if (iDollarIdx != -1) strResult = strResult.substring(0, iDollarIdx); return (strResult); } /** * Makes sure that the supplied string is no longer than provided length. */ public static String ensureStringLength(String str, int iLength) { if (str.length() > iLength) str = str.substring(0, iLength) + " (...)"; return (str); } /** * Makes sure that the supplied string doesn't have any lines (separated by HTML line break tag) longer * than specified; assumes that there are no line breaks in the source line. * * @param str The string to work with. * @param iLineLength Desired length of each line. * @param bIgnoreBrokenWords True if line breaks are to be inserted exactly each <code>iLineLength</code> * symbols (which will most likely cause broken words); false to insert line breaks * at the first space after <code>iLineLength</code> symbols since last line break. * @return New string with inserted HTML line breaks. */ public static String ensureLineLengthWithinString(String str, int iLineLength, boolean bIgnoreBrokenWords) { StringBuilder out = new StringBuilder(str); // keep inserting line breaks from end of the line till the beginning until all done int iLineBreakPosition = 0; while (iLineBreakPosition >= 0 && iLineBreakPosition < out.length()) { // insert line break either exactly at calculated position or iLineBreakPosition += iLineLength; iLineBreakPosition = (bIgnoreBrokenWords ? iLineBreakPosition : out.indexOf(" ", iLineBreakPosition)); if (iLineBreakPosition > 0 && iLineBreakPosition < out.length()) { out.insert(iLineBreakPosition, "<br>"); iLineBreakPosition += 4; // -- four is the length of "<br>" } } return (out.toString()); } /** * This is a convenience method for calling * {@link Util#pluraliseNoun(String, long, boolean)} * with <code>false</code> as a value for third parameter. */ public static String pluraliseNoun(String noun, long count) { return (pluraliseNoun(noun, count, false)); } /** * Performs naive pluralisation of the supplied noun. * * @param noun Noun in a singular form. * @param count Number of occurrences of the item, for which the noun is provided. * @param forceAppendingSByDefault <code>true</code> to make sure that "y" -> "ies" * substitution is <b>not made</b>, but instead "s" is appended * to unmodified root of the noun. * @return Pluralised <code>noun</code>: with appended -s or -y replaced with -ies. */ public static String pluraliseNoun(String noun, long count, boolean forceAppendingSByDefault) { if (count % 10 != 1 || count == 11) { if (!forceAppendingSByDefault && noun.endsWith("y")) { return (noun.substring(0, noun.length() - 1) + "ies"); // e.g. ENTRY -> ENTRIES } else { return (noun + "s"); // e.g. SHIP -> SHIPS } } else { // no need to pluralise - count is of the type 21, 31, etc.. return noun; } } /** * Calculates time difference between two {@link Calendar} instances. * * @param earlier The "earlier" date. * @param later The "later" date. * @param maxDifferenceMillis The maximum allowed time difference between the two * {@link Calendar} instances, in milliseconds. If the calculated * difference will exceed <code>maxDifferenceMillis</code>, * <code>null</code> will be returned. If this parameter has * a value <code>less or equal to zero</code>, any time difference * between the {@link Calendar} instances will be permitted. * @return String in the form "XX seconds|minutes|hours|days ago". Proper pluralisation will * be performed on the name of the time unit. <code>null</code> will be returned in * cases where one of the {@link Calendar} instances is <code>null</code>, time * difference between the provided instances is greater than <code>maxDifferenceMillis</code> * or <code>earlier</code> date is not really earlier than <code>later</code> one. */ public static String getAgoString(Calendar earlier, Calendar later, long maxDifferenceMillis) { // one of the dates is missing if (earlier == null || later == null) { return null; } if (earlier.before(later)) { long differenceMillis = later.getTimeInMillis() - earlier.getTimeInMillis(); if (maxDifferenceMillis <= 0 || (maxDifferenceMillis > 0 && differenceMillis <= maxDifferenceMillis)) { long result = 0; String unitName = ""; if (differenceMillis < 60 * 1000) { result = differenceMillis / 1000; unitName = "second"; } else if (differenceMillis < 60 * 60 * 1000) { result = differenceMillis / (60 * 1000); unitName = "minute"; } else if (differenceMillis < 24 * 60 * 60 * 1000) { result = differenceMillis / (60 * 60 * 1000); unitName = "hour"; } else { result = differenceMillis / (24 * 60 * 60 * 1000); unitName = "day"; } return (result + " " + Util.pluraliseNoun(unitName, result, true) + " ago"); } else { // the difference is too large - larger than the supplied threshold return null; } } else { // the "later" date is not really later than the "earlier" one return null; } } /** * Joins the set of tokens in the provided list into a single string. * This method is a shorthand for {@link Util#join(List, String, String, String)}. * * @param tokens List of strings to join. * @param separator Separator to insert between individual strings. * @return String of the form <code>[token1][separator][token2][separator]...[tokenN]</code> */ public static String join(List<String> tokens, String separator) { return (join(tokens, null, null, separator)); } /** * Joins the set of tokens in the provided list into a single string. * * Any empty strings or <code>null</code> entries in the <code>tokens</code> list * will be removed to achieve a better resulting joined string. * * @param tokens List of strings to join. * @param prefix String to prepend to each token. * @param suffix String to append to each token. * @param separator Separator to insert between individual strings. * @return String of the form <code>[prefix][token1][suffix][separator][prefix][token2][suffix][separator]...[prefix][tokenN][suffix]</code> * <br/><br/> * Example: call <code>join(["cat","sat","on","the","mat"], "[", "]", ", ")</code> results in the following output: * <code>"[cat], [sat], [on], [the], [mat]"</code> */ public static String join(List<String> tokens, String prefix, String suffix, String separator) { if (tokens == null) { // nothing to join return (null); } // list of strings is not empty, but some pre-processing is necessary // to remove any empty strings that may be there for (int i = tokens.size() - 1; i >= 0; i--) { if (tokens.get(i) == null || tokens.get(i).length() == 0) { tokens.remove(i); } } // now start the actual processing, but it may be the case that all strings // were empty and we now have an empty list if (tokens.isEmpty()) { // nothing to join - just return an empty string return (""); } else { // there are some tokens -- perform the joining String effectivePrefix = (prefix == null ? "" : prefix); String effectiveSuffix = (suffix == null ? "" : suffix); String effectiveSeparator = (separator == null ? "" : separator); StringBuilder result = new StringBuilder(); for (int i = 0; i < tokens.size(); i++) { result.append(effectivePrefix + tokens.get(i) + effectiveSuffix); result.append(i == tokens.size() - 1 ? "" : effectiveSeparator); } return (result.toString()); } } /** * Determines whether the plugin is running as a standalone JFrame or inside Taverna Workbench. * This is a naive test, based only on the fact that Taverna uses Raven ApplicationRuntime. */ public static boolean isRunningInTaverna() { try { // ApplicationRuntime class is defined within Taverna API. If this is available, // it should mean that the plugin runs within Taverna. ApplicationRuntime.getInstance(); return true; } catch (NoClassDefFoundError e) { return false; } } // === STRIPPING OUT HTML FROM STRINGS === public static String prepareStringForComponent(String source) { return "<html>" + StringEscapeUtils.escapeHtml(source) + "</html>"; } /* * === The following section is providing URL handling methods. === */ /** * See: {@link Util#appendStringBeforeParametersOfURL(String, String, boolean)} * * Assumes the last parameter as false, so that the URL encoding will be done. */ public static String appendStringBeforeParametersOfURL(String url, String strToAppend) { return (appendStringBeforeParametersOfURL(url, strToAppend, false)); } /** * Tiny helper to strip out all HTML tags. This will not leave any HTML tags * at all (so that the content can be displayed in DialogTextArea - and the * like - components. This helps to present HTML content inside JAVA easier. */ public static String stripAllHTML(String source) { // don't do anything if not string is provided if (source == null) return (""); // need to preserve at least all line breaks // (ending and starting paragraph also make a line break) source = source.replaceAll("</p>[\r\n]*<p>", "<br/>"); source = source.replaceAll("[\\s]+", " "); source = source.replaceAll("\\<br/?\\>", "\n"); source = source.replaceAll("\n ", "\n"); // strip all HTML source = source.replaceAll("\\<.*?\\>", ""); // any HTML tags source = source.replaceAll("&\\w{1,4};", ""); // this is for things like " ", ">", etc return (source); } /** * Appends given string at the end of the provided URL just before the list of parameters in the url. * * For example, appending ".xml" to URL "http://www.sandbox.biocatalogue.org/services?tag=blast" will * yield "http://www.sandbox.biocatalogue.org/services.xml?tag=blast". * * No duplication checking is made - if the URL is already ending (before parameters) with the value of * the string to append, that string will still be appended. * * @param url URL to append the string to. * @param strToAppend The string to append. The value will be url-encode before appending. * @return New string containing modified <code>url</code> with the <code>strToAppend</code> appended * before the "query string" of the URL. */ public static String appendStringBeforeParametersOfURL(String url, String strToAppend, boolean ignoreURLEncoding) { StringBuilder modifiedURL = new StringBuilder(url); int iPositionToInsertProvidedString = modifiedURL.indexOf("?"); if (iPositionToInsertProvidedString == -1) iPositionToInsertProvidedString = modifiedURL.length(); String stringToInsert = (ignoreURLEncoding ? strToAppend : Util.urlEncodeQuery(strToAppend)); modifiedURL.insert(iPositionToInsertProvidedString, stringToInsert); return (modifiedURL.toString()); } /** * This method takes a collection of name-value pairs in the form of a map. * It then adds all parameters from this map to the provided URL. * * If any parameter has the same name as was already present in the URL, the new value * will replace the existing one. * * The implementation of this method is not particularly efficient - it makes a * lot of overhead, but it's fine for non-intensive usage. * * @param url The URL to add a new parameter to. * @param Map of parameters to add to the URL. Keys and values of the map are the names and values for URL parameters. * @return New string which is the original <code>url</code> with all provided * parameters (in the form <code>name</code>=<code>value</code> pair) added to it. */ public static String appendAllURLParameters(String url, Map<String, String> parameterMap) { if (parameterMap == null || parameterMap.size() == 0) { // nothing to add, return the same URL return (url); } else { // just call an overloaded method which has the main logic // to do this action for each name-value pair in the map String out = url; for (Map.Entry<String, String> anotherParameter : parameterMap.entrySet()) { out = appendURLParameter(out, anotherParameter); } return (out); } } /** * This method takes a string representation of a URL and a name-value pair * of strings - in the form of Map.Entry instance to add to the URL as a new parameter. * * If parameter with the same name was already present in the URL, the new value * will replace the existing one. * * @param url The URL to add a new parameter to. * @param Map.Entry instance containing the name & the value for the parameter to add. * @return New string which is the original <code>url</code> with the desired * parameter (<code>name</code>=<code>value</code> pair) added to it. */ public static String appendURLParameter(String url, Map.Entry<String, String> parameter) { if (parameter == null) { // nothing to add, return the same URL return (url); } else { // just call an overloaded method which has the main logic to do this action return (appendURLParameter(url, parameter.getKey(), parameter.getValue())); } } /** * This method takes a string representation of a URL and a name-value pair * of strings to add to the URL as a new parameter. * * If parameter with the same name was already present in the URL, the new value * will replace the existing one. * * @param url The URL to add a new parameter to. * @param parameter String array with 2 elements - first element is the name * of the parameter to add, second - the value of the parameter. * @return New string which is the original <code>url</code> with the desired * parameter (<code>name</code>=<code>value</code> pair) added to it. */ public static String appendURLParameter(String url, String[] parameter) { if (parameter == null || parameter.length != 2) { // nothing to add, return the same URL return (url); } else { // just call an overloaded method which has the main logic to do this action return (appendURLParameter(url, parameter[0], parameter[1])); } } /** * This method takes a string representation of a URL and a name-value pair * of strings to add to the URL as a new parameter. * * If parameter with the same name was already present in the URL, the new value * will replace the existing one. * * @param url The URL to add a new parameter to. * @param name Name of the parameter to add. * @param value Value of the parameter to add. * @return New string which is the original <code>url</code> with the desired * parameter (<code>name</code>=<code>value</code> pair) added to it. */ public static String appendURLParameter(String url, String name, String value) { // if name of the parameter is not given, ignore this request // (makes sense to return the same URL as the input in this case - // as appending "nothing" wouldn't make it any different) if (name == null || name.length() == 0) { return (url); } // do everything in the protected block try { // parse the parameters of the given URL Map<String, String> urlParameters = extractURLParameters(url); if (urlParameters == null) { // there were no parameters in the original URL, create new map urlParameters = new HashMap<String, String>(); } // add the new parameter (this will replace a parameter with identical // name if it was already present in the map) urlParameters.put(name, value); // parse the URL string into the URL object to extract original query string URL theURL = new URL(url); String originalQueryString = theURL.getQuery(); // prepare the basis for the new URL to return String newUrl = null; if (originalQueryString != null) { // replace the original query string with empty space to // give way for appending the new query string newUrl = url.replace(originalQueryString, ""); } else { // there were no parameters in the original URL newUrl = url + "?"; } // append the new query string newUrl += constructURLQueryString(urlParameters); return (newUrl); } catch (Exception e) { logger.error("\nCouldn't append parameter ('" + name + "', '" + value + "') to the URL: " + url, e); return (null); } } /** * Extracts a value of a specific parameter from the supplied URL. * * @param url The URL to extract the parameter from. * @param parameterName Name of the URL parameter to extract the value for. * @return Value of the parameter with <code>parameterName</code> in the given <code>url</code>. * If the parameter with specified name is not found in the given <code>url</code>, * <code>null</code> is returned instead. */ public static String extractURLParameter(String url, String parameterName) { // both URL and the name of the required parameter must be supplied if (url == null || url.length() == 0 || parameterName == null || parameterName.length() == 0) return null; Map<String, String> urlParameters = extractURLParameters(url); if (urlParameters != null) { // the URL has some parameters; check what's the value of the desired parameter return (urlParameters.get(parameterName)); } else { // the URL doesn't contain any parameters return (null); } } /** * Extracts the query string from the provided URL. Parses this query string into * a map of parameters. * * All parameters (both names and values) will have special characters unescaped * (i.e. decoded from the standard url-encoding) and can be used directly. * * @param url The string representation of the URL to parse. */ public static Map<String, String> extractURLParameters(String url) { try { // extract the query part of the supplied URL URL theURL = new URL(url); String queryString = theURL.getQuery(); // prepare storage for output Map<String, String> parameterMap = null; // extract each name-value pair from query string (if any are specified in the URL) if (queryString != null && queryString.length() > 0) { // only initialise if there are some parameters parameterMap = new HashMap<String, String>(); for (String parameter : queryString.split("&")) { String[] nameValueArr = parameter.split("="); String name = nameValueArr[0]; // parameter name must always be present String value = (nameValueArr.length == 2 ? nameValueArr[1] : null); // could be that parameter value is not set (e.g. "q=") - insert null then // decode possible special characters name = urlDecodeQuery(name); if (value != null) value = urlDecodeQuery(value); parameterMap.put(name, value); } } return (parameterMap); } catch (MalformedURLException e) { // some problem occurred - report it; can't return any data in this case logger.error("Couldn't parse parameters of a URL: " + url + "; details below:", e); return null; } } /** * This method is the opposite for <code>extractURLParameters(String url)</code>. * It takes a map of parameters, performs URL-encoding of each and assembles them * into a query string. * * The query string then can be added to the <code>URL</code> object by using standard * Java API. * * @param urlParameters Map of parameters to use in query string construction. * @return URL-encoded query string. */ public static String constructURLQueryString(Map<String, String> urlParameters) { if (urlParameters != null) { StringBuilder queryString = new StringBuilder(); // iterate through all parameters and reconstruct the query string for (Map.Entry<String, String> parameter : urlParameters.entrySet()) { if (queryString.length() > 0) queryString.append("&"); // parameter separator queryString.append(urlEncodeQuery(parameter.getKey()) + "=" + urlEncodeQuery(parameter.getValue())); } return (queryString.toString()); } else { return (null); } } /** * Prepares the string to serve as a part of url query to the server. * @param query The string that needs URL encoding. * @return URL encoded string that can be inserted into the request URL. */ public static String urlEncodeQuery(String query) { // "fast exit" - if null supplied, just return an empty string; // this is because in the URLs we have "q=", rather than "q=null" - this will cater for such cases if (query == null) return (""); // encode the query String strRes = ""; try { strRes = URLEncoder.encode(query, "UTF-8"); } catch (UnsupportedEncodingException e) { // do nothing } return (strRes); } /** * Decodes a string which came as a part of of URL (e.g. a URL parameter). This converts * codes of escaped special characters back into those special characters. * * @param query The string that needs URL decoded. * @return Decoded string that will contain all the special characters. */ public static String urlDecodeQuery(String query) { String strRes = ""; try { strRes = URLDecoder.decode(query, "UTF-8"); } catch (UnsupportedEncodingException e) { // do nothing } return (strRes); } /** * This method is "clones" an object supplied as an argument. It uses * serialisation to achieve this (as opposed to manually implementing deep * copying of all referenced objects in the graph of the provided object). * This technique is used to make sure that the new object will be exact * replica, but totally independent of the original one. * * Note that this code works ~100 times slower than it would do if deep copying * was implemented. However, this will not be used in tight loops (and in loops * at all), so for one-off tasks it is fine. * * @author Dave Miller<br/> * Original version of the code in this method is taken from * <a href="http://www.javaworld.com/javaworld/javatips/jw-javatip76.html?page=2"> * http://www.javaworld.com/javaworld/javatips/jw-javatip76.html?page=2 * </a> [accessed on 25/Feb/2010]. * <br/><br/> * * @author Subhajit Dasgupta<br/> * Example of using an alternative class loader during object de-serialisation * was taken from * <a href="http://blogs.sun.com/adventures/entry/desrializing_objects_custom_class_loaders"> * http://blogs.sun.com/adventures/entry/desrializing_objects_custom_class_loaders * </a> [accessed on 29/Mar/2010]. * * @return Deep copy of the provided object. If deep copying doesn't succeed, * <code>null</code> is returned. */ public static Object deepCopy(Object objectToCopy) { // a "safety net" - a class loader of BioCatalogue perspective may be used in // de-serialisation process to make sure that all classes are recognised // (system class loader may not be able to "see" all BioCatalogue plugin's files, // but just those in Taverna's /lib folder) final ClassLoader[] customClassLoaders = new ClassLoader[] { BioCataloguePerspective.class.getClassLoader() }; try { ObjectOutputStream oos = null; ObjectInputStream ois = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); oos = new ObjectOutputStream(bos); // serialise and pass the object oos.writeObject(objectToCopy); oos.flush(); // read and return the new object ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); ois = new ObjectInputStream(bin) { /** * <code>resolveClass()</code> method is overridden to make use of * custom ClassLoader in the de-serialisation process. * <br/> * This is needed to make sure that the ClassLoader of the BioCatalogue * perspective is used as opposed to the system ClassLoader which will * only be able to see classes from Taverna's /lib folder. */ protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { String className = desc.getName(); try { // attempt to use default class loader return Class.forName(className); } catch (ClassNotFoundException exc) { // default class loader was unable to locate a required class - // attempt to use one of the provided class loaders for (ClassLoader cl : customClassLoaders) { try { return cl.loadClass(className); } catch (ClassNotFoundException e) { /* do nothing here - there may be other class loaders to try */ } } // none of the class loaders was able to recognise the currently // de-serialised class, so it's indeed an exception throw new ClassNotFoundException(className + " -- neither system, nor alternative class loaders were able to load this class"); } } }; return ois.readObject(); } catch (Exception e) { logger.error("Could not perform deep copy of " + objectToCopy.getClass() + " instance", e); } finally { oos.close(); ois.close(); } } catch (Exception e) { logger.error( "Could not close object streams during deep copy of " + objectToCopy.getClass() + " instance"); } // Error occurred - couldn't produce the deep copy... return null; } }