1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
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
94 return (TagConstituent) currentTags.getFirst();
95 }
96 }
97 }
98
99
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
119 final SortedMap nestedMap = tagMap.get(tagname);
120 final TagConstituent result;
121
122 if ((nestedMap != null) && !nestedMap.isEmpty()) {
123
124 final Integer keyToUse = new Integer(markupSeriesNo);
125 final LinkedList tagsInSeries =
126 (LinkedList) nestedMap.get(keyToUse);
127
128 if ((tagsInSeries != null) && !tagsInSeries.isEmpty()) {
129
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
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
213 SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
214 tagMap.get(tagname);
215 if (nestedMap == null) {
216
217 nestedMap = new TreeMap<Integer, LinkedList<TagConstituent>>();
218 tagMap.put(tagname, nestedMap);
219 }
220
221
222 LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
223 if (tagsInSeries == null) {
224
225 tagsInSeries = new LinkedList<TagConstituent>();
226 nestedMap.put(seriesNo, tagsInSeries);
227 }
228
229
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
250 SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
251 tagMap.get(tagname);
252 if (nestedMap == null) {
253 return false;
254 }
255
256
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
265 nestedMap.remove(seriesNo);
266 if (nestedMap.isEmpty()) {
267
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 }