001    /*
002     * Copyright (c) 1997, 2010, Oracle and/or its affiliates. All rights reserved.
003     * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
004     *
005     *
006     *
007     *
008     *
009     *
010     *
011     *
012     *
013     *
014     *
015     *
016     *
017     *
018     *
019     *
020     *
021     *
022     *
023     *
024     */
025    
026    package railo.commons.util.mod;
027    
028    import java.io.Externalizable;
029    import java.util.Collection;
030    import java.util.HashMap;
031    import java.util.Iterator;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import railo.runtime.exp.ExpressionException;
036    import railo.runtime.exp.PageException;
037    
038    public abstract class AbstractMapPro<K,V> implements MapPro<K,V>,Externalizable {
039        /**
040         * Sole constructor.  (For invocation by subclass constructors, typically
041         * implicit.)
042         */
043        protected AbstractMapPro() {
044        }
045        
046        // TODO better implementation
047        @Override
048        public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
049            out.writeObject(new HashMap(this));
050        }
051        @Override
052        public void readExternal(java.io.ObjectInput in) throws java.io.IOException, ClassNotFoundException {
053            this.putAll((Map)in.readObject());
054        }
055        
056        
057    
058        // Query Operations
059    
060        /**
061         * {@inheritDoc}
062         *
063         * <p>This implementation returns <tt>entrySet().size()</tt>.
064         */
065        public int size() {
066            return entrySet().size();
067        }
068    
069        /**
070         * {@inheritDoc}
071         *
072         * <p>This implementation returns <tt>size() == 0</tt>.
073         */
074        public boolean isEmpty() {
075            return size() == 0;
076        }
077    
078        /**
079         * {@inheritDoc}
080         *
081         * <p>This implementation iterates over <tt>entrySet()</tt> searching
082         * for an entry with the specified value.  If such an entry is found,
083         * <tt>true</tt> is returned.  If the iteration terminates without
084         * finding such an entry, <tt>false</tt> is returned.  Note that this
085         * implementation requires linear time in the size of the map.
086         *
087         * @throws ClassCastException   {@inheritDoc}
088         * @throws NullPointerException {@inheritDoc}
089         */
090        public boolean containsValue(Object value) {
091            Iterator<Entry<K,V>> i = entrySet().iterator();
092            if (value==null) {
093                while (i.hasNext()) {
094                    Entry<K,V> e = i.next();
095                    if (e.getValue()==null)
096                        return true;
097                }
098            } else {
099                while (i.hasNext()) {
100                    Entry<K,V> e = i.next();
101                    if (value.equals(e.getValue()))
102                        return true;
103                }
104            }
105            return false;
106        }
107    
108        /**
109         * {@inheritDoc}
110         *
111         * <p>This implementation iterates over <tt>entrySet()</tt> searching
112         * for an entry with the specified key.  If such an entry is found,
113         * <tt>true</tt> is returned.  If the iteration terminates without
114         * finding such an entry, <tt>false</tt> is returned.  Note that this
115         * implementation requires linear time in the size of the map; many
116         * implementations will override this method.
117         *
118         * @throws ClassCastException   {@inheritDoc}
119         * @throws NullPointerException {@inheritDoc}
120         */
121        public boolean containsKey(Object key) {
122            Iterator<Map.Entry<K,V>> i = entrySet().iterator();
123            if (key==null) {
124                while (i.hasNext()) {
125                    Entry<K,V> e = i.next();
126                    if (e.getKey()==null)
127                        return true;
128                }
129            } else {
130                while (i.hasNext()) {
131                    Entry<K,V> e = i.next();
132                    if (key.equals(e.getKey()))
133                        return true;
134                }
135            }
136            return false;
137        }
138    
139        /**
140         * {@inheritDoc}
141         *
142         * <p>This implementation iterates over <tt>entrySet()</tt> searching
143         * for an entry with the specified key.  If such an entry is found,
144         * the entry's value is returned.  If the iteration terminates without
145         * finding such an entry, <tt>null</tt> is returned.  Note that this
146         * implementation requires linear time in the size of the map; many
147         * implementations will override this method.
148         *
149         * @throws ClassCastException            {@inheritDoc}
150         * @throws NullPointerException          {@inheritDoc}
151         */
152        public V get(Object key) {
153            Iterator<Entry<K,V>> i = entrySet().iterator();
154            if (key==null) {
155                while (i.hasNext()) {
156                    Entry<K,V> e = i.next();
157                    if (e.getKey()==null)
158                        return e.getValue();
159                }
160            } else {
161                while (i.hasNext()) {
162                    Entry<K,V> e = i.next();
163                    if (key.equals(e.getKey()))
164                        return e.getValue();
165                }
166            }
167            return null;
168        }
169    
170    
171        // Modification Operations
172    
173        /**
174         * {@inheritDoc}
175         *
176         * <p>This implementation always throws an
177         * <tt>UnsupportedOperationException</tt>.
178         *
179         * @throws UnsupportedOperationException {@inheritDoc}
180         * @throws ClassCastException            {@inheritDoc}
181         * @throws NullPointerException          {@inheritDoc}
182         * @throws IllegalArgumentException      {@inheritDoc}
183         */
184        public V put(K key, V value) {
185            throw new UnsupportedOperationException();
186        }
187    
188        /**
189         * {@inheritDoc}
190         *
191         * <p>This implementation iterates over <tt>entrySet()</tt> searching for an
192         * entry with the specified key.  If such an entry is found, its value is
193         * obtained with its <tt>getValue</tt> operation, the entry is removed
194         * from the collection (and the backing map) with the iterator's
195         * <tt>remove</tt> operation, and the saved value is returned.  If the
196         * iteration terminates without finding such an entry, <tt>null</tt> is
197         * returned.  Note that this implementation requires linear time in the
198         * size of the map; many implementations will override this method.
199         *
200         * <p>Note that this implementation throws an
201         * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt>
202         * iterator does not support the <tt>remove</tt> method and this map
203         * contains a mapping for the specified key.
204         *
205         * @throws UnsupportedOperationException {@inheritDoc}
206         * @throws ClassCastException            {@inheritDoc}
207         * @throws NullPointerException          {@inheritDoc}
208         */
209        public V remove(Object key) {
210            Iterator<Entry<K,V>> i = entrySet().iterator();
211            Entry<K,V> correctEntry = null;
212            if (key==null) {
213                while (correctEntry==null && i.hasNext()) {
214                    Entry<K,V> e = i.next();
215                    if (e.getKey()==null)
216                        correctEntry = e;
217                }
218            } else {
219                while (correctEntry==null && i.hasNext()) {
220                    Entry<K,V> e = i.next();
221                    if (key.equals(e.getKey()))
222                        correctEntry = e;
223                }
224            }
225    
226            V oldValue = null;
227            if (correctEntry !=null) {
228                oldValue = correctEntry.getValue();
229                i.remove();
230            }
231            return oldValue;
232        }
233    
234    
235        // Bulk Operations
236    
237        /**
238         * {@inheritDoc}
239         *
240         * <p>This implementation iterates over the specified map's
241         * <tt>entrySet()</tt> collection, and calls this map's <tt>put</tt>
242         * operation once for each entry returned by the iteration.
243         *
244         * <p>Note that this implementation throws an
245         * <tt>UnsupportedOperationException</tt> if this map does not support
246         * the <tt>put</tt> operation and the specified map is nonempty.
247         *
248         * @throws UnsupportedOperationException {@inheritDoc}
249         * @throws ClassCastException            {@inheritDoc}
250         * @throws NullPointerException          {@inheritDoc}
251         * @throws IllegalArgumentException      {@inheritDoc}
252         */
253        public void putAll(Map<? extends K, ? extends V> m) {
254            for (Map.Entry<? extends K, ? extends V> e : m.entrySet())
255                put(e.getKey(), e.getValue());
256        }
257    
258        /**
259         * {@inheritDoc}
260         *
261         * <p>This implementation calls <tt>entrySet().clear()</tt>.
262         *
263         * <p>Note that this implementation throws an
264         * <tt>UnsupportedOperationException</tt> if the <tt>entrySet</tt>
265         * does not support the <tt>clear</tt> operation.
266         *
267         * @throws UnsupportedOperationException {@inheritDoc}
268         */
269        public void clear() {
270            entrySet().clear();
271        }
272    
273    
274        // Views
275    
276        /**
277         * Each of these fields are initialized to contain an instance of the
278         * appropriate view the first time this view is requested.  The views are
279         * stateless, so there's no reason to create more than one of each.
280         */
281        transient volatile Set<K>        keySet = null;
282        transient volatile Collection<V> values = null;
283    
284        /**
285         * {@inheritDoc}
286         *
287         * <p>This implementation returns a set that subclasses {@link AbstractSet}.
288         * The subclass's iterator method returns a "wrapper object" over this
289         * map's <tt>entrySet()</tt> iterator.  The <tt>size</tt> method
290         * delegates to this map's <tt>size</tt> method and the
291         * <tt>contains</tt> method delegates to this map's
292         * <tt>containsKey</tt> method.
293         *
294         * <p>The set is created the first time this method is called,
295         * and returned in response to all subsequent calls.  No synchronization
296         * is performed, so there is a slight chance that multiple calls to this
297         * method will not all return the same set.
298         */
299        public Set<K> keySet() {
300            if (keySet == null) {
301                keySet = new AbstractSet<K>() {
302                    public Iterator<K> iterator() {
303                        return new Iterator<K>() {
304                            private Iterator<Entry<K,V>> i = entrySet().iterator();
305    
306                            public boolean hasNext() {
307                                return i.hasNext();
308                            }
309    
310                            public K next() {
311                                return i.next().getKey();
312                            }
313    
314                            public void remove() {
315                                i.remove();
316                            }
317                        };
318                    }
319    
320                    public int size() {
321                        return AbstractMapPro.this.size();
322                    }
323    
324                    public boolean isEmpty() {
325                        return AbstractMapPro.this.isEmpty();
326                    }
327    
328                    public void clear() {
329                        AbstractMapPro.this.clear();
330                    }
331    
332                    public boolean contains(Object k) {
333                        return AbstractMapPro.this.containsKey(k);
334                    }
335                };
336            }
337            return keySet;
338        }
339    
340        /**
341         * {@inheritDoc}
342         *
343         * <p>This implementation returns a collection that subclasses {@link
344         * AbstractCollection}.  The subclass's iterator method returns a
345         * "wrapper object" over this map's <tt>entrySet()</tt> iterator.
346         * The <tt>size</tt> method delegates to this map's <tt>size</tt>
347         * method and the <tt>contains</tt> method delegates to this map's
348         * <tt>containsValue</tt> method.
349         *
350         * <p>The collection is created the first time this method is called, and
351         * returned in response to all subsequent calls.  No synchronization is
352         * performed, so there is a slight chance that multiple calls to this
353         * method will not all return the same collection.
354         */
355        public Collection<V> values() {
356            if (values == null) {
357                values = new AbstractCollection<V>() {
358                    public Iterator<V> iterator() {
359                        return new Iterator<V>() {
360                            private Iterator<Entry<K,V>> i = entrySet().iterator();
361    
362                            public boolean hasNext() {
363                                return i.hasNext();
364                            }
365    
366                            public V next() {
367                                return i.next().getValue();
368                            }
369    
370                            public void remove() {
371                                i.remove();
372                            }
373                        };
374                    }
375    
376                    public int size() {
377                        return AbstractMapPro.this.size();
378                    }
379    
380                    public boolean isEmpty() {
381                        return AbstractMapPro.this.isEmpty();
382                    }
383    
384                    public void clear() {
385                        AbstractMapPro.this.clear();
386                    }
387    
388                    public boolean contains(Object v) {
389                        return AbstractMapPro.this.containsValue(v);
390                    }
391                };
392            }
393            return values;
394        }
395    
396        public abstract Set<Entry<K,V>> entrySet();
397    
398    
399        // Comparison and hashing
400    
401        /**
402         * Compares the specified object with this map for equality.  Returns
403         * <tt>true</tt> if the given object is also a map and the two maps
404         * represent the same mappings.  More formally, two maps <tt>m1</tt> and
405         * <tt>m2</tt> represent the same mappings if
406         * <tt>m1.entrySet().equals(m2.entrySet())</tt>.  This ensures that the
407         * <tt>equals</tt> method works properly across different implementations
408         * of the <tt>Map</tt> interface.
409         *
410         * <p>This implementation first checks if the specified object is this map;
411         * if so it returns <tt>true</tt>.  Then, it checks if the specified
412         * object is a map whose size is identical to the size of this map; if
413         * not, it returns <tt>false</tt>.  If so, it iterates over this map's
414         * <tt>entrySet</tt> collection, and checks that the specified map
415         * contains each mapping that this map contains.  If the specified map
416         * fails to contain such a mapping, <tt>false</tt> is returned.  If the
417         * iteration completes, <tt>true</tt> is returned.
418         *
419         * @param o object to be compared for equality with this map
420         * @return <tt>true</tt> if the specified object is equal to this map
421         */
422        public boolean equals(Object o) {
423            if (o == this)
424                return true;
425    
426            if (!(o instanceof Map))
427                return false;
428            Map<K,V> m = (Map<K,V>) o;
429            if (m.size() != size())
430                return false;
431    
432            try {
433                Iterator<Entry<K,V>> i = entrySet().iterator();
434                while (i.hasNext()) {
435                    Entry<K,V> e = i.next();
436                    K key = e.getKey();
437                    V value = e.getValue();
438                    if (value == null) {
439                        if (!(m.get(key)==null && m.containsKey(key)))
440                            return false;
441                    } else {
442                        if (!value.equals(m.get(key)))
443                            return false;
444                    }
445                }
446            } catch (ClassCastException unused) {
447                return false;
448            } catch (NullPointerException unused) {
449                return false;
450            }
451    
452            return true;
453        }
454    
455        // TODO add again current implementation creates a infinti loop when child=this
456        /*public int hashCode() {
457            int h = 0;
458            Iterator<Entry<K,V>> i = entrySet().iterator();
459            while (i.hasNext())
460                h += i.next().hashCode();
461            return h;
462        }*/
463    
464        /**
465         * Returns a string representation of this map.  The string representation
466         * consists of a list of key-value mappings in the order returned by the
467         * map's <tt>entrySet</tt> view's iterator, enclosed in braces
468         * (<tt>"{}"</tt>).  Adjacent mappings are separated by the characters
469         * <tt>", "</tt> (comma and space).  Each key-value mapping is rendered as
470         * the key followed by an equals sign (<tt>"="</tt>) followed by the
471         * associated value.  Keys and values are converted to strings as by
472         * {@link String#valueOf(Object)}.
473         *
474         * @return a string representation of this map
475         */
476        public String toString() {
477            Iterator<Entry<K,V>> i = entrySet().iterator();
478            if (! i.hasNext())
479                return "{}";
480    
481            StringBuilder sb = new StringBuilder();
482            sb.append('{');
483            for (;;) {
484                Entry<K,V> e = i.next();
485                K key = e.getKey();
486                V value = e.getValue();
487                sb.append(key   == this ? "(this Map)" : key);
488                sb.append('=');
489                sb.append(value == this ? "(this Map)" : value);
490                if (! i.hasNext())
491                    return sb.append('}').toString();
492                sb.append(',').append(' ');
493            }
494        }
495    
496        /**
497         * Returns a shallow copy of this <tt>AbstractMap</tt> instance: the keys
498         * and values themselves are not cloned.
499         *
500         * @return a shallow copy of this map
501         */
502        protected Object clone() throws CloneNotSupportedException {
503            AbstractMapPro<K,V> result = (AbstractMapPro<K,V>)super.clone();
504            result.keySet = null;
505            result.values = null;
506            return result;
507        }
508    
509        /**
510         * Utility method for SimpleEntry and SimpleImmutableEntry.
511         * Test for equality, checking for nulls.
512         */
513        private static boolean eq(Object o1, Object o2) {
514            return o1 == null ? o2 == null : o1.equals(o2);
515        }
516    
517        // Implementation Note: SimpleEntry and SimpleImmutableEntry
518        // are distinct unrelated classes, even though they share
519        // some code. Since you can't add or subtract final-ness
520        // of a field in a subclass, they can't share representations,
521        // and the amount of duplicated code is too small to warrant
522        // exposing a common abstract class.
523    
524    
525        /**
526         * An Entry maintaining a key and a value.  The value may be
527         * changed using the <tt>setValue</tt> method.  This class
528         * facilitates the process of building custom map
529         * implementations. For example, it may be convenient to return
530         * arrays of <tt>SimpleEntry</tt> instances in method
531         * <tt>Map.entrySet().toArray</tt>.
532         *
533         * @since 1.6
534         */
535        public static class SimpleEntry<K,V>
536            implements Entry<K,V>, java.io.Serializable
537        {
538            private static final long serialVersionUID = -8499721149061103585L;
539    
540            private final K key;
541            private V value;
542    
543            /**
544             * Creates an entry representing a mapping from the specified
545             * key to the specified value.
546             *
547             * @param key the key represented by this entry
548             * @param value the value represented by this entry
549             */
550            public SimpleEntry(K key, V value) {
551                this.key   = key;
552                this.value = value;
553            }
554    
555            /**
556             * Creates an entry representing the same mapping as the
557             * specified entry.
558             *
559             * @param entry the entry to copy
560             */
561            public SimpleEntry(Entry<? extends K, ? extends V> entry) {
562                this.key   = entry.getKey();
563                this.value = entry.getValue();
564            }
565    
566            /**
567             * Returns the key corresponding to this entry.
568             *
569             * @return the key corresponding to this entry
570             */
571            public K getKey() {
572                return key;
573            }
574    
575            /**
576             * Returns the value corresponding to this entry.
577             *
578             * @return the value corresponding to this entry
579             */
580            public V getValue() {
581                return value;
582            }
583    
584            /**
585             * Replaces the value corresponding to this entry with the specified
586             * value.
587             *
588             * @param value new value to be stored in this entry
589             * @return the old value corresponding to the entry
590             */
591            public V setValue(V value) {
592                V oldValue = this.value;
593                this.value = value;
594                return oldValue;
595            }
596    
597            @Override
598            public boolean equals(Object o) {
599                if (!(o instanceof Map.Entry))
600                    return false;
601                Map.Entry e = (Map.Entry)o;
602                return eq(key, e.getKey()) && eq(value, e.getValue());
603            }
604    
605            @Override
606            public int hashCode() {
607                return (key   == null ? 0 :   key.hashCode()) ^
608                       (value == null ? 0 : value.hashCode());
609            }
610    
611            /**
612             * Returns a String representation of this map entry.  This
613             * implementation returns the string representation of this
614             * entry's key followed by the equals character ("<tt>=</tt>")
615             * followed by the string representation of this entry's value.
616             *
617             * @return a String representation of this map entry
618             */
619            public String toString() {
620                return key + "=" + value;
621            }
622    
623        }
624    
625        /**
626         * An Entry maintaining an immutable key and value.  This class
627         * does not support method <tt>setValue</tt>.  This class may be
628         * convenient in methods that return thread-safe snapshots of
629         * key-value mappings.
630         *
631         * @since 1.6
632         */
633        public static class SimpleImmutableEntry<K,V>
634            implements Entry<K,V>, java.io.Serializable
635        {
636            private static final long serialVersionUID = 7138329143949025153L;
637    
638            private final K key;
639            private final V value;
640    
641            /**
642             * Creates an entry representing a mapping from the specified
643             * key to the specified value.
644             *
645             * @param key the key represented by this entry
646             * @param value the value represented by this entry
647             */
648            public SimpleImmutableEntry(K key, V value) {
649                this.key   = key;
650                this.value = value;
651            }
652    
653            /**
654             * Creates an entry representing the same mapping as the
655             * specified entry.
656             *
657             * @param entry the entry to copy
658             */
659            public SimpleImmutableEntry(Entry<? extends K, ? extends V> entry) {
660                this.key   = entry.getKey();
661                this.value = entry.getValue();
662            }
663    
664            /**
665             * Returns the key corresponding to this entry.
666             *
667             * @return the key corresponding to this entry
668             */
669            public K getKey() {
670                return key;
671            }
672    
673            /**
674             * Returns the value corresponding to this entry.
675             *
676             * @return the value corresponding to this entry
677             */
678            public V getValue() {
679                return value;
680            }
681    
682            /**
683             * Replaces the value corresponding to this entry with the specified
684             * value (optional operation).  This implementation simply throws
685             * <tt>UnsupportedOperationException</tt>, as this class implements
686             * an <i>immutable</i> map entry.
687             *
688             * @param value new value to be stored in this entry
689             * @return (Does not return)
690             * @throws UnsupportedOperationException always
691             */
692            public V setValue(V value) {
693                throw new UnsupportedOperationException();
694            }
695    
696            @Override
697            public boolean equals(Object o) {
698                if (!(o instanceof Map.Entry))
699                    return false;
700                Map.Entry e = (Map.Entry)o;
701                return eq(key, e.getKey()) && eq(value, e.getValue());
702            }
703    
704            @Override
705            public int hashCode() {
706                return (key   == null ? 0 :   key.hashCode()) ^
707                       (value == null ? 0 : value.hashCode());
708            }
709    
710            /**
711             * Returns a String representation of this map entry.  This
712             * implementation returns the string representation of this
713             * entry's key followed by the equals character ("<tt>=</tt>")
714             * followed by the string representation of this entry's value.
715             *
716             * @return a String representation of this map entry
717             */
718            public String toString() {
719                return key + "=" + value;
720            }
721    
722        }
723        
724        public static PageException invalidKey(Map map,Object key, boolean remove) {
725    
726                    StringBuilder sb=new StringBuilder();
727                    Iterator it = map.keySet().iterator();
728                    Object k;
729                    while(it.hasNext()){
730                            k = it.next();
731                            if(sb.length()>0)sb.append(',');
732                            sb.append(k.toString());
733                    }
734                    return new ExpressionException(
735                                    (remove?
736                                                    "cannot remove key ["+key+"] from struct, key doesn't exist":
737                                                    "key [" + key + "] doesn't exist") +
738                                    " (existing keys:" + sb.toString() + ")" );
739            }
740    
741    }