blob: 8b19b69bd91d348c992bd94a2431a20299aeb458 [file] [log] [blame]
package net.sf.taverna.t2.workbench.helper;
import java.awt.Component;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.help.BadIDException;
import javax.help.HelpSet;
import javax.help.HelpSetException;
import javax.help.Map.ID;
import javax.help.TryMap;
import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import org.apache.log4j.Logger;
/**
* This class loads the {@link HelpSet} and also deals with the registration of
* ids and the decoding from a {@link Component} to the corresponding id. These
* two sets of functionality should possibly be separated.
*
* @author alanrw
*/
// TODO Convert to a bean
public final class HelpCollator {
private static Logger logger = Logger.getLogger(HelpCollator.class);
/**
* The HelpSet that is being used.
*/
private static HelpSet hs = null;
/**
* The mapping from components to ids. This is used because of problems with
* CSH throwing exceptions because it tried to use ids that were not in the
* map.
*/
private static Map<Component, String> idMap;
/**
* Indicates whether the HelpCollator has been initialized.
*/
private static boolean initialized = false;
/**
* A Pattern for normalizing the ids.
*/
private static Pattern nonAlphanumeric;
/**
* The emptyHelp is set if the HelpCollator was unable to read the
*/
private static boolean emptyHelp = true;
private static int TIMEOUT = 5000;
private static String externalHelpSetURL = "http://www.mygrid.org.uk/taverna/helpset/"
+ version() + "/helpset.hs";
// private static Profile profile = ProfileFactory.getInstance().getProfile();
private static String version() {
return "NO-VERSION";//profile.getVersion();
// TODO find a better way to find the version
}
/**
* Attempt to read the up-to-date HelpSet from the web
*/
private static void readExternalHelpSet() {
try {
URL url = new URL(externalHelpSetURL);
checkConnection(url);
hs = new HelpSet(null, url);
if (hs.getLocalMap() == null) {
hs = null;
logger.error("Helpset from " + externalHelpSetURL
+ " local map was null");
} else
logger.info("Read external help set from " + externalHelpSetURL);
} catch (MissingResourceException e) {
logger.error("No external HelpSet URL specified", e);
} catch (MalformedURLException e) {
logger.error("External HelpSet URL is malformed", e);
} catch (HelpSetException e) {
logger.error("External HelpSet could not be read", e);
} catch (IOException e) {
logger.error("IOException reading External HelpSet", e);
}
}
private static void checkConnection(URL url) throws IOException {
if (!url.getProtocol().startsWith("http"))
return;
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setReadTimeout(TIMEOUT);
connection.setConnectTimeout(TIMEOUT);
connection.setRequestMethod("HEAD");
connection.getInputStream().close();
connection.disconnect();
}
/**
* This methods creates a HelpSet based upon, in priority, the external
* HelpSet, then a newly created empty HelpSet.
*/
private static void initialize() {
if (initialized)
return;
readExternalHelpSet();
if (hs == null) {
hs = new HelpSet();
hs.setLocalMap(new TryMap());
} else {
logger.trace("EmptyHelp set to false");
emptyHelp = false;
}
idMap = new HashMap<>();
nonAlphanumeric = Pattern.compile("[^a-z0-9\\.]");
initialized = true;
}
/**
* Indicates if an empty HelpSet is being used
*
* @return
*/
public static boolean isEmptyHelp() {
return emptyHelp;
}
public static URL getURLFromID(String id) throws BadIDException,
MalformedURLException {
initialize();
logger.trace("Looking for id: " + id);
ID theId = ID.create(id, hs);
if (theId == null)
return null;
return hs.getCombinedMap().getURLFromID(theId);
}
/**
* Register a component under the specified id. The method checks that the
* id is known to the HelpSet's map.
*
* @param component
* @param id
*/
public static void registerComponent(Component component, String id) {
logger.trace("Attempting to register " + id);
initialize();
String normalizedId = normalizeString(id.toLowerCase());
if (idMap.containsKey(component)) {
logger.info("Registered " + normalizedId);
return;
}
/*
* If Workbench is started up while there is no network connection -
* hs.getLocalMap() is null for some reason
*/
if (hs != null && hs.getLocalMap() != null
&& hs.getLocalMap().isValidID(normalizedId, hs)) {
idMap.put(component, normalizedId);
logger.info("Registered " + normalizedId);
} else
logger.warn("Refused to register component as " + normalizedId
+ " not in map");
}
/**
* Register a component. Since no id is specified, the HelpCollator takes
* the canonical name of the component's class. This is useful when an
* explicit hierarchy-based approach has been taken.
*
* @param component
*/
public static void registerComponent(Component component) {
String canonicalName = component.getClass().getCanonicalName();
if (canonicalName != null)
registerComponent(component, canonicalName);
}
/**
* Register a component based upon its parent's class and a suffix
* indicating the component's purpose in the parent.
*
* @param component
* @param parent
* @param suffix
*/
public static void registerComponent(Component component, Object parent,
String suffix) {
String canonicalName = parent.getClass().getCanonicalName();
if (canonicalName != null)
registerComponent(component, canonicalName + "-" + suffix);
}
/**
* Try to find an id for the Component. This code should be re-written when
* we have more experience in how to couple the UI and HelpSets.
*
* @param c
* @return
*/
static String getHelpID(Component c) {
initialize();
boolean found = false;
String result = null;
if (c instanceof JTree) {
String idInTree = getHelpIDInTree((JTree) c);
if (idInTree != null) {
found = true;
result = idInTree;
}
}
Component working = c;
if (c != null)
logger.trace("Starting at a " + working.getClass());
while (!found && (working != null)) {
if (idMap.containsKey(working)) {
result = idMap.get(working);
found = true;
logger.trace("Found component id " + result);
} else {
String className = working.getClass().getCanonicalName();
if (hs.getLocalMap().isValidID(className, hs)) {
result = className;
found = true;
logger.trace("Found class name " + result);
}
}
if (!found) {
working = working.getParent();
if (working != null)
logger.trace("Moved up to a " + working.getClass());
}
}
return result;
}
/**
* Change the input String into an id that contains only alphanumeric
* characters or hyphens.
*
* @param input
* @return
*/
private static String normalizeString(String input) {
Matcher m = nonAlphanumeric.matcher(input);
return m.replaceAll("-");
}
/**
* If help is sought on part of a JTree, then this method attempts to find a
* node of the tree that can be mapped to an id. The possibilities are ad
* hoc and should be re-examined when more experience is gained.
*
* @param c
* @return
*/
private static String getHelpIDInTree(JTree c) {
initialize();
TreePath tp = c.getSelectionPath();
if (tp == null)
return null;
Object o = tp.getLastPathComponent();
if (o == null)
return null;
if (o instanceof DefaultMutableTreeNode) {
DefaultMutableTreeNode dmtn = (DefaultMutableTreeNode) o;
if (dmtn.getUserObject() != null)
o = dmtn.getUserObject();
}
String className = o.getClass().getCanonicalName();
logger.trace("Tree node as a string is " + o);
String possibility = normalizeString(o.toString().toLowerCase());
logger.trace("Normalized is " + possibility);
logger.trace("Tree node class name is " + className);
possibility = className + "-" + possibility;
logger.trace("Possibility is " + possibility);
String result;
if (hs.getLocalMap().isValidID(possibility, hs)) {
result = possibility;
logger.trace("Accepted tree node " + result);
} else if (hs.getLocalMap().isValidID(className, hs)) {
result = className;
logger.trace("Found tree node class name " + result);
} else {
result = null;
}
logger.debug("Tree node is a " + o.getClass());
return result;
}
}