View Javadoc

1   /*
2    * Copyright (C) 2003-2006 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 program is free software; you can redistribute it and/or modify
8    * it under the terms of the GNU General Public License as published by
9    * the Free Software Foundation; either version 2 of the License, or
10   * (at your option) any later version.
11   *
12   * This program 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
15   * GNU General Public License for more details.
16   *
17   * You should have received a copy of the GNU General Public License
18   * along with this program; if not, visit
19   * http://www.gnu.org/licenses/gpl.html or write to the Free Software
20   * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  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.6 $, $Date: 2006/10/21 16:04:29 $, $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          super();
56      }
57  
58      /***
59       * Checks whether this instance contains at least one appearance of the
60       * specified tag.
61       *
62       * @param tagname the name of the tag to check
63       * @return <code>true</code> iff the container contains one or more
64       * appearances of the specified tag
65       */
66      public boolean contains(final String tagname) {
67          return tagMap.containsKey(tagname);
68      }
69  
70      /***
71       * Finds the first appearance of a tag.
72       * If the specified tag did never appear within the requested markup series,
73       * <code>null</code> is returned instead.
74       *
75       * @param tagname the name of the tag to check
76       * @return the found apearance of the tag, or <code>null</code> if no
77       * matching appearance was found
78       */
79      public TagConstituent findFirst(final String tagname) {
80          // check entries for tag name
81          final SortedMap nestedMap = tagMap.get(tagname);
82  
83          if ((nestedMap != null) && !nestedMap.isEmpty()) {
84              final Iterator keyIter = nestedMap.keySet().iterator();
85              Integer currentKey;
86              LinkedList currentTags;
87  
88              while (keyIter.hasNext()) {
89                  currentKey = (Integer) keyIter.next();
90                  currentTags = (LinkedList) nestedMap.get(currentKey);
91  
92                  if ((currentTags != null) && !currentTags.isEmpty()) {
93                      // return first appearance
94                      return (TagConstituent) currentTags.getFirst();
95                  }
96              }
97          }
98  
99          // no appearance found
100         return null;
101     }
102 
103     /***
104      * Finds the appearance of a tag within a specified markup series.
105      * If the specified tag didn't appear within the requested markup series,
106      * <code>null</code> is returned instead.
107      *
108      * @param tagname the name of the tag to check
109      * @param markupSeriesNo the markup series number to match
110      * @param returnFirst whether to return the first or the last
111      * appearance of the tag within the specified series, if several
112      * appearances are found
113      * @return the found apearance of the tag, or <code>null</code> if no
114      * matching appearance was found
115      */
116     public TagConstituent findInSeries(final String tagname,
117             final int markupSeriesNo, final boolean returnFirst) {
118         // check entries for tag name
119         final SortedMap nestedMap = tagMap.get(tagname);
120         final TagConstituent result;
121 
122         if ((nestedMap != null) && !nestedMap.isEmpty()) {
123             // check entries of specified markup series
124             final Integer keyToUse = new Integer(markupSeriesNo);
125             final LinkedList tagsInSeries =
126                 (LinkedList) nestedMap.get(keyToUse);
127 
128             if ((tagsInSeries != null) && !tagsInSeries.isEmpty()) {
129                 // return first resp. last appearance
130                 if (returnFirst) {
131                     result = (TagConstituent) tagsInSeries.getFirst();
132                 } else {
133                     result = (TagConstituent) tagsInSeries.getLast();
134                 }
135             } else {
136                 result = null;
137             }
138         } else {
139             result = null;
140         }
141         return result;
142     }
143 
144     /***
145      * Forces the removal of a single appearance of a tag from the
146      * container. Throws a RuntimeException if the appearance could not be
147      * removed, i.e. it didn't not exist in the container. Use this method
148      * if you are sure that the specified appearance exists.
149      *
150      * @param tag the tag to remove
151      * @throws RuntimeException stating an "Implementation error" if the
152      * specified tag appearance could <em>not</em> be removed
153      */
154     public final void forceRemove(final TagConstituent tag)
155             throws RuntimeException {
156         if (!remove(tag)) {
157             // remove failed
158             throw new RuntimeException("Implementation error: forced removal "
159                 + "failed for tag " + tag);
160         }
161     }
162 
163     /***
164      * Whether this tag container is empty.
165      *
166      * @return <code>true</code> iff this container is empty, i.e. doesn't
167      * contain any tags
168      */
169     public boolean isEmpty() {
170         return tagMap.isEmpty();
171     }
172 
173     /***
174      * Grants subclasses direct access to the map that is used internally for
175      * managing the tags. Maps tag names Strings to nested TreeMaps mapping
176      * markup series Integers to LinkedLists of TagConstituents.
177      *
178      * @return the tag map manages by this class
179      */
180     protected Map getTagMap() {
181         return tagMap;
182     }
183 
184     /***
185      * Inserts an appearance of a tag into the container. The name of the
186      * tag is used as key; the whole constituent is appended at the end of the
187      * list of appearances stored for this tag name.
188      *
189      * @param tag the tag to insert
190      */
191     public final void push(final TagConstituent tag) {
192         push(tag, true);
193     }
194 
195     /***
196      * Inserts an appearance of a tag into the container. The name of the
197      * tag is used as key; the whole constituent is inserted in the list of
198      * appearances stored for this tag name.
199      *
200      * @param tag the tag to insert
201      * @param appendAtEnd whether to insert the tag at the end or at the begin
202      * of appearances within a series
203      * @throws UnsupportedOperationException might be thrown by child classes
204      * that don't support prepending if <code>appendAtEnd</code> is
205      * <code>false</code>
206      */
207     public void push(final TagConstituent tag, final boolean appendAtEnd)
208             throws UnsupportedOperationException {
209         final String tagname = tag.getName();
210         final Integer seriesNo = new Integer(tag.getMarkupSeriesNo());
211 
212         // check entries for tag name
213         SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
214             tagMap.get(tagname);
215         if (nestedMap == null) {
216             // create and insert new map
217             nestedMap = new TreeMap<Integer, LinkedList<TagConstituent>>();
218             tagMap.put(tagname, nestedMap);
219         }
220 
221         // check entries within series no.
222         LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
223         if (tagsInSeries == null) {
224             // create and insert new list
225             tagsInSeries = new LinkedList<TagConstituent>();
226             nestedMap.put(seriesNo, tagsInSeries);
227         }
228 
229         // add tag to list
230         if (appendAtEnd) {
231             tagsInSeries.addLast(tag);
232         } else {
233             tagsInSeries.addFirst(tag);
234         }
235     }
236 
237     /***
238      * Removes a single appearance of a tag from the container.
239      *
240      * @param tag the tag to remove
241      * @return <code>true</code> if the specified TagConstituent was
242      * removed successfully; <code>false</code> otherwise (the specified
243      * constituent wasn't found in the container)
244      */
245     public boolean remove(final TagConstituent tag) {
246         final String tagname = tag.getName();
247         final Integer seriesNo = new Integer(tag.getMarkupSeriesNo());
248 
249         // check entries for tag name
250         SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
251             tagMap.get(tagname);
252         if (nestedMap == null) {
253             return false;
254         }
255 
256         // check entries within series no.
257         LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
258         if (tagsInSeries == null) {
259             return false;
260         }
261         final boolean removedFromSeries = tagsInSeries.remove(tag);
262 
263         if (removedFromSeries && tagsInSeries.isEmpty()) {
264             // inner list is now empty -- remove it
265             nestedMap.remove(seriesNo);
266             if (nestedMap.isEmpty()) {
267                 // nested map is now empty -- remove it too
268                 tagMap.remove(tagname);
269             }
270         }
271         return removedFromSeries;
272     }
273 
274     /***
275      * Returns a string representation of this object.
276      *
277      * @return a textual representation
278      */
279     public String toString() {
280         return new ToStringBuilder(this)
281             .append("tag map", tagMap)
282             .toString();
283     }
284 
285 }