package net.sf.taverna.biocatalogue.model; | |
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 net.sf.taverna.t2.ui.perspectives.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; | |
} | |
} |