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 }