[MOBY-guts] biomoby commit

Paul Gordon gordonp at dev.open-bio.org
Mon Mar 29 19:54:36 UTC 2010


gordonp
Mon Mar 29 15:54:36 EDT 2010
Update of /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui
In directory dev.open-bio.org:/tmp/cvs-serv10568/src/main/ca/ucalgary/seahawk/gui

Modified Files:
	MobyContentPane.java 
Log Message:
Filter update
moby-live/Java/src/main/ca/ucalgary/seahawk/gui MobyContentPane.java,1.22,1.23
===================================================================
RCS file: /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java,v
retrieving revision 1.22
retrieving revision 1.23
diff -u -r1.22 -r1.23
--- /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java	2010/03/17 20:21:55	1.22
+++ /home/repository/moby/moby-live/Java/src/main/ca/ucalgary/seahawk/gui/MobyContentPane.java	2010/03/29 19:54:36	1.23
@@ -12,6 +12,7 @@
 import org.biomoby.client.MobyRequest;
 import org.biomoby.client.MobyRequestEvent;
 import org.biomoby.client.MobyRequestEventHandler;
+import org.biomoby.registry.meta.Registry;
 import org.biomoby.shared.*;
 import org.biomoby.shared.data.*;
 import org.biomoby.shared.parser.MobyTags;
@@ -23,13 +24,28 @@
 import javax.xml.parsers.*;
 import javax.xml.transform.*;
 import javax.xml.transform.stream.*;
-
 import javax.xml.xpath.*;
 
 import javax.swing.*;
 import javax.swing.event.*;
-
-import java.awt.*;
+import javax.swing.text.AttributeSet;
+import javax.swing.text.SimpleAttributeSet;
+import javax.swing.text.DefaultHighlighter;
+import javax.swing.text.Highlighter;
+import javax.swing.text.Style;
+import javax.swing.text.StyleConstants;
+import javax.swing.text.StyledDocument;
+import javax.swing.text.html.HTML;
+import javax.swing.text.html.HTMLDocument;
+
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Cursor;
+import java.awt.Desktop;
+import java.awt.Dimension;
+import java.awt.Graphics;
+import java.awt.Point;
+import java.awt.Toolkit;
 import java.awt.datatransfer.*;
 import java.awt.print.*;
 import java.awt.event.*;
@@ -37,6 +53,7 @@
 import java.net.URL;
 import java.net.URLConnection;
 import java.util.*;
