View Javadoc

1   /*
2    * $Id: PropertyNode.java,v 1.36 2005/07/12 17:06:45 jlerner Exp $
3    *
4    * Copyright (c) 1999-2004, BBN Technologies, LLC.
5    * All rights reserved.
6    * http://www.daml.org/legal/opensource/bbn_license.html
7    */
8   
9   package com.bbn.swede.core.dom;
10  
11  import java.util.ArrayList;
12  import java.util.Arrays;
13  import java.util.List;
14  
15  import org.eclipse.jface.text.IRegion;
16  
17  import com.bbn.swede.core.dom.rdf.ParseType;
18  import com.hp.hpl.jena.rdf.arp.MalformedURIException;
19  import com.hp.hpl.jena.rdf.arp.URI;
20  import com.hp.hpl.jena.rdf.model.AnonId;
21  import com.hp.hpl.jena.rdf.model.Model;
22  import com.hp.hpl.jena.rdf.model.Property;
23  import com.hp.hpl.jena.rdf.model.RDFList;
24  import com.hp.hpl.jena.rdf.model.RDFNode;
25  import com.hp.hpl.jena.rdf.model.ReifiedStatement;
26  import com.hp.hpl.jena.rdf.model.Resource;
27  import com.hp.hpl.jena.rdf.model.Statement;
28  
29  /***
30   * Common implementation for property nodes.  A property node is any tag that
31   * is treated as a predicate, either due to the type of the tag or based on its
32   * position in the document relative to other things and predicates.
33   * @author jlerner
34   */
35  public abstract class PropertyNode extends TagNode
36  {
37     /***
38      * Default constructor for property nodes.  Creates a parentless, childless
39      * node as a singleton tag.
40      */
41     public PropertyNode()
42     {
43        //properties default to singleton tags
44        super(true);
45     }
46  
47     /***
48      * Creates a parentless, childless node as either a singleton or with 
49      * discrete begin and end tags.
50      * @param bSingleton <code>true</code> to create a singleton tag, 
51      * <code>false</code> to create discrete begin and end tags.
52      */
53     public PropertyNode(boolean bSingleton)
54     {
55        super(bSingleton);
56     }
57  
58     /***
59      * The Jena statement resource associated with this property node.  This
60      * may be null if the property is not part of a complete statement.
61      */
62     protected Statement _stmtAssoc;
63     
64     /***
65      * The resource representing the reification of the statement formed by this
66      * property node.  May be <code>null</code> if the associated statement is
67      * not reified.
68      */
69     protected ReifiedStatement _reified;
70     /***
71      * Associates a Jena statement with the node.
72      * @param stmt The statement.
73      */
74     protected void setAssociatedStatement(Statement stmt)
75     {
76        _stmtAssoc = stmt;
77     }
78     
79     /***
80      * Associates a Jena reified statement resource with this node.
81      * @param rs The reified statement resource.
82      */
83     protected void setReification(ReifiedStatement rs)
84     {
85        _reified = rs;
86     }
87  
88     /***
89      * Retrieves the Jena statement associated with the node.
90      * @return The associated statement, or <code>null</code> if no statement
91      *         is associated.
92      */
93     public Statement getAssociatedStatement()
94     {
95        return _stmtAssoc;
96     }
97     
98     /***
99      * Retrieves the Jena resource representing the reification of the statement
100     * associated with this node.
101     * @return The reified statement resource, or <code>null</code> if the
102     *         associated statement is not reified.
103     */
104    public ReifiedStatement getReification()
105    {
106       return _reified;
107    }
108 
109    /***
110     * <p>Creates a Jena statement to represent the property node.  The
111     * resulting statement will be of the form [parentResource, getQName(),
112     * createObject()].  Statement creation will fail if there is no resource
113     * associated with the parent node or if getObject() fails.</p>
114     *
115     * <p>If the node has an ID, the created statement will be flagged in the model
116     * as being reified.</p>
117     * @param model The Jena model in which to create the statement.
118     * @return The created statement, or <code>null</code> if no statement could
119     *         be created.
120     * @see #createObject(Model)
121     */
122    protected Statement createJenaStatement(Model model)
123    {
124       if (!(_nodeParent instanceof ClassNode))
125       {
126          return null;
127       }
128       Resource resSubject = ((ClassNode) _nodeParent).getAssociatedResource();
129       if (resSubject == null)
130       {
131          return null;
132       }
133 
134       String sUri = getUri();
135       if (sUri == null)
136       {
137          return null;
138       }
139       Property p = model.getProperty(sUri);
140 
141       RDFNode resObject = createObject(model);
142       if (resObject == null)
143       {
144          return null;
145       }
146       Statement stmt = model.createStatement(resSubject, p, resObject);
147       OASTNode[] nodes = getNodesOfType(RDF_ID, 1);
148       //TODO: check for size > 1 and yadda yadda yadda
149       if (nodes.length > 0 && !stmt.isReified())
150       {
151          AttributeNode an = (AttributeNode)nodes[0];
152          String sValue = '#' + an.getValue();
153          String sBase = getXmlBase();
154          try
155          {
156             URI uriBase = (sBase == null ? null : new URI(sBase));
157             URI uri = new URI(uriBase, sValue);
158             ReifiedStatement rs = model.createReifiedStatement(uri.toString(), stmt);
159             setReification(rs);
160          }
161          catch (MalformedURIException e)
162          {
163             //TODO: place error marker indicating a relative URI with no xml:base
164          }
165       }
166       setAssociatedStatement(stmt);
167       model.add(stmt);
168       return stmt;
169    }
170 
171    /***
172     * <p>Creates a Jena RDFNode to serve as the object of the statement for this
173     * propety node.  The object of the statement will be determined by the
174     * children of this node.  If it has a child rdf:resource node, or a child
175     * tag node with an associated resource,  the object is the appropriate
176     * Jena resource.  If it has a child Literal node, the object is literal 
177     * text.</p>
178     *
179     * <p>If more than one of these node types is a child of this node, the
180     * behavior is undefined.</p>
181     * @return The Jean RDFNode to serve as the object of the statement.
182     */
183    private RDFNode createObject(Model model)
184    {
185       RDFNode resObject = null;
186       OASTNode[] nodes = this.getNodesOfType(OASTNode.RDF_PARSETYPE, 1);
187       OASTNode[] children = getChildren();
188       if (nodes.length > 0)
189       {
190          ParseType pt = (ParseType)nodes[0];
191          ArrayList alThings = new ArrayList();
192          for (int i = 0; i < children.length; i++)
193          {
194             OASTNode n = children[i];
195             if (n instanceof ClassNode)
196             {
197                Resource res = ((ClassNode)n).createJenaResource(model);
198                alThings.add(res);
199             }
200          }
201          RDFList list = pt.createList(model, alThings.iterator());
202          return list;
203       }
204       
205       List l = new ArrayList(Arrays.asList(getNodesOfType(RDF_RESOURCE, 1)));
206       l.addAll(Arrays.asList(getNodesOfType(RDF_NODEID, 1)));
207       for (int i = 0; i < children.length; i++)
208       {
209          if (children[i] instanceof ClassNode
210             || children[i] instanceof Literal)
211          {
212             l.add(children[i]);
213          }
214       }
215       //TODO: check for size > 1 and add marker
216       if (l.size() == 0)
217       {
218          return null;
219       }
220       OASTNode node = (OASTNode)l.get(0);
221       if (node == null)
222       {
223          return null;
224       }
225       switch (node.getNodeType())
226       {
227          case LITERAL :
228             resObject = model.createLiteral(node.toString());
229             break;
230          case RDF_RESOURCE :
231             String sBase = getXmlBase();
232             try
233             {
234                URI uriBase = (sBase == null ? null : new URI(sBase));
235                URI uri = new URI(uriBase, ((AttributeNode) node).getValue());
236                resObject = model.createResource(uri.toString());
237             }
238             catch (MalformedURIException e)
239             {
240                //TODO: place a marker indicating the illegal URI
241                e.printStackTrace();
242             }
243             break;
244          case RDF_NODEID :
245             resObject =
246                model.createResource(
247                   new AnonId(((AttributeNode) node).getValue()));
248             break;
249          default :
250             ClassNode cn = (ClassNode) node;
251             resObject = cn.getAssociatedResource();
252             break;
253       }
254       return resObject;
255    }
256    
257    /***
258     * Removes this property's associated statement from the Jena model.  If
259     * the same statement is specified by another node in the document, it will
260     * not be removed.  If  there is no statement associated with this node, 
261     * nothing happens.
262     * @param model The model to remove the statement from.
263     */
264    protected void removeJenaStatement(Model model)
265    {
266       if (getAssociatedStatement() == null)
267       {
268          return;
269       }
270       Statement stmt = getAssociatedStatement();
271       setAssociatedStatement(null);
272       Resource res = getReification();
273       setReification(null);
274       if (res != null && getOWLAbstractSyntaxTree().getNode(res) == null)
275       {
276          //Nobody else is using the reification of this statement,
277          //so it's safe to remove it.
278          if (stmt.isReified())
279          {
280             stmt.removeReification();
281          }
282       }
283       if (stmt != null && getOWLAbstractSyntaxTree().getNode(stmt) == null)
284       {
285          //Nobody else is using the statement, go ahead and clean it up.
286          model.remove(stmt);
287       }
288       //Otherwise, leave things as-is and they'll get cleaned up later when
289       //the last references to them are removed.
290    }
291 
292    /***
293     * Removes the node representing the object of the RDF statement.  If the
294     * property does not have an object already, nothing happens.  If a child
295     * node is removed, this node will be displaced to compensate.
296     * @throws OASTException if this node is attached to a read-only OAST
297     */
298    private void removeObject() throws OASTException
299    {
300       OASTNode child = getFirstChild();
301       if (child == null)
302       {
303          OASTNode[] nodes = getNodesOfType(RDF_RESOURCE, 1);
304          if (nodes.length == 0)
305          {
306             return;
307          }
308          child = nodes[0];
309          //TODO: check for more than one resource attribute & throw exception?
310       }
311       //TODO: check for more than one child & throw exception?
312       remove(child, true);
313    }
314 
315    /***
316     * Attaches a tag node or literal as the lone non-attribute child of this
317     * property node.  
318     * @param node The node to attach.
319     * @throws OASTException if this node is attached to a read-only OAST
320     *                       or the node to attach is of the wrong type.
321     */
322    private void attachObject(final OASTNode node) throws OASTException
323    {
324       if (!(node instanceof TagNode) && !(node instanceof Literal))
325       {
326          throw new OASTException(
327             "Attempted to attach illegal node type as object of statement", this);
328       }
329       
330       IOASTRunnable action = new DefaultOASTRunnable()
331       {
332          /* (non-Javadoc)
333           * @see com.bbn.swede.core.dom.IOASTRunnable#run()
334           */
335          public void run() throws OASTException
336          {
337             TagNode tag = splitSingleton();
338             IRegion[] regions = tag.simplePartitioning();
339             int iOffset = regions[regions.length - 1].getOffset();
340             node.accept(new NodeDisplacementVisitor(0, iOffset));
341             tag.insert(node, true);
342          }
343       };
344       if (getOWLAbstractSyntaxTree() != null)
345       {
346          getOWLAbstractSyntaxTree().run(action);
347       }
348       else
349       {
350          action.run();
351       }
352    }
353    
354    /***
355     * Changes the object of this property to a literal.
356     * @param sLiteral The text to use as the new object
357     * @throws OASTException if this node is attached to a read-only OAST
358     */
359    public void setObjectLiteral(final String sLiteral) throws OASTException
360    {
361       IOASTRunnable action = new DefaultOASTRunnable()
362       {
363          public void run() throws OASTException
364          {
365             removeObject();
366             if (sLiteral != null && sLiteral.length() > 0)
367             {
368                Literal l = new Literal(sLiteral);
369                attachObject(l);
370             }
371             else
372             {
373                //Ensure that the tag has discrete begin and end tags, even
374                //though the literal is empty for now
375                splitSingleton();
376             }
377          }
378       };
379       if (getOWLAbstractSyntaxTree() == null)
380       {
381          action.run();
382       }
383       else
384       {
385          getOWLAbstractSyntaxTree().run(action);
386       }
387    }
388    
389    /***
390     * Changes the object of this property to an individual whose tags are nested
391     * within the property node.
392     * @param tagObject The individual to use as the new object
393     * @throws OASTException if this node is attached to a read-only OAST
394     */
395    public void setObjectTag(final ClassNode tagObject) throws OASTException
396    {
397       IOASTRunnable action = new DefaultOASTRunnable()
398       {
399          public void run() throws OASTException
400          {
401             removeObject();
402             attachObject(tagObject);
403             
404          }
405       };
406       if (getOWLAbstractSyntaxTree() == null)
407       {
408          action.run();
409       }
410       else
411       {
412          getOWLAbstractSyntaxTree().run(action);
413       }
414    }
415    
416    /***
417     * Changes the object of this property to a resource defined by a tag elsewhere.
418     * @param sResourceURI The URI of the resource to use as the new object
419     * @throws OASTException if this node is attached to a read-only OAST
420     */
421    public void setObjectResource(final String sResourceURI) throws OASTException
422    {
423       IOASTRunnable action = new DefaultOASTRunnable()
424       {
425          /* (non-Javadoc)
426           * @see com.bbn.swede.core.dom.IOASTRunnable#run()
427           */
428          public void run() throws OASTException
429          {
430             removeObject();
431             addAttribute("rdf:resource", sResourceURI);
432          }
433       };
434       if (getOWLAbstractSyntaxTree() == null)
435       {
436          action.run();
437       }
438       else
439       {
440          getOWLAbstractSyntaxTree().run(action);
441       }
442    }
443    
444    /***
445     * Retrieves the object of the statement represented by this property node.
446     * This node may be either an rdf:resource attribute, a literal, or a tag.
447     * @return The object of the statement, or <code>null</code> if this node
448     *         is not part of a complete statement.
449     * @throws OASTException if the property has more than one child that could
450     *                       be interpreted as the object of the statement.
451     */
452    public OASTNode getObject() throws OASTException
453    {
454       OASTNode[] resources = getNodesOfType(RDF_RESOURCE);
455       OASTNode[] literals = getNodesOfType(LITERAL);
456       OASTNode tag = getFirstChild();
457       while (tag != null && !(tag instanceof TagNode))
458       {
459          tag = getNextChild(tag);
460       }
461       
462       int childCount = resources.length + literals.length;
463       if (tag != null)
464       {
465          childCount++;
466       }
467       if (childCount == 0)
468       {
469          return null;
470       }
471       if (childCount > 1)
472       {
473          throw new OASTException("Object of statement cannot be uniquely determined", this);
474       }
475       
476       if (tag != null)
477       {
478          return tag;
479       }
480       else if (resources.length > 0)
481       {
482          return resources[0];
483       }
484       else
485       {
486          return literals[0];
487       }
488    }
489 }