View Javadoc

1   /*
2    * Copyright (C) 2003-2004 Christian Siefkes <christian@siefkes.net>.
3    * Development of this software is supported by the German Research Society,
4    * Berlin-Brandenburg Graduate School in Distributed Information Systems
5    * (DFG grant no. GRK 316).
6    *
7    * This library is free software; you can redistribute it and/or
8    * modify it under the terms of the GNU Lesser General Public
9    * License as published by the Free Software Foundation; either
10   * version 2.1 of the License, or (at your option) any later version.
11   *
12   * This library is distributed in the hope that it will be useful,
13   * but WITHOUT ANY WARRANTY; without even the implied warranty of
14   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15   * Lesser General Public License for more details.
16   *
17   * You should have received a copy of the GNU Lesser General Public
18   * License along with this library; if not, visit
19   * http://www.gnu.org/licenses/lgpl.html or write to the Free Software
20   * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
21   */
22  package de.fu_berlin.ties.xml;
23  
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.Map;
28  import java.util.SortedMap;
29  import java.util.TreeMap;
30  
31  import org.apache.commons.lang.builder.ToStringBuilder;
32  
33  /***
34   * This class manages tag sequences by keeping track of names and appearances
35   * of tags. It is a helper class used for XML adjustment.
36   * Instances of this class are <em>not</em> thread-safe.
37   *
38   * @author Christian Siefkes
39   * @version $Revision: 1.2 $, $Date: 2004/09/07 10:01:48 $, $Author: siefkes $
40   */
41  public class TagContainer {
42  
43      /***
44       * Stores the mappings from tag names Strings to nested TreeMaps
45       * mapping markup series Integers to LinkedLists of TagConstituents.
46       */
47      private final Map<String, SortedMap<Integer, LinkedList<TagConstituent>>>
48          tagMap =
49          new HashMap<String, SortedMap<Integer, LinkedList<TagConstituent>>>();
50  
51      /***
52       * Creates a new instance.
53       */
54      public TagContainer() {
55      }
56  
57      /***
58       * Checks whether this instance contains at least one appearance of the
59       * specified tag.
60       *
61       * @param tagname the name of the tag to check
62       * @return <code>true</code> iff the container contains one or more
63       * appearances of the specified tag
64       */
65      public boolean contains(final String tagname) {
66          return tagMap.containsKey(tagname);
67      }
68  
69      /***
70       * Finds the first appearance of a tag.
71       * If the specified tag did never appear within the requested markup series,
72       * <code>null</code> is returned instead.
73       *
74       * @param tagname the name of the tag to check
75       * @return the found apearance of the tag, or <code>null</code> if no
76       * matching appearance was found
77       */
78      public TagConstituent findFirst(final String tagname) {
79          // check entries for tag name
80          final SortedMap nestedMap = tagMap.get(tagname);
81  
82          if ((nestedMap != null) && !nestedMap.isEmpty()) {
83              final Iterator keyIter = nestedMap.keySet().iterator();
84              Integer currentKey;
85              LinkedList currentTags;
86  
87              while (keyIter.hasNext()) {
88                  currentKey = (Integer) keyIter.next();
89                  currentTags = (LinkedList) nestedMap.get(currentKey);
90  
91                  if ((currentTags != null) && !currentTags.isEmpty()) {
92                      // return first appearance
93                      return (TagConstituent) currentTags.getFirst();
94                  }
95              }
96          }
97  
98          // no appearance found
99          return null;
100     }
101 
102     /***
103      * Finds the appearance of a tag within a specified markup series.
104      * If the specified tag didn't appear within the requested markup series,
105      * <code>null</code> is returned instead.
106      *
107      * @param tagname the name of the tag to check
108      * @param markupSeriesNo the markup series number to match
109      * @param returnFirst whether to return the first or the last
110      * appearance of the tag within the specified series, if several
111      * appearances are found
112      * @return the found apearance of the tag, or <code>null</code> if no
113      * matching appearance was found
114      */
115     public TagConstituent findInSeries(final String tagname,
116             final int markupSeriesNo, final boolean returnFirst) {
117         // check entries for tag name
118         final SortedMap nestedMap = tagMap.get(tagname);
119         final TagConstituent result;
120 
121         if ((nestedMap != null) && !nestedMap.isEmpty()) {
122             // check entries of specified markup series
123             final Integer keyToUse = new Integer(markupSeriesNo);
124             final LinkedList tagsInSeries =
125                 (LinkedList) nestedMap.get(keyToUse);
126 
127             if ((tagsInSeries != null) && !tagsInSeries.isEmpty()) {
128                 // return first resp. last appearance
129                 if (returnFirst) {
130                     result = (TagConstituent) tagsInSeries.getFirst();
131                 } else {
132                     result = (TagConstituent) tagsInSeries.getLast();
133                 }
134             } else {
135                 result = null;
136             }
137         } else {
138             result = null;
139         }
140         return result;
141     }
142 
143     /***
144      * Forces the removal of a single appearance of a tag from the
145      * container. Throws a RuntimeException if the appearance could not be
146      * removed, i.e. it didn't not exist in the container. Use this method
147      * if you are sure that the specified appearance exists.
148      *
149      * @param tag the tag to remove
150      * @throws RuntimeException stating an "Implementation error" if the
151      * specified tag appearance could <em>not</em> be removed
152      */
153     public final void forceRemove(final TagConstituent tag)
154             throws RuntimeException {
155         if (!remove(tag)) {
156             // remove failed
157             throw new RuntimeException("Implementation error: forced removal "
158                 + "failed for tag " + tag);
159         }
160     }
161 
162     /***
163      * Whether this tag container is empty.
164      *
165      * @return <code>true</code> iff this container is empty, i.e. doesn't
166      * contain any tags
167      */
168     public boolean isEmpty() {
169         return tagMap.isEmpty();
170     }
171 
172     /***
173      * Grants subclasses direct access to the map that is used internally for
174      * managing the tags. Maps tag names Strings to nested TreeMaps mapping
175      * markup series Integers to LinkedLists of TagConstituents.
176      *
177      * @return the tag map manages by this class
178      */
179     protected Map getTagMap() {
180         return tagMap;
181     }
182 
183     /***
184      * Inserts an appearance of a tag into the container. The name of the
185      * tag is used as key; the whole constituent is appended at the end of the
186      * list of appearances stored for this tag name.
187      *
188      * @param tag the tag to insert
189      */
190     public final void push(final TagConstituent tag) {
191         push(tag, true);
192     }
193 
194     /***
195      * Inserts an appearance of a tag into the container. The name of the
196      * tag is used as key; the whole constituent is inserted in the list of
197      * appearances stored for this tag name.
198      *
199      * @param tag the tag to insert
200      * @param appendAtEnd whether to insert the tag at the end or at the begin
201      * of appearances within a series
202      * @throws UnsupportedOperationException might be thrown by child classes
203      * that don't support prepending if <code>appendAtEnd</code> is
204      * <code>false</code>
205      */
206     public void push(final TagConstituent tag, final boolean appendAtEnd)
207             throws UnsupportedOperationException {
208         final String tagname = tag.getName();
209         final Integer seriesNo = new Integer(tag.getMarkupSeriesNo());
210 
211         // check entries for tag name
212         SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
213             tagMap.get(tagname);
214         if (nestedMap == null) {
215             // create and insert new map
216             nestedMap = new TreeMap<Integer, LinkedList<TagConstituent>>();
217             tagMap.put(tagname, nestedMap);
218         }
219 
220         // check entries within series no.
221         LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
222         if (tagsInSeries == null) {
223             // create and insert new list
224             tagsInSeries = new LinkedList<TagConstituent>();
225             nestedMap.put(seriesNo, tagsInSeries);
226         }
227 
228         // add tag to list
229         if (appendAtEnd) {
230             tagsInSeries.addLast(tag);
231         } else {
232             tagsInSeries.addFirst(tag);
233         }
234     }
235 
236     /***
237      * Removes a single appearance of a tag from the container.
238      *
239      * @param tag the tag to remove
240      * @return <code>true</code> if the specified TagConstituent was
241      * removed successfully; <code>false</code> otherwise (the specified
242      * constituent wasn't found in the container)
243      */
244     public boolean remove(final TagConstituent tag) {
245         final String tagname = tag.getName();
246         final Integer seriesNo = new Integer(tag.getMarkupSeriesNo());
247 
248         // check entries for tag name
249         SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
250             tagMap.get(tagname);
251         if (nestedMap == null) {
252             return false;
253         }
254 
255         // check entries within series no.
256         LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
257         if (tagsInSeries == null) {
258             return false;
259         }
260         final boolean removedFromSeries = tagsInSeries.remove(tag);
261 
262         if (removedFromSeries && tagsInSeries.isEmpty()) {
263             // inner list is now empty -- remove it
264             nestedMap.remove(seriesNo);
265             if (nestedMap.isEmpty()) {
266                 // nested map is now empty -- remove it too
267                 tagMap.remove(tagname);
268             }
269         }
270         return removedFromSeries;
271     }
272 
273     /***
274      * Returns a string representation of this object.
275      *
276      * @return a textual representation
277      */
278     public String toString() {
279         return new ToStringBuilder(this)
280             .append("tag map", tagMap)
281             .toString();
282     }
283 
284 }