+import java.util.regex.*;
 
 /**
  * Text area contained in a Seahawk GUI tab.  Displays HTML, RTF, text etc. using a JEditorPane.
@@ -83,7 +100,16 @@
     private int lastClickY = 1;
     private boolean hasFailed = false;
     private boolean isContentsXML = false;
-    private DataRecorder dataRecorder;
+    private DataFlowRecorder dataRecorder;
+    private List<String> xPtrsReferencedInNextService;
+    private Map<String,String> filteredData; //Map<xptrOfFilteredData,>
+    private boolean filterChanged;
+    private Document currentDoc; //the doc DOM being actively filtered
+    private NodeList currentSelectedData = null;
+    private String currentSelectionXPath = null;
+    private List<String> filterableNodes = null;  // List<xptr>
+    private List<AttributeSet> origStyles = null;
+    private boolean firstDocRendering = true;
 
     // Next two items used for service wrapping
     private JMenuItem createServicePopupItem;
@@ -114,8 +140,22 @@
     private final static String wsServletPath = "/SOAPServlet";
     private static Acme.Serve.Serve servletContainer;
     private static Map<String,MobyContentGUI> id2GuiMap;
+    private static DocumentBuilder docBuilder;
+    private static XPathFactory xPathFactory;
+
+    static{
+	xPathFactory = XPathFactory.newInstance();
+
+	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+	dbf.setNamespaceAware(true);
+	try{
+	    docBuilder = dbf.newDocumentBuilder();
+	} catch(Exception e){
+	    logger.warn("Cannot get XML parser from configuration", e);
+	}
+    }
 
-    public MobyContentPane(MobyContentGUI cGUI, MobyServicesGUI sGUI, JTabbedPane parentComponent, DataRecorder recorder, JLabel statusBar){
+    public MobyContentPane(MobyContentGUI cGUI, MobyServicesGUI sGUI, JTabbedPane parentComponent, DataFlowRecorder recorder, JLabel statusBar){
 	tabbedPane = parentComponent;
 	status = statusBar;
 	contentGUI = cGUI;
@@ -158,6 +198,8 @@
 	historyTabLabels = new HashMap<URL,String>();
 	filterHistory = new HashMap<URL,FilterSearch>();
 	deletedFilters = new HashMap<URL,FilterSearch>();
+	xPtrsReferencedInNextService = new Vector<String>();
+	filteredData = new HashMap<String,String>();
 
 	ClassLoader cl = getClass().getClassLoader();
 	if(cl == null){
@@ -198,7 +240,7 @@
 	return tabbedPane;
     }
 
-    public DataRecorder getDataRecorder(){
+    public DataFlowRecorder getDataFlowRecorder(){
 	return dataRecorder;
     }
 
@@ -391,7 +433,7 @@
 		    }
 		}));
 	    new Thread(){public void run(){srv.serve();}}.start();
-
+	    
 	    PBERecorder recorder = new PBERecorder();
 	    recorder.setGUIMap(id2GuiMap);
 	    servlet.setRecorder(recorder);
@@ -400,6 +442,9 @@
 	    srv.addServlet(servletPath, servlet); // our deployment
 	}
 	else if(contentGUI.getServletContainer().getServlet(servletPath) == null){
+	    PBERecorder recorder = new PBERecorder();
+	    recorder.setGUIMap(id2GuiMap);
+	    servlet.setRecorder(recorder);
 	    contentGUI.getServletContainer().addServlet(servletPath, servlet); 
 	}
 
@@ -694,12 +739,21 @@
 	}
 	// If this page has a filter associated with it before reload it
 	try{
+	    currentDoc = null;  // get rid of previous in-memory doc use for interactive filtering, if any
+	    currentSelectedData = null;
+	    currentSelectionXPath = null;
+	    filterableNodes = null;
+	    origStyles = null;
+	    firstDocRendering = true;
+	    editorPane.getHighlighter().removeAllHighlights(); 
+
 	    if(filterHistory.containsKey(url)){
 		setFilterVisible(true);
 	    }
 	    else{
 		setFilterVisible(false);
 	    }
+	    filterChanged = false;
 	} catch(Exception e){
 	    logger.warn("Could not enable filter history for this document: "+e.getMessage(), e);
 	}
@@ -729,17 +783,69 @@
     }
     
     public boolean canGoForward(){
-	return historyIndex < history.size()-1;
+	return historyIndex < history.size()-1 && isFilterForwardConsistent();
+    }
+
+    /**
+     * If the filter condition has changed since the next service was originally called,
+     * only allow navigation to that document (service results) again if the new filter allows 
+     * the data used in the original call to pass (for logical dataflow consistency).
+     */
+    public boolean isFilterForwardConsistent(){
+	// If there is no forward doc, it is consistent I suppose
+	if(historyIndex == history.size()-1){
+	    contentGUI.setForwardButtonToolTip(null);
+	    return true;
+	}
+	// If no filter on current doc, we must be okay to go forward
+	if(getFilter() == null){
+	    contentGUI.setForwardButtonToolTip(null);
+	    return true;
+	}
+
+	// Get the data from this doc used as the input to the forward document
+        if(xPtrsReferencedInNextService == null){
+	    try{
+		xPtrsReferencedInNextService = DataUtils.getInputXPtrs(history.elementAt(historyIndex+1), history.elementAt(historyIndex));
+	    } catch(Exception e){
+		logger.error("Cannot get the provenance data from "+history.elementAt(historyIndex+1), e);
+	    }
+	    
+	}
+	// See if all the particular data from this doc that was used in the next service is still avaialble after
+	// applying the current version of the filter
+	for(String usedxPtr: xPtrsReferencedInNextService){
+	    for(String filteredXPtr: filteredData.keySet()){
+		if(usedxPtr.equals(filteredXPtr) ||
+		   usedxPtr.startsWith(filteredXPtr+"/")){
+		    // the data used in the next service is filtered in the current view, not consistent to allow forward button
+		    // give the person a reason the forward is disabled
+		    contentGUI.setForwardButtonToolTip("<html>Cannot go forward to next doc, the corresponding service<br>"+
+						       "depended on input data that is now filtered.  Reset the data filter<br>"+
+						       "to be able to go forward again</html>");
+		    return false;  
+		}
+	    }
+	}
+
+	// If they differ, check that the forward doc's input src is not filtered out currently
+	contentGUI.setForwardButtonToolTip(null);
+	return true;
     }
 
     public void goForward(){
 	if(canGoForward()){
+	    if(filterChanged){
+		DataUtils.updateInputFilter(history.elementAt(historyIndex+1), history.elementAt(historyIndex), getFilter());
+	    }
 	    gotoURL(history.elementAt(++historyIndex), false);
+	    xPtrsReferencedInNextService = null;
 	}
     }
 
     public void goBackward(){
 	if(canGoBack()){
+	    xPtrsReferencedInNextService = null;
 	    gotoURL(history.elementAt(--historyIndex), false);
 	}
     }
