001/**
002 *
003 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
004 *
005 * This library is free software; you can redistribute it and/or
006 * modify it under the terms of the GNU Lesser General Public
007 * License as published by the Free Software Foundation; either 
008 * version 2.1 of the License, or (at your option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful,
011 * but WITHOUT ANY WARRANTY; without even the implied warranty of
012 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013 * Lesser General Public License for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public 
016 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
017 * 
018 **/
019package lucee.runtime.text.xml;
020
021import java.util.ArrayList;
022import java.util.Comparator;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map.Entry;
026
027import lucee.runtime.PageContext;
028import lucee.runtime.dump.DumpData;
029import lucee.runtime.dump.DumpProperties;
030import lucee.runtime.dump.DumpTable;
031import lucee.runtime.dump.DumpUtil;
032import lucee.runtime.dump.SimpleDumpData;
033import lucee.runtime.exp.ExpressionException;
034import lucee.runtime.exp.PageException;
035import lucee.runtime.exp.PageRuntimeException;
036import lucee.runtime.op.Caster;
037import lucee.runtime.text.xml.struct.XMLObject;
038import lucee.runtime.text.xml.struct.XMLStruct;
039import lucee.runtime.type.Collection;
040import lucee.runtime.type.KeyImpl;
041import lucee.runtime.type.dt.DateTime;
042import lucee.runtime.type.it.EntryIterator;
043import lucee.runtime.type.it.KeyIterator;
044import lucee.runtime.type.it.StringIterator;
045import lucee.runtime.type.util.ArraySupport;
046import lucee.runtime.type.util.ArrayUtil;
047import lucee.runtime.util.ArrayIterator;
048
049import org.w3c.dom.Document;
050import org.w3c.dom.Node;
051import org.w3c.dom.NodeList;
052
053/**
054 * 
055 */
056public final class XMLNodeList extends ArraySupport implements NodeList, XMLObject{
057
058        private boolean caseSensitive;
059        private Document doc;
060        private Node parent;
061        private String filter;
062        private final short type;
063        
064        /**
065         * @param parent Parent Node
066         * @param caseSensitive
067         */
068    public XMLNodeList(Node parent, boolean caseSensitive, short type) {
069        this(parent,caseSensitive,type,null);
070    }
071    public XMLNodeList(Node parent, boolean caseSensitive,short type, String filter) {
072        this.filter=filter;
073        this.type=type;
074        if(parent instanceof XMLStruct) {
075            XMLStruct xmlNode = ((XMLStruct)parent);
076            this.parent=xmlNode.toNode();
077            this.caseSensitive=xmlNode.getCaseSensitive();
078        }
079        else {
080            this.parent=parent;
081            this.caseSensitive=caseSensitive;
082        }
083        this.doc=this.parent.getOwnerDocument();
084    }
085
086        @Override
087        public int getLength() {
088                return XMLUtil.childNodesLength(parent,type,caseSensitive,filter);
089        }
090
091        @Override
092        public Node item(int index) {
093                return XMLCaster.toXMLStruct(getChildNode(index),caseSensitive);
094        }
095
096        @Override
097        public int size() {
098                return getLength();
099        }
100
101        @Override
102        public Collection.Key[] keys() {
103                Collection.Key[] keys=new Collection.Key[getLength()];
104                for(int i=1;i<=keys.length;i++) {
105                        keys[i-1]=KeyImpl.init(i+"");
106                }
107                return keys;
108        }
109        
110        @Override
111        public int[] intKeys() {
112                int[] keys=new int[getLength()];
113                for(int i=1;i<=keys.length;i++) {
114                        keys[i-1]=i;
115                }
116                return keys;
117        }
118        
119        public Object removeEL(Collection.Key key) {
120                return removeEL(Caster.toIntValue(key.getString(),-1));
121        }
122
123
124        @Override
125        public Object remove(Collection.Key key) throws PageException {
126                return removeE(Caster.toIntValue(key.getString()));
127        }
128
129        @Override
130        public Object removeEL(int index) {
131                int len=size();
132                if(index<1 || index>len) return null;
133                try {
134                        return XMLCaster.toXMLStruct(parent.removeChild(XMLCaster.toRawNode(item(index-1))),caseSensitive);
135                } 
136                catch (Exception e) {
137                        return null;
138                }
139        }
140        
141        @Override
142        public Object removeE(int index) throws PageException {
143                int len=size();
144                if(index<1 || index>len)
145                        throw new ExpressionException("can't remove value form XML Node List at index "+index+
146                                ", valid indexes goes from 1 to "+len);
147                return XMLCaster.toXMLStruct(parent.removeChild(XMLCaster.toRawNode(item(index-1))),caseSensitive);
148        }
149
150        @Override
151        public void clear() {
152                Node[] nodes=getChildNodesAsArray();
153                for(int i=0;i<nodes.length;i++) {
154                        parent.removeChild(XMLCaster.toRawNode(nodes[i]));
155                }
156        }
157
158
159        @Override
160        public Object get(String key) throws ExpressionException {
161                return getE(Caster.toIntValue(key));
162        }
163
164        @Override
165        public Object get(Collection.Key key) throws ExpressionException {
166                return get(key.getString());
167        }
168        
169        @Override
170        public Object getE(int key) throws ExpressionException {
171                Object rtn= item(key-1);
172                if(rtn==null) throw new ExpressionException("invalid index ["+key+"] for XML Node List , indexes goes from [0-"+size()+"]");
173                return rtn;
174        }
175
176        @Override
177        public Object get(String key, Object defaultValue) {
178                int index=Caster.toIntValue(key,Integer.MIN_VALUE);
179                if(index==Integer.MIN_VALUE) return defaultValue;
180            return get(index,defaultValue);
181        }
182
183        @Override
184        public Object get(Collection.Key key, Object defaultValue) {
185                return get(key.getString(),defaultValue);
186        }
187
188        @Override
189        public Object get(int key, Object defaultValue) {
190                Object rtn= item(key-1);
191                if(rtn==null) return defaultValue;
192                return rtn;
193        }
194
195        @Override
196        public Object set(String key, Object value) throws PageException {
197                return setE(Caster.toIntValue(key),value);
198        }
199
200        @Override
201        public Object set(Collection.Key key, Object value) throws PageException {
202                return set(key.getString(), value);
203        }
204        
205        @Override
206        public Object setE(int index, Object value) throws PageException {
207                // check min Index
208                        if(index<1)
209                                throw new ExpressionException("invalid index ["+index+"] to set a child node, valid indexes start at 1");
210                
211                Node[] nodes=getChildNodesAsArray();
212                
213                // if index Greater len append
214                if(index>nodes.length) return append(value);
215                
216                // remove all children
217                clear();
218                
219                // set all children before new Element
220                for(int i=1;i<index;i++) {
221                        append(nodes[i-1]);
222                }
223                
224                // set new Element
225                append(XMLCaster.toNode(doc,value,true));
226                
227                // set all after new Element
228                for(int i=index;i<nodes.length;i++) {
229                        append(nodes[i]);
230                }
231                
232                return value;
233        }
234
235        @Override
236        public Object setEL(String key, Object value) {
237                int index=Caster.toIntValue(key,Integer.MIN_VALUE);
238                if(index==Integer.MIN_VALUE) return null;
239                return setEL(index,value);
240        }
241
242        @Override
243        public Object setEL(Collection.Key key, Object value) {
244                return setEL(key.getString(), value);
245        }
246        
247        @Override
248        public Object setEL(int index, Object value) {
249                try {
250                        return setE(index,value);
251                } catch (PageException e) {
252                        return null;
253                }
254        }
255
256        @Override
257        public Iterator<Collection.Key> keyIterator() {
258                return new KeyIterator(keys());
259        }
260    
261    @Override
262        public Iterator<String> keysAsStringIterator() {
263        return new StringIterator(keys());
264    }
265        
266        @Override
267        public Iterator<Entry<Key, Object>> entryIterator() {
268                return new EntryIterator(this,keys());
269        }
270        
271        public Iterator<Object> valueIterator() {
272                Object[] values=new Object[getLength()];
273                for(int i=0;i<values.length;i++) {
274                        values[i]=item(i);
275                }
276                return new ArrayIterator(values);
277        }
278
279        @Override
280        public DumpData toDumpData(PageContext pageContext, int maxlevel, DumpProperties dp) {
281                maxlevel--;
282                DumpTable table = new DumpTable("xml","#cc9999","#ffffff","#000000");
283                table.setTitle("Array (XML Node List)");
284                int len=size();
285                
286                for(int i=1;i<=len;i++) {
287                        table.appendRow(1,new SimpleDumpData(i),DumpUtil.toDumpData(item(i-1), pageContext,maxlevel,dp));
288                }
289                return table;
290        }
291        
292        @Override
293        public Object append(Object o) throws PageException {
294                return parent.appendChild(XMLCaster.toNode(doc,o,true));
295        }
296        
297        public Object appendEL(Object o) {
298                try {
299                        return append(o);
300                } catch (Exception e) {
301                        return null;
302                }
303        }
304        
305        @Override
306        public Object clone() {
307                return duplicate(true);
308        }
309
310
311        @Override
312        public Collection duplicate(boolean deepCopy) {
313                return new XMLNodeList(parent.cloneNode(deepCopy),caseSensitive,type);
314        }
315        
316
317        @Override
318        public int getDimension() {
319                return 1;
320        }
321        
322        @Override
323        public boolean insert(int index, Object value) throws PageException {
324                // check min Index
325                if(index<1)
326                        throw new ExpressionException("invalid index ["+index+"] to insert a child node, valid indexes start at 1");
327        
328                Node[] nodes=getChildNodesAsArray();
329                
330                // if index Greater len append
331                        if(index>nodes.length) {
332                                append(value);
333                                return true;
334                        }
335                
336                // remove all children
337                clear();
338                
339                // set all children before new Element
340                for(int i=1;i<index;i++) {
341                        append(nodes[i-1]);
342                }
343                
344                // set new Element
345                append(XMLCaster.toNode(doc,value,true));
346                
347                // set all after new Element
348                for(int i=index;i<=nodes.length;i++) {
349                        append(nodes[i-1]);
350                }
351                
352                return true;
353        }
354        
355        @Override
356        public Object prepend(Object o) throws PageException {
357                
358                Node[] nodes=getChildNodesAsArray();
359                
360                // remove all children
361                clear();
362                
363                // set new Element
364                append(XMLCaster.toNode(doc,o,true));
365                
366                // set all after new Element
367                for(int i=0;i<nodes.length;i++) {
368                        append(nodes[i]);
369                }
370                return o;
371        }
372        
373        @Override
374        public void resize(int to) throws ExpressionException {
375                if(to>size())throw new ExpressionException("can't resize a XML Node List Array with empty Elements");
376        }
377        
378        @Override
379        public void sort(String sortType, String sortOrder)
380                        throws ExpressionException {
381                throw new ExpressionException("can't sort a XML Node List Array","sorttype:"+sortType+";sortorder:"+sortOrder);
382        }
383        
384        @Override
385        public void sort(Comparator comp) {
386                throw new PageRuntimeException("can't sort a XML Node List Array");
387        }
388
389        @Override
390        public Object[] toArray() {
391                return getChildNodesAsArray();
392        }
393        
394        public ArrayList toArrayList() {
395                Object[] arr=toArray();
396                ArrayList list=new ArrayList();
397                for(int i=0;i>arr.length;i++) {
398                        list.add(arr[i]);
399                }
400                return list;
401        }
402        
403        /**
404         * @return returns a output from the content as plain Text
405         */
406        public String toPlain() {
407                StringBuffer sb=new StringBuffer();
408                int length=size();
409                for(int i=1;i<=length;i++) {
410                        sb.append(i);
411                        sb.append(": ");
412                        sb.append(get(i,null));
413                        sb.append("\n");
414                }
415                return sb.toString();
416        }
417    
418    private Node getChildNode(int index) {
419                return XMLUtil.getChildNode(parent,type,caseSensitive,filter,index);
420        }
421        
422        private Node[] getChildNodesAsArray() {
423                return XMLUtil.getChildNodesAsArray(parent,type,caseSensitive,filter);
424        }
425
426    @Override
427    public boolean containsKey(String key) {
428        return get(key,null)!=null;
429    }   
430
431        @Override
432        public boolean containsKey(Collection.Key key) {
433        return get(key,null)!=null;
434        }
435
436    @Override
437    public boolean containsKey(int key) {
438        return get(key,null)!=null;
439    }
440
441    @Override
442    public boolean getCaseSensitive() {
443        return caseSensitive;
444    }      
445    
446    @Override
447    public String castToString() throws ExpressionException {
448        throw new ExpressionException("Can't cast XML NodeList to String");
449    }
450    
451        @Override
452        public String castToString(String defaultValue) {
453                return defaultValue;
454        }
455
456    @Override
457    public boolean castToBooleanValue() throws ExpressionException {
458        throw new ExpressionException("Can't cast XML NodeList to a boolean value");
459    }
460    
461    @Override
462    public Boolean castToBoolean(Boolean defaultValue) {
463        return defaultValue;
464    }
465
466
467    @Override
468    public double castToDoubleValue() throws ExpressionException {
469        throw new ExpressionException("Can't cast XML NodeList to a number value");
470    }
471    
472    @Override
473    public double castToDoubleValue(double defaultValue) {
474        return defaultValue;
475    }
476
477
478    @Override
479    public DateTime castToDateTime() throws ExpressionException {
480        throw new ExpressionException("Can't cast XML NodeList to a Date");
481    }
482    
483    @Override
484    public DateTime castToDateTime(DateTime defaultValue) {
485        return defaultValue;
486    }
487
488        @Override
489        public int compareTo(boolean b) throws ExpressionException {
490                throw new ExpressionException("can't compare XML NodeList with a boolean value");
491        }
492
493        @Override
494        public int compareTo(DateTime dt) throws PageException {
495                throw new ExpressionException("can't compare XML NodeList with a DateTime Object");
496        }
497
498        @Override
499        public int compareTo(double d) throws PageException {
500                throw new ExpressionException("can't compare XML NodeList with a numeric value");
501        }
502
503        @Override
504        public int compareTo(String str) throws PageException {
505                throw new ExpressionException("can't compare XML NodeList with a String");
506        }
507
508        @Override
509        public long sizeOf() {
510                return ArrayUtil.sizeOf((List)this);
511        }
512}