001    /*
002     * Copyright 2005 John G. Wilson
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     *     http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     *
016     */
017    
018    package groovy.util.slurpersupport;
019    
020    import groovy.lang.Buildable;
021    import groovy.lang.Closure;
022    import groovy.lang.DelegatingMetaClass;
023    import groovy.lang.GString;
024    import groovy.lang.GroovyObject;
025    import groovy.lang.GroovyObjectSupport;
026    import groovy.lang.GroovyRuntimeException;
027    import groovy.lang.IntRange;
028    import groovy.lang.MetaClass;
029    import groovy.lang.Writable;
030    
031    import java.math.BigDecimal;
032    import java.math.BigInteger;
033    import java.net.MalformedURLException;
034    import java.net.URI;
035    import java.net.URISyntaxException;
036    import java.net.URL;
037    import java.util.ArrayList;
038    import java.util.HashMap;
039    import java.util.Iterator;
040    import java.util.LinkedList;
041    import java.util.List;
042    import java.util.Map;
043    import java.util.Stack;
044    
045    import org.codehaus.groovy.runtime.DefaultGroovyMethods;
046    
047    
048    /**
049     * @author John Wilson
050     */
051    
052    public abstract class GPathResult extends GroovyObjectSupport implements Writable, Buildable {
053        protected final GPathResult parent;
054        protected final String name;
055        protected final String namespacePrefix;
056        protected final Map namespaceMap = new HashMap();
057        protected final Map namespaceTagHints;
058    
059        /**
060         * @param parent
061         * @param name
062         * @param namespacePrefix
063         * @param namespaceTagHints
064         */
065        public GPathResult(final GPathResult parent, final String name, final String namespacePrefix, final Map namespaceTagHints) {
066            if (parent == null) {
067                // we are the top of the tree
068                this.parent = this;
069                this.namespaceMap.put("xml", "http://www.w3.org/XML/1998/namespace");  // The XML namespace is always defined
070            } else {
071                this.parent = parent;
072                this.namespaceMap.putAll(parent.namespaceMap);
073            }
074            this.name = name;
075            this.namespacePrefix = namespacePrefix;
076            this.namespaceTagHints = namespaceTagHints;
077    
078            setMetaClass(getMetaClass()); // wrap the standard MetaClass with the delegate
079        }
080    
081        /* (non-Javadoc)
082         * @see groovy.lang.GroovyObjectSupport#setMetaClass(groovy.lang.MetaClass)
083         */
084        public void setMetaClass(final MetaClass metaClass) {
085            final MetaClass newMetaClass = new DelegatingMetaClass(metaClass) {
086                /* (non-Javadoc)
087                * @see groovy.lang.DelegatingMetaClass#getAttribute(java.lang.Object, java.lang.String)
088                */
089                public Object getAttribute(final Object object, final String attribute) {
090                    return GPathResult.this.getProperty("@" + attribute);
091                }
092                
093                public void setAttribute(final Object object, final String attribute, final Object newValue) {
094                    GPathResult.this.setProperty("@" + attribute, newValue);
095                }
096            };
097            super.setMetaClass(newMetaClass);
098        }
099    
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    }