@@ -889,7 +995,7 @@
 	}
 
 	try {
-            URL outputURL = dataRecorder.saveOutputData(mre);
+            URL outputURL = DataUtils.saveOutputData(mre);
             gotoURL(outputURL, true);
 	} catch (Exception e) {
 	    failed("Could not write a local file");
@@ -1009,8 +1115,13 @@
 		logger.warn("Failed: could not get hyperlink host: " + targetURL);
 	    }
 	    else if(isMobyURL(targetURL)){
-		status.setText("Retrieving service list for selected data");
-		showMobyOptions(targetURL);
+		if(isLinkEnabled(targetURL)){
+		    status.setText("Retrieving service list for selected data");
+		    showMobyOptions(targetURL);
+		}
+		else{
+		    status.setText("Change the data filter to re-enable this link");
+		}
 	    }
 	    // External, hopefully HTML link
 	    else{
@@ -1023,7 +1134,12 @@
 	    overHyperlink = true;
 	    lastHyperlinkHovered = targetURL;
 	    if(isMobyURL(targetURL)){
-		status.setText("Click to discover more Moby services, or drag onto a Web form");
+		if(isLinkEnabled(targetURL)){
+		    status.setText("Click to discover more Moby services, or drag onto a Web form");
+		}
+		else{
+		    status.setText("This link is current disabled because of the data filter.");
+		}
 	    }
 	    else if(targetURL == null){
 		String desc = he.getDescription();
@@ -1042,6 +1158,19 @@
 	}
     }
 
+    private boolean isLinkEnabled(URL url){
+	String ref = url.getRef();
+	if(ref == null || ref.length() == 0){
+	    return true;
+	}
+	for(String disabledXPtr: filteredData.keySet()){
+	    if(ref.equals(disabledXPtr) || ref.startsWith(disabledXPtr+"/")){
+		return false;
+	    }
+	}
+	return true;
+    }
+
     public MobyDataInstance getDraggedData(){
 	return urlToMobyData(lastHyperlinkDragged);
     }
@@ -1054,9 +1183,8 @@
 	String docFragID = targetURL.getRef();  // We store the xpath in the anchor part of the URL
 
 	// Complex case, load the doc fragment from the MOBY XML source file
-	if(docFragID != null && docFragID.length() > 0 && contentGUI.getDocumentBuilder() != null &&
-	   !targetURL.getHost().equals("moby")){
-	    return loadMobyDataFromXPointer(targetURL);
+	if(docFragID != null && docFragID.length() > 0 && !targetURL.getHost().equals("moby")){
+	    return DataUtils.loadMobyDataFromXPointer(targetURL, filterHistory.get(getCurrentURL()));
 	}
 
 	// Simple case, build the object using the values encoded in the URL itself
@@ -1208,164 +1336,334 @@
 	return mobyData;
     }
     
