View Javadoc

1   /*
2    * Copyright 2005 John G. Wilson
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   */
17  
18  package groovy.util.slurpersupport;
19  
20  import groovy.lang.Buildable;
21  import groovy.lang.Closure;
22  import groovy.lang.DelegatingMetaClass;
23  import groovy.lang.GString;
24  import groovy.lang.GroovyObject;
25  import groovy.lang.GroovyObjectSupport;
26  import groovy.lang.GroovyRuntimeException;
27  import groovy.lang.IntRange;
28  import groovy.lang.MetaClass;
29  import groovy.lang.Writable;
30  
31  import java.math.BigDecimal;
32  import java.math.BigInteger;
33  import java.net.MalformedURLException;
34  import java.net.URI;
35  import java.net.URISyntaxException;
36  import java.net.URL;
37  import java.util.ArrayList;
38  import java.util.HashMap;
39  import java.util.Iterator;
40  import java.util.LinkedList;
41  import java.util.List;
42  import java.util.Map;
43  import java.util.Stack;
44  
45  import org.codehaus.groovy.runtime.DefaultGroovyMethods;
46  
47  
48  /***
49   * @author John Wilson
50   */
51  
52  public abstract class GPathResult extends GroovyObjectSupport implements Writable, Buildable {
53      protected final GPathResult parent;
54      protected final String name;
55      protected final String namespacePrefix;
56      protected final Map namespaceMap = new HashMap();
57      protected final Map namespaceTagHints;
58  
59      /***
60       * @param parent
61       * @param name
62       * @param namespacePrefix
63       * @param namespaceTagHints
64       */
65      public GPathResult(final GPathResult parent, final String name, final String namespacePrefix, final Map namespaceTagHints) {
66          if (parent == null) {
67              // we are the top of the tree
68              this.parent = this;
69              this.namespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");  // The XML namespace is always defined
70          } else {
71              this.parent = parent;
72              this.namespaceMap.putAll(parent.namespaceMap);
73          }
74          this.name = name;
75          this.namespacePrefix = namespacePrefix;
76          this.namespaceTagHints = namespaceTagHints;
77  
78          setMetaClass(getMetaClass()); // wrap the standard MetaClass with the delegate
79      }
80  
81      /* (non-Javadoc)
82       * @see groovy.lang.GroovyObjectSupport#setMetaClass(groovy.lang.MetaClass)
83       */
84      public void setMetaClass(final MetaClass metaClass) {
85          final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {
86              /* (non-Javadoc)
87              * @see groovy.lang.DelegatingMetaClass#getAttribute(java.lang.Object, java.lang.String)
88              */
89              public Object getAttribute(final Object object, final String attribute) {
90                  return GPathResult.this.getProperty("@" + attribute);
91              }
92              
93              public void setAttribute(final Object object, final String attribute, final Object newValue) {
94                  GPathResult.this.setProperty("@" + attribute, newValue);
95              }
96          };
97          super.setMetaClass(newMetaClass);
98      }
99  
100     public Object getProperty(final String property) {
101         if ("..".equals(property)) {
102             return parent();
103         } else if ("*".equals(property)) {
104             return children();
105         } else if ("**".equals(property)) {
106             return depthFirst();
107         } else if (property.startsWith("@")) {
108             if (property.indexOf(":") != -1) {
109                 final int i = property.indexOf(":");
110                 return new Attributes(this, "@" + property.substring(i + 1), property.substring(1, i), this.namespaceTagHints);
111             } else {
112                 return new Attributes(this, property, this.namespaceTagHints);
113             }
114         } else {
115             if (property.indexOf(":") != -1) {
116                 final int i = property.indexOf(":");
117                 return new NodeChildren(this, property.substring(i + 1), property.substring(0, i), this.namespaceTagHints);
118             } else {
119                 return new NodeChildren(this, property, this.namespaceTagHints);
120             }
121         }
122     }
123 
124     public void setProperty(final String property, final Object newValue) {
125         if (property.startsWith("@")) {
126             if (newValue instanceof String || newValue instanceof GString) {
127             final Iterator iter = iterator();
128             
129                 while (iter.hasNext()) {
130                 final NodeChild child = (NodeChild)iter.next();
131                 
132                     child.attributes().put(property.substring(1), newValue);
133                 }
134             }
135         } else {
136         final GPathResult result = new NodeChildren(this, property, this.namespaceTagHints);
137         
138             if (newValue instanceof Map) {
139             final Iterator iter = ((Map)newValue).entrySet().iterator();
140             
141                 while (iter.hasNext()) {
142                 final Map.Entry entry = (Map.Entry)iter.next();
143                 
144                     result.setProperty("@" + entry.getKey(), entry.getValue());
145                 }
146             } else {           
147               if (newValue instanceof Closure) {
148                   result.replaceNode((Closure)newValue);
149               } else {
150                   result.replaceBody(newValue);
151               }
152             }
153         }
154     }
155     
156     public Object leftShift(final Object newValue) {
157         appendNode(newValue);
158         return this;
159     }
160     
161     public Object plus(final Object newValue) {
162         this.replaceNode(new Closure(this) {
163             public void doCall(Object[] args) {
164             final GroovyObject delegate = (GroovyObject)getDelegate();
165              
166                 delegate.getProperty("mkp");
167                 delegate.invokeMethod("yield", args);
168                 
169                 delegate.getProperty("mkp");
170                 delegate.invokeMethod("yield", new Object[]{newValue});
171             }
172         });
173         
174         return this;
175     }
176     
177     protected abstract void replaceNode(Closure newValue);
178     
179     protected abstract void replaceBody(Object newValue);
180     
181     protected abstract void appendNode(Object newValue);
182 
183     public String name() {
184         return this.name;
185     }
186 
187     public GPathResult parent() {
188         return this.parent;
189     }
190 
191     public GPathResult children() {
192         return new NodeChildren(this, this.namespaceTagHints);
193     }
194     
195     public String lookupNamespace(final String prefix) {
196         return (String)this.namespaceTagHints.get(prefix);
197     }
198 
199     public String toString() {
200         return text();
201     }
202 
203     public Integer toInteger() {
204         return DefaultGroovyMethods.toInteger(text());
205     }
206 
207     public Long toLong() {
208         return DefaultGroovyMethods.toLong(text());
209     }
210 
211     public Float toFloat() {
212         return DefaultGroovyMethods.toFloat(text());
213     }
214 
215     public Double toDouble() {
216         return DefaultGroovyMethods.toDouble(text());
217     }
218 
219     public BigDecimal toBigDecimal() {
220         return DefaultGroovyMethods.toBigDecimal(text());
221     }
222 
223     public BigInteger toBigInteger() {
224         return DefaultGroovyMethods.toBigInteger(text());
225     }
226 
227     public URL toURL() throws MalformedURLException {
228         return DefaultGroovyMethods.toURL(text());
229     }
230 
231     public URI toURI() throws URISyntaxException {
232         return DefaultGroovyMethods.toURI(text());
233     }
234 
235     public Boolean toBoolean() {
236         return DefaultGroovyMethods.toBoolean(text());
237     }
238 
239     public GPathResult declareNamespace(final Map newNamespaceMapping) {
240         this.namespaceMap.putAll(newNamespaceMapping);
241         return this;
242     }
243 
244     /* (non-Javadoc)
245     * @see java.lang.Object#equals(java.lang.Object)
246     */
247     public boolean equals(Object obj) {
248         return text().equals(obj.toString());
249     }
250 
251     public Object getAt(final int index) {
252         if (index < 0) throw new ArrayIndexOutOfBoundsException(index);
253         
254         final Iterator iter = iterator();
255         int count = 0;
256     
257         while (iter.hasNext()) {
258             if (count++ == index) {
259                 return iter.next();
260             } else {
261                 iter.next();
262             }
263         }
264         
265         return new NoChildren(this, this.name, this.namespaceTagHints);
266     }
267 
268     public Object getAt(final IntRange range) {
269     final int from = range.getFromInt();
270     final int to = range.getToInt();
271     
272         if (range.isReverse()) {
273             throw new GroovyRuntimeException("Reverse ranges not supported, range supplied is ["+ to + ".." + from + "]");
274         } else if (from < 0 || to < 0) {
275             throw new GroovyRuntimeException("Negative range indexes not supported, range supplied is ["+ from + ".." + to + "]");
276         } else {
277             return new Iterator() {
278             final Iterator iter = iterator();
279             Object next;
280             int count = 0;
281             
282                public boolean hasNext() {
283                    if (count <= to) {
284                        while (iter.hasNext()) {
285                            if (count++ >= from) {
286                                this.next = iter.next();
287                                return true;
288                            } else {
289                                iter.next();
290                            }
291                        }
292                    }
293 
294                    return false;
295                 }
296 
297                 public Object next() {
298                     return next;
299                 }
300 
301                 public void remove() {
302                     throw new UnsupportedOperationException();
303                 }
304                 
305             };
306         }
307      }
308 
309     public void putAt(final int index, final Object newValue) {
310     final GPathResult result = (GPathResult)getAt(index);
311     
312         if (newValue instanceof Closure) {
313             result.replaceNode((Closure)newValue);
314         } else {
315             result.replaceBody(newValue);
316         }
317     }
318     
319     public Iterator depthFirst() {
320         return new Iterator() {
321             private final List list = new LinkedList();
322             private final Stack stack = new Stack();
323             private Iterator iter = iterator();
324             private GPathResult next = getNextByDepth();
325 
326             public boolean hasNext() {
327                 return this.next != null;
328             }
329 
330             public Object next() {
331                 try {
332                     return this.next;
333                 } finally {
334                     this.next = getNextByDepth();
335                 }
336             }
337 
338             public void remove() {
339                 throw new UnsupportedOperationException();
340             }
341 
342             private GPathResult getNextByDepth() {
343                 while (this.iter.hasNext()) {
344                     final GPathResult node = (GPathResult) this.iter.next();
345                     this.list.add(node);
346                     this.stack.push(this.iter);
347                     this.iter = node.children().iterator();
348                 }
349 
350                 if (this.list.isEmpty()) {
351                     return null;
352                 } else {
353                     GPathResult result = (GPathResult) this.list.get(0);
354                     this.list.remove(0);
355                     this.iter = (Iterator) this.stack.pop();
356                     return result;
357                 }
358             }
359         };
360     }
361 
362     /***
363      * An iterator useful for traversing XML documents/fragments in breadth-first order.
364      *
365      * @return Iterator the iterator of GPathResult objects
366      */
367     public Iterator breadthFirst() {
368         return new Iterator() {
369             private final List list = new LinkedList();
370             private Iterator iter = iterator();
371             private GPathResult next = getNextByBreadth();
372 
373             public boolean hasNext() {
374                 return this.next != null;
375             }
376 
377             public Object next() {
378                 try {
379                     return this.next;
380                 } finally {
381                     this.next = getNextByBreadth();
382                 }
383             }
384 
385             public void remove() {
386                 throw new UnsupportedOperationException();
387             }
388 
389             private GPathResult getNextByBreadth() {
390                 List children = new ArrayList();
391                 while (this.iter.hasNext() || !children.isEmpty()) {
392                     if (this.iter.hasNext()) {
393                         final GPathResult node = (GPathResult) this.iter.next();
394                         this.list.add(node);
395                         this.list.add(this.iter);
396                         children.add(node.children());
397                     } else {
398                         List nextLevel = new ArrayList();
399                         for (int i = 0; i < children.size(); i++) {
400                             GPathResult next = (GPathResult) children.get(i);
401                             Iterator iterator = next.iterator();
402                             while (iterator.hasNext()) {
403                                 nextLevel.add(iterator.next());
404                             }
405                         }
406                         this.iter = nextLevel.iterator();
407                         children = new ArrayList();
408                     }
409                 }
410                 if (this.list.isEmpty()) {
411                     return null;
412                 } else {
413                     GPathResult result = (GPathResult) this.list.get(0);
414                     this.list.remove(0);
415                     this.iter = (Iterator) this.list.get(0);
416                     this.list.remove(0);
417                     return result;
418                 }
419             }
420         };
421     }
422 
423     public List list() {
424         final Iterator iter = nodeIterator();
425         final List result = new LinkedList();
426         while (iter.hasNext()) {
427             result.add(new NodeChild((Node) iter.next(), this.parent, this.namespacePrefix, this.namespaceTagHints));
428         }
429         return result;
430     }
431 
432     public boolean isEmpty() {
433         return size() == 0;
434     }
435 
436     public abstract int size();
437 
438     public abstract String text();
439 
440     public abstract GPathResult parents();
441 
442     public abstract Iterator childNodes();
443 
444     public abstract Iterator iterator();
445 
446     public abstract GPathResult find(Closure closure);
447 
448     public abstract GPathResult findAll(Closure closure);
449 
450     public abstract Iterator nodeIterator();
451 }