View Javadoc

1   /*
2    * Copyright [2005] [University Corporation for Advanced Internet Development, Inc.]
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  package org.opensaml.xml.util;
18  
19  import java.util.AbstractList;
20  import java.util.ArrayList;
21  import java.util.Collection;
22  import java.util.List;
23  import java.util.Map;
24  import java.util.concurrent.ConcurrentHashMap;
25  
26  import javax.xml.namespace.QName;
27  
28  import org.opensaml.xml.XMLObject;
29  
30  /**
31   * A list which indexes XMLObjects by their schema type and element QName for quick retrival based on those items.
32   * 
33   * @param <ElementType> the type of element added to the list
34   */
35  public class IndexedXMLObjectChildrenList<ElementType extends XMLObject> extends XMLObjectChildrenList<ElementType> {
36  
37      /** Index of objects by type and name. */
38      private Map<QName, ArrayList<ElementType>> objectIndex;
39  
40      /**
41       * Constructor.
42       * 
43       * @param parent the parent of the {@link XMLObject}s added to the list
44       */
45      public IndexedXMLObjectChildrenList(XMLObject parent) {
46          super(parent);
47          objectIndex = new ConcurrentHashMap<QName, ArrayList<ElementType>>();
48      }
49  
50      /**
51       * Constructor.
52       * 
53       * @param parent the parent of all elements
54       * @param col collection to add to this list
55       */
56      public IndexedXMLObjectChildrenList(XMLObject parent, Collection<ElementType> col) {
57          super(parent);
58          objectIndex = new ConcurrentHashMap<QName, ArrayList<ElementType>>();
59          addAll(col);
60      }
61  
62      /** {@inheritDoc} */
63      public void clear() {
64          super.clear();
65          objectIndex.clear();
66      }
67  
68      /**
69       * Retrieves all the SAMLObjects that have given schema type or element name.
70       * 
71       * @param typeOrName the schema type or element name
72       * 
73       * @return list of SAMLObjects that have given schema type or element name or null
74       */
75      public List<ElementType> get(QName typeOrName) {
76          return objectIndex.get(typeOrName);
77      }
78  
79      /**
80       * Replaces the element at the specified position in this list with the specified element.
81       * 
82       * @param index index of element to replace
83       * @param element element to be stored at the specified position
84       * 
85       * @return the element previously at the specified position
86       */
87      public ElementType set(int index, ElementType element) {
88          ElementType returnValue = super.set(index, element);
89  
90          removeElementFromIndex(returnValue);
91  
92          indexElement(element);
93          return returnValue;
94      }
95  
96      /**
97       * Inserts the specified element at the specified position in this list. Shifts the element currently at that
98       * position (if any) and any subsequent elements to the right (adds one to their indices).
99       * 
100      * @param index index of element to add
101      * @param element element to be stored at the specified position
102      */
103     public void add(int index, ElementType element) {
104         super.add(index, element);
105         indexElement(element);
106     }
107 
108     /**
109      * Removes the element at the specified position in this list. Shifts any subsequent elements to the left (subtracts
110      * one from their indices). Returns the element that was removed from the list
111      * 
112      * @param index the index of the element to remove
113      * 
114      * @return the element removed from the list
115      */
116     public ElementType remove(int index) {
117         ElementType returnValue = super.remove(index);
118 
119         removeElementFromIndex(returnValue);
120 
121         return returnValue;
122     }
123 
124     /**
125      * Removes a given element from the list and index.
126      * 
127      * @param element the element to be removed
128      * 
129      * @return true if the element was in the list and removed, false if not
130      */
131     public boolean remove(ElementType element) {
132         boolean elementRemoved = false;
133 
134         elementRemoved = super.remove(element);
135         if (elementRemoved) {
136             removeElementFromIndex(element);
137         }
138 
139         return elementRemoved;
140     }
141 
142     /**
143      * Returns a view of the list that only contains elements stored under the given index. The returned list is backed
144      * by this list so and supports all optional operations, so changes made to the returned list are reflected in this
145      * list.
146      * 
147      * @param index index of the elements returned in the list view
148      * 
149      * @return a view of this list that contains only the elements stored under the given index
150      */
151     public List<? extends ElementType> subList(QName index) {
152         if (!objectIndex.containsKey(index)) {
153             objectIndex.put(index, new ArrayList<ElementType>());
154         }
155 
156         return new ListView<ElementType>(this, index);
157     }
158 
159     /**
160      * Indexes the given SAMLObject by type and element name.
161      * 
162      * @param element the SAMLObject to index
163      */
164     protected void indexElement(ElementType element) {
165         if (element == null) {
166             return;
167         }
168 
169         QName type = element.getSchemaType();
170         if (type != null) {
171             indexElement(type, element);
172         }
173 
174         indexElement(element.getElementQName(), element);
175     }
176 
177     /**
178      * Indexes the given SAMLobject by the given index.
179      * 
180      * @param index the index for the element
181      * @param element the element to be indexed
182      */
183     protected void indexElement(QName index, ElementType element) {
184         ArrayList<ElementType> objects = objectIndex.get(index);
185         if (objects == null) {
186             objects = new ArrayList<ElementType>();
187             objectIndex.put(index, objects);
188         }
189 
190         objects.add(element);
191     }
192 
193     /**
194      * Removes the given element from the schema type and element qname index.
195      * 
196      * @param element the element to remove from the index
197      */
198     protected void removeElementFromIndex(ElementType element) {
199         if (element == null) {
200             return;
201         }
202 
203         QName type = element.getSchemaType();
204         if (type != null) {
205             removeElementFromIndex(type, element);
206         }
207 
208         removeElementFromIndex(element.getElementQName(), element);
209     }
210 
211     /**
212      * Removes an object from the given index id.
213      * 
214      * @param index the id of the index
215      * @param element the element to be removed from that index
216      */
217     protected void removeElementFromIndex(QName index, ElementType element) {
218         ArrayList<ElementType> objects = objectIndex.get(index);
219         if (objects != null) {
220             objects.remove(element);
221         }
222 
223         if (objects.size() == 0) {
224             objectIndex.remove(index);
225         }
226     }
227 }
228 
229 /**
230  * A special list that works as a view of an IndexedXMLObjectChildrenList showing only the sublist associated with a
231  * given index. Operations performed on this sublist are reflected in the backing list.
232  * 
233  * @param <ElementType> the XMLObject type that this list operates on
234  */
235 class ListView<ElementType extends XMLObject> extends AbstractList<ElementType> {
236 
237     /** List that backs this view. */
238     private IndexedXMLObjectChildrenList<ElementType> backingList;
239 
240     /** Index that points to the list, in the backing list, that this view operates on. */
241     private QName index;
242 
243     /** List, in the backing list, that the given index points to. */
244     private List<ElementType> indexList;
245 
246     /**
247      * Constructor.
248      * 
249      * @param newBackingList the list that backs this view
250      * @param newIndex the element schema type or name of the sublist this view operates on
251      */
252     public ListView(IndexedXMLObjectChildrenList<ElementType> newBackingList, QName newIndex) {
253         backingList = newBackingList;
254         index = newIndex;
255         indexList = backingList.get(index);
256     }
257 
258     /**
259      * Checks to see if the given element is contained in this list.
260      * 
261      * @param element the element to check for
262      * 
263      * @return true if the element is in this list, false if not
264      */
265     public boolean contains(ElementType element) {
266         return indexList.contains(element);
267     }
268 
269     /** {@inheritDoc} */
270     public ElementType get(int newIndex) {
271         return indexList.get(newIndex);
272     }
273 
274     /** {@inheritDoc} */
275     public int size() {
276         return indexList.size();
277     }
278 
279     /** {@inheritDoc} */
280     public ElementType set(int newIndex, ElementType element) {
281 
282         if (newIndex < 0 && newIndex > indexList.size()) {
283             throw new IndexOutOfBoundsException();
284         }
285 
286         ElementType replacedElement;
287         int elementIndex;
288 
289         replacedElement = indexList.get(newIndex);
290         elementIndex = backingList.indexOf(replacedElement);
291         backingList.set(elementIndex, element);
292 
293         return replacedElement;
294     }
295 
296     /** {@inheritDoc} */
297     public void add(int newIndex, ElementType element) {
298         indexCheck(element);
299         backingList.add(element);
300     }
301 
302     /** {@inheritDoc} */
303     public ElementType remove(int newIndex) {
304         return backingList.remove(newIndex);
305     }
306 
307     /**
308      * Checks to make sure the given element schema type or name matches the index given at construction time.
309      * 
310      * @param element the element to check
311      * 
312      * @throws IllegalArgumentException thrown if the element schema type or name does not match the index
313      */
314     protected void indexCheck(ElementType element) throws IllegalArgumentException {
315         if (index.equals(element.getSchemaType()) || index.equals(element.getElementQName())) {
316             return;
317         } else {
318             throw new IllegalArgumentException("Element " + element.getElementQName() + " is not of type " + index);
319         }
320     }
321 }