-    protected MobyDataInstance loadMobyDataFromXPointer(URL targetURL){
-	// Are we dealing with a simple object or a complex one?
-	MobyDataInstance mobyData = null;
-	
-	// Find the DOM fragment corresponding to the MOBY ID anchor specified
-	// in the link URL by using an XPath statement on the source MOBY doc
-	//System.err.println("Retrieving complex object from XPointer " + targetURL);
-	
-	// A child Xpointer is of the form /1/2/1/1, specifying the DOM child 
-	// descent path from the root node to get to the target node.  The 
-	// equivalent XPath is /*[1]/*[2]/*[1]/*[1]
-	String childXPath = targetURL.getRef().replaceAll("/(\\d+)", "/*[$1]");
-	URL currentURL = null;
-	try{currentURL = new URL(targetURL.toString().replaceAll("#"+targetURL.getRef(), ""));}
-	catch(Exception e){
-	    logger.error("Couldn't extract referenceless URL from " + targetURL);
-	}
 
-	// Build the DOM
-	Element mobyDOMRoot = null;
-	Document domDoc = null;
-	try{
-	    domDoc = contentGUI.getDocumentBuilder().parse(targetURL.openStream());
-	} catch(org.xml.sax.SAXException saxe){
-	    logger.error("The document defining the MOBY data " +
-			       "could not be parsed: " + saxe);
-	    return null;
-	} catch(java.io.IOException ioe){
-	    logger.error("The document defining the MOBY data " +
-			       " could not be read (from " + targetURL + "): " + ioe);
+    /**
+     * Gets the filter criteria currently being applied to the document being viewed.
+     *
+     * @return null unless there is a currently a non-blank filter criteria
+     */
+    public FilterSearch getFilter(){
+	if(getCurrentURL() == null){
 	    return null;
 	}
-	
-	if(domDoc != null){
-	    mobyDOMRoot = domDoc.getDocumentElement();
+	FilterSearch fs = filterHistory.get(getCurrentURL());
+	if(fs != null && fs.getFilterRegex().length() > 0){
+	    return fs;
 	}
-	if(mobyDOMRoot == null){
-	    logger.warn("Error: Could not get MOBY document as DOM from source URL " 
-			       + targetURL + " (empty or malformed document?)");
-	    return null;
+	return null;
+    }
+
+    /**
+     * Determine the parts of the XML document matching the FilterSearch criteria, and filter the view accordingly.
+     */
+    public void applyFilter(boolean apply){
+	if(!filterChanged){
+	    filterChanged = true;
 	}
-	
-	// Build and run the XPath statement
-	Element mobyObject = null;
-	NodeList idSearchResult = null;
-	Object xobject = null;
-	try{
-	    xobject = XPathFactory.newInstance().newXPath().evaluate(childXPath, 
-								     new InputSource(targetURL.openStream()), 
-								     XPathConstants.NODESET);
-						
-	    // Check the results
-	    if(xobject != null){
-		idSearchResult = (NodeList) xobject;
+	filteredData.clear();
+
+	if(currentDoc == null){  // parse the doc into memory if not already there
+	    try{
+		currentDoc = docBuilder.parse(getCurrentURL().openStream());
+	    } catch(Exception e){
+		logger.error("Could not parse Moby document " + getCurrentURL(), e);
+		return;
+	    }
+	}
+	// Filter out any moby jobs, parameters, collection members or HAS members that aren't in the matchingNodes
+	if(filterableNodes == null){
+	    filterableNodes = new Vector<String>();
+	    NodeList nodes = currentDoc.getElementsByTagNameNS(MobyTags.MOBY_XML_NS, MobyTags.MOBYDATA);
+	    for(int i = 0; i < nodes.getLength(); i++){
+		filterableNodes.add(getXPtr(nodes.item(i)));
+		addHASXPtrs(filterableNodes, (Element) nodes.item(i));
+	    }
+		
+	    // Collections
+	    nodes = currentDoc.getElementsByTagNameNS(MobyTags.MOBY_XML_NS, MobyTags.COLLECTION);
+	    for(int i = 0; i < nodes.getLength(); i++){
+		String collectionXPtr = getXPtr(nodes.item(i));
+		NodeList collectionMembers = nodes.item(i).getChildNodes();
+		int nonElementCnt = 0;
+		for(int j = 0; j < collectionMembers.getLength(); j++){		    
+		    if(!(collectionMembers.item(j) instanceof Element)){
+			nonElementCnt++;
+			continue;
+		    }
+		    filterableNodes.add(collectionXPtr+"/"+(j-nonElementCnt+1)+"/1");
+		}
+	    }
+	}
+
+	if(!apply || !filterHistory.containsKey(getCurrentURL()) ||
+	   (filterHistory.containsKey(getCurrentURL()) && 
+	    filterHistory.get(getCurrentURL()).getFilterRegex().toString().length() == 0)){
+	    // Show whole doc if no filter or if filter is blank, so nothing to do here
+	    if(firstDocRendering){
+		firstDocRendering = false;
 	    }
 	    else{
-		logger.warn("Could not find Moby object in document " + currentURL + 
-			    ", referred to by the reference ID in Moby link " + targetURL +
-			    " (document changed?)");
-		return null;
+		displayFilterEffect(); //need to cleanup previous state
 	    }
+	    return;
 	}
-	catch(Exception e){// Syntax error
-	    logger.error("Error: Could not search Moby data instance for XPath " + 
-			 childXPath + "):" + e);
-	    return null;
+	// Otherwise make a list of xpointers to data that should be grayed out
+	FilterSearch fs = filterHistory.get(getCurrentURL());
+	XPathOption xsel = fs.getSelectedXPath();
+	
+	// Find all the matching data
+	XPath xpath = xPathFactory.newXPath();
+	// Find the applicable DOM nodes if not yet found, or if selection criteria have changed
+	if(currentSelectedData == null || currentSelectionXPath == null || !currentSelectionXPath.equals(xsel.getXPath())){
+	    currentSelectionXPath = xsel.getXPath();
+	    try{
+		currentSelectedData = (NodeList) xpath.evaluate(currentSelectionXPath, currentDoc, XPathConstants.NODESET);
+	    } catch(Exception e){
+		logger.error("Could not evaluate XPath (" + xsel.getXPath() + ") over " + getCurrentURL(), e);
+		return;
+	    }
+	    //	    System.err.println("There are " + currentSelectedData.getLength() + 
+	    //		       " items selected in the document using XPath " + currentSelectionXPath);
 	}
 	
-	if(idSearchResult.getLength() == 0){
-	    logger.warn("Error: Could not find Moby data instance with XPath (" + childXPath + 
-			" in " + targetURL);
-	    return null;
+	// Find just the data subset also matching the regex
+	Map<String,Boolean> matchingXPtrs = new LinkedHashMap<String,Boolean>();
+	// Build the FSA only once, for efficiency
+	Pattern regex = Pattern.compile(fs.getFilterRegex().toString(), Pattern.MULTILINE);
+	for(int i = 0; i < currentSelectedData.getLength(); i++){
+	    Node node = currentSelectedData.item(i);	    
+	    if(node instanceof Element){
+		if(regex.matcher(((Element) node).getTextContent()).find()){
+		    matchingXPtrs.put(getXPtr(node), Boolean.TRUE);
+		}
+		else{
+		    matchingXPtrs.put(getXPtr(node), Boolean.FALSE);
+		}
+		//System.err.println("Adding " + getXPtr(node) + " as " + matchingXPtrs.get(getXPtr(node)));
+	    }
+	    else if(node instanceof Attr){
+		if(regex.matcher(((Attr) node).getValue()).find()){
+		    // Mark the element to which the attribute belongs
+		    matchingXPtrs.put(getXPtr(((Attr) node).getOwnerElement()), Boolean.TRUE);
+		}
+		else{
+		    matchingXPtrs.put(getXPtr(((Attr) node).getOwnerElement()), Boolean.FALSE);
+		}
+	    }
+	    else{
+		logger.warn("Found filter xpath result item that was not an Element or Attribute as expected ("+
+			    node.getClass().getName()+")");
+	    }
 	}
-	if(idSearchResult.getLength() > 1){
-	    logger.warn("Error: Moby data instance could not beresolved because " +
-			"there are multiple elements with XPath " + childXPath + " in " +
-			currentURL);
-	    return null;
+	
+	Map<String,Boolean> parentMatchingXPtrs = new LinkedHashMap<String,Boolean>();
+	for(String currXPtr: filterableNodes){
+	    for(Map.Entry<String,Boolean> matchingXPtr: matchingXPtrs.entrySet()){
+		if(matchingXPtr.getKey().startsWith(currXPtr+"/")){ // a parent of the matching data
+		    // No positive example yet?
+		    if(!parentMatchingXPtrs.containsKey(currXPtr) || !parentMatchingXPtrs.get(currXPtr).booleanValue()){
+			//System.err.println("Adding "+ matchingXPtr.getValue() + " for " + currXPtr);
+			parentMatchingXPtrs.put(currXPtr, matchingXPtr.getValue());
+		    }
+		}
+	    }
 	}
-	if(!(idSearchResult.item(0) instanceof Element)){
-	    logger.warn("Error: Moby data instance with XPath " + childXPath + " in " +
-			currentURL + " was not an element as required!");
-	    return null;
+
+	matchingXPtrs.putAll(parentMatchingXPtrs);
+	for(String currXPtr: matchingXPtrs.keySet()){
+	    // Is part of the selection criteria, but doesn't match the regex
+	    if(!matchingXPtrs.get(currXPtr).booleanValue()){
+		filteredData.put(currXPtr, "todo");
+	    }
 	}
-	mobyObject = (Element) idSearchResult.item(0);
+
+	//todo: filter objects without the HAS field at all...
 	
-	// Create the Java MOBY API object based on the linked document DOM fragment
-	try{
-	    MobyDataInstance mdi = MobyDataObject.createInstanceFromDOM(mobyObject, SeahawkOptions.getRegistry());
-	    if(mdi instanceof MobyDataObject){
-		mobyData = mdi;
+	// apply gray out in the document display
+	displayFilterEffect();
+    }
+
+    // If any moby object member is in a HAS relationship, add it to the list of filterable items
+    private void addHASXPtrs(List<String> filterList, Element object){
+	String tagName = object.getLocalName();
+	boolean isContainer = false;
+	MobyDataType mobyDataType = null;	
+	if(tagName.equals(MobyTags.MOBYDATA) || tagName.equals(MobyTags.COLLECTION) || tagName.equals(MobyTags.SIMPLE) ||
+	   tagName.equals(MobyTags.CROSSREFERENCE)){
+	    isContainer = true;
+	}
+	else{
+	    mobyDataType = MobyDataType.getDataType(tagName, SeahawkOptions.getRegistry());
+	    if(mobyDataType == null){
+		logger.warn("Found datatype unknown to the registry ("+object.getLocalName()+")");
+		return;
 	    }
-	    else if(mdi instanceof MobyDataObjectSet){
-		mobyData = mdi;
+	}
+
+	NodeList members = object.getChildNodes();
+	for(int i = 0; i < members.getLength(); i++){
+	    if(!(members.item(i) instanceof Element)){
+		continue;
+	    }
+
+	    Element member = (Element) members.item(i);
+	    addHASXPtrs(filterList, member);
+
+	    if(!isContainer){
+		String memberName = member.getAttribute(MobyTags.ARTICLENAME);
+		MobyRelationship membership = mobyDataType.getChild(memberName);
+		//System.err.println("Relationship for " + tagName  + " member " + memberName + 
+		//		   " is " + (membership == null ? null : membership.getRelationshipType()));
+		if(membership != null && membership.getRelationshipType() == Central.iHAS){
+		    filterList.add(getXPtr(member));
+		}
 	    }
-	    else{
-		logger.warn("Error: Moby data instance retrieved with XPath " + childXPath + " in " +
-			    currentURL + " was not a primary MOBY input object as expected");
-		return null;
-	    }
-
-	    // To avoid misinterpretation of primitive datatypes as Objects with any namespace in queries for
-	    // services, add a token namespace to get reasonable service results.
-	    if(mobyData instanceof MobyDataObject && ((MobyDataObject) mobyData).getPrimaryNamespace() == null){
-		((MobyDataObject) mobyData).setPrimaryNamespace(new MobyNamespace("unknown"));
-	    }
-
-	    // For workflow creation, etc. associate the potential input data with its source generalized XPath
-	    // Also, if there is currently a filter on the doc, store it too as useful info for workflow creation.
-	    StringBuilder userData = new StringBuilder();
-	    userData.append(targetURL.getProtocol()+":"+targetURL.getPath() + 
-			    "#" + elementInContextToNameBasedXPath(mobyObject));
-	    // todo: get rid of ref part of url for lookup
-	    logger.warn("Looking up filter info for "+ currentURL);
-	    if(filterHistory.containsKey(currentURL)){
-		FilterSearch fs = filterHistory.get(currentURL);
-		logger.warn("Found filter info for "+ currentURL + ": " + fs);
-		if(fs.getFilterRegex().length() > 0){
-		    XPathOption xsel = fs.getSelectedXPath();
-		    userData.append("\t"+fs.getFilterRegex()+"\t"+xsel.getXPath()+"\t"+xsel.getDesc());
-		}
-	    }
-	    mobyData.setUserData(userData.toString());
-	}
-	catch(MobyException mobye){  // Logic error
-	    logger.error("Error: Could not construct Moby data instance from document fragment: " + 
-			 mobye);
-	    mobye.printStackTrace();
 	}
-	
-	return mobyData;
     }
 
-    /**
-     * Gets the filter criteria currently being applied to the document being viewed.
-     *
-     * @return null unless there is a currently a non-blank filter criteria
-     */
-    public FilterSearch getFilter(){
-	if(getCurrentURL() == null){
-	    return null;
+    // Recursively ascend the DOM tree and find out our place in its branching structure
+    private String getXPtr(Node n){
+	if(n == null || n instanceof Document){
+	    return "";
 	}
-	FilterSearch fs = filterHistory.get(getCurrentURL());
-	if(fs != null && fs.getFilterRegex().length() > 0){
-	    return fs;
+	Node parent = n.getParentNode();
+
+	NodeList children = parent.getChildNodes();
+	int nonElementCnt = 0;
+	for(int i = 0; i < children.getLength(); i++){
+	    if(!(children.item(i) instanceof Element)){
+		nonElementCnt++; 
+		continue;
+	    }
+	    if(n == children.item(i)){
+		return getXPtr(parent)+"/"+(i-nonElementCnt+1);
+	    }
 	}
 	return null;
     }
 
     /**
-     * Restrict the view of the XML document to the parts matching the FilterSearch criteria 
+     * Gray out parts of the HTML view if they are part of the filtered data.
      */
-    public void applyFilter(boolean apply){
-	if(!apply || !filterHistory.containsKey(getCurrentURL()) ||
-	   (filterHistory.containsKey(getCurrentURL()) && 
-	    filterHistory.get(getCurrentURL()).getFilterRegex().toString().length() == 0)){
-	    // Show whole doc if no filter or if filter is blank
+    private void displayFilterEffect(){
+	
+	HTMLDocument d = (HTMLDocument) editorPane.getStyledDocument();
+
+	if(origStyles == null){
+ 	    // Store a copy of the doc's default rendering for all elements with xpointer 
+ 	    // (i.e. filterable elements) before doing any filter display mods,
+ 	    // as this allows us to remove the filter easily without reloading the document.
+ 	    origStyles = new Vector<AttributeSet>();	    
+ 	    for(String filterable: filterableNodes){
+ 		javax.swing.text.Element el = d.getElement(filterable);
+ 		origStyles.add(d.getParagraphElement(el.getStartOffset()).getAttributes());
+ 	    }
+ 	    firstDocRendering = false;
+ 	}
+ 	// Restore the original document appearance before applying the current filter...
+ 	else{
+ 	    Iterator<AttributeSet> origAttIter = origStyles.iterator();
+	    // Restore text data
+  	    for(String filterable: filterableNodes){
+ 		javax.swing.text.AbstractDocument.AbstractElement el = 
+		    (javax.swing.text.AbstractDocument.AbstractElement) d.getElement(filterable);
+		javax.swing.text.SimpleAttributeSet plain = new javax.swing.text.SimpleAttributeSet();
+		d.setParagraphAttributes(el.getStartOffset(), el.getEndOffset()-el.getStartOffset()+1, origAttIter.next(), true);
+ 	    }
+	    // Restore hyperlinks to default (blue underlined) appearance
+	    javax.swing.text.SimpleAttributeSet blueAttrSet = new javax.swing.text.SimpleAttributeSet();
+	    blueAttrSet.addAttribute(StyleConstants.Foreground, Color.blue);
+	    blueAttrSet.addAttribute(StyleConstants.Underline, Boolean.TRUE);
+	    for(HTMLDocument.Iterator it = d.getIterator(HTML.Tag.A); it.isValid(); it.next()) {
+		String link = (String) it.getAttributes().getAttribute(HTML.Attribute.HREF);
+		if(link == null){
+		    continue;
+		}
+		d.setCharacterAttributes(it.getStartOffset(), it.getEndOffset()-it.getStartOffset()+1, blueAttrSet, false);
+	    }
+	}
+
+	javax.swing.text.SimpleAttributeSet grayAttrSet = new javax.swing.text.SimpleAttributeSet();
+	grayAttrSet.addAttribute(StyleConstants.Foreground, Color.lightGray);
+	grayAttrSet.addAttribute(StyleConstants.Underline, Boolean.FALSE);
+
+	// Gray out hyperlinks that are for filtered data or their children
+	for(HTMLDocument.Iterator it = d.getIterator(HTML.Tag.A); it.isValid(); it.next()) {
+	    String link = (String) it.getAttributes().getAttribute(HTML.Attribute.HREF);
+	    if(link == null || link.indexOf("#") == -1){
+		continue;
+	    }
+	    link = link.replaceFirst("^.*#(.*)$", "$1"); // just get the ref
+	    
+	    for(Map.Entry<String,String> dataToFilter: filteredData.entrySet()){
+		if(link.equals(dataToFilter.getKey()) || link.startsWith(dataToFilter.getKey()+"/")){
+		    d.setCharacterAttributes(it.getStartOffset(), it.getEndOffset()-it.getStartOffset()+1, grayAttrSet, false);
+		}
+	    }
+	}
+
+	// Gray out the mismatched textual data
+	for(Map.Entry<String,String> dataToFilter: filteredData.entrySet()){	    
+
+	    //System.err.println("Filtering "+dataToFilter.getKey());
+	    javax.swing.text.AbstractDocument.AbstractElement htmlDiv = 
+		(javax.swing.text.AbstractDocument.AbstractElement) d.getElement(dataToFilter.getKey());
+	    if(htmlDiv == null){
+		logger.error("Could not find HTML element with expected id "+dataToFilter.getKey());
+		continue;
+	    }
+	    int areaStart = htmlDiv.getStartOffset();
+	    int areaEnd = htmlDiv.getEndOffset();
+	    d.setParagraphAttributes(areaStart, areaEnd-areaStart+1, grayAttrSet, false);
 	}
+
+	// Highlight the matches
+	Highlighter h = editorPane.getHighlighter();
+	h.removeAllHighlights(); //get rid of old highlights
+	FilterSearch fs = filterHistory.get(getCurrentURL());
+	int matchCount = 0;
+	if(fs != null && fs.getFilterRegex().length() > 0){
+	    try{
+		javax.swing.text.LayeredHighlighter.LayerPainter painter = 
+		    new DefaultHighlighter.DefaultHighlightPainter(Color.yellow);
+		Pattern p = Pattern.compile(fs.getFilterRegex().toString(), Pattern.MULTILINE);
+		for(String filterable: filterableNodes){
+		    javax.swing.text.Element el = d.getElement(filterable);
+		    String fragment = d.getText(el.getStartOffset(),
+						el.getEndOffset() - el.getStartOffset());
+		    Matcher matcher = p.matcher(fragment);
+		    while (matcher.find()) {
+			h.addHighlight(el.getStartOffset() + matcher.start(),
+				       el.getStartOffset() + matcher.end(),
+				       painter);
+			matchCount++;
+		    }
+		}
+	    } catch(Exception e){
+		logger.error("Could not apply match highlights", e);
+	    }
+	}
+	status.setText(" Found "+matchCount+" match"+(matchCount == 1 ? "": "es"));
+
+	// Update the ability to press the forward button, based on the new filter criteria
+	contentGUI.updateHistoryButtons();
+    }
+
+    private List<javax.swing.text.Element> getHyperlinkElements(javax.swing.text.Element el){
+	List<javax.swing.text.Element> links = new Vector<javax.swing.text.Element>();
+	for(int i = 0; i < el.getElementCount(); i++){
+	    javax.swing.text.Element childEl = el.getElement(i);
+	    System.err.println("Found child element " + childEl.getName());
+	    if(HTML.Tag.A.equals(childEl.getName())){
+		links.add(childEl);
+	    }
+	    else{
+		for(int j = 0; j < childEl.getElementCount(); j++){
+		    links.addAll(getHyperlinkElements(childEl.getElement(j)));
+		}
+	    }
+	}
+	return links;
     }
 
     /**
@@ -1384,7 +1682,7 @@
 		    filterHistory.put(getCurrentURL(), new FilterSearch(getCurrentURL(), contentGUI.getDocumentBuilder()));
 		}
 	    }
-	    logger.warn("Setting filter to "+filterHistory.get(getCurrentURL()) + " for " +getCurrentURL());
+	    logger.debug("Setting filter to "+filterHistory.get(getCurrentURL()) + " for " +getCurrentURL());
 	    filterSearchWidget.setFilter(filterHistory.get(getCurrentURL()));
 	    searchPanel.add(filterSearchWidget);
 	    searchPanel.revalidate();
@@ -1396,7 +1694,7 @@
 		deletedFilters.put(getCurrentURL(), filterHistory.remove(getCurrentURL()));
 	    }
 	    filterSearchWidget.setFilter(null);
-	    applyFilter(false);  // no filter GUI = no filter effect
+	    if(canFilter()){applyFilter(false);}  // no filter GUI = no filter effect
 	    searchPanel.remove(filterSearchWidget);
 	    searchPanel.revalidate();
 	    revalidate();
@@ -1404,63 +1702,6 @@
     }
 
     /**
-     * Generalizes the element instance into an XPath retrieving it and all 
-     * similarly nested elements (based on traversing the parent nodes and prepending their names)
-     */
-    protected String elementInContextToNameBasedXPath(Element targetElement){
-	String xpath = "";
-	
-	for(Element currentElement = targetElement; 
-	    currentElement != null;
-	    currentElement = (Element) currentElement.getParentNode()){
-	    String elName = currentElement.getLocalName();
-	    // top level parameters' names are important
-	    String articleName = currentElement.getAttributeNS(MobyTags.MOBY_XML_NS, MobyTags.ARTICLENAME);
-	    if(articleName == null || articleName.trim().length() == 0){
-		articleName = currentElement.getAttribute(MobyTags.ARTICLENAME);
-	    }
-
-	    if(elName.equals(MobyTags.SIMPLE)){
-		xpath = "/"+MobyTags.SIMPLE+"[@"+MobyTags.ARTICLENAME + " = '" +
-		    articleName+"']" +xpath;		
-	    }
-
-	    else if(elName.equals(MobyTags.COLLECTION)){
-		xpath = "/"+MobyTags.COLLECTION+"[@"+MobyTags.ARTICLENAME + " = '" +
-		    articleName +"']"+xpath;
-	    }
-	    else if(elName.equals(MobyTags.MOBYOBJECT)){
-		// Obviously, assume a base object is interesting based on its namespace
-		xpath = "/*[@"+MobyTags.OBJ_NAMESPACE + " = '" +
-		    currentElement.getAttribute(MobyTags.OBJ_NAMESPACE)+"']" + xpath;
-	    }
-	    else if(elName.equals(MobyTags.MOBYFLOAT) ||
-		    elName.equals(MobyTags.MOBYSTRING) ||
-		    elName.equals(MobyTags.MOBYBOOLEAN) ||
-		    elName.equals(MobyTags.MOBYINTEGER) ||
-		    elName.equals(MobyTags.MOBYDATETIME)){
-		// Assume that it isn't the primitive type, but rather the articleName
-		// that makes it appropriate to use.
-		xpath = "/*[@"+MobyTags.ARTICLENAME + " = '" +
-		    articleName+"']" + xpath;
-	    }
-	    else{
-		// It's a complex type.  Assume the type is why we are picking it.
-		// TODO: In future we may want to check if a parent type is allowed in 
-		// this slot, and generalize to that.  May also want to check that there isn't more
-		// than one member of this type at this level in a HAS relationship...
-		// in that case we probably want to go by articleName to catch the full semantics
-		xpath = "/"+elName + xpath;
-	    }
-	    if(!(currentElement.getParentNode() instanceof Element)){
-		break; // reached document node
-	    }
-	}
-	
-	return xpath;
-    }
-
-    /**
      * This method tries to figure out what kinds of MOBY Data could be 
      * created from the string given.  e.g. Keywords, DNASequence, etc.
      */
@@ -1638,14 +1879,13 @@
 	    dragging = true;  // so we don't export many times in a row while dragging
 	    lastHyperlinkDragged = lastHyperlinkHovered;
 	    status.setText("Drop the hyperlink onto a Web form field to populate it");
-	    System.err.println("Dragging "+lastHyperlinkDragged);
 	    getTransferHandler().exportToClipboard(this, 
 						   Toolkit.getDefaultToolkit().getSystemClipboard(), 
 						   TransferHandler.COPY);
 	    getTransferHandler().exportAsDrag(this, e, TransferHandler.COPY);
 	}
 	else if(!dragging){
-	    System.err.println("Dragging with no hyperlink activated");
+	    //System.err.println("Dragging with no hyperlink activated");
 	}
     }
 




More information about the MOBY-guts mailing list