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.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
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
93 return (TagConstituent) currentTags.getFirst();
94 }
95 }
96 }
97
98
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
118 final SortedMap nestedMap = tagMap.get(tagname);
119 final TagConstituent result;
120
121 if ((nestedMap != null) && !nestedMap.isEmpty()) {
122
123 final Integer keyToUse = new Integer(markupSeriesNo);
124 final LinkedList tagsInSeries =
125 (LinkedList) nestedMap.get(keyToUse);
126
127 if ((tagsInSeries != null) && !tagsInSeries.isEmpty()) {
128
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
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
212 SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
213 tagMap.get(tagname);
214 if (nestedMap == null) {
215
216 nestedMap = new TreeMap<Integer, LinkedList<TagConstituent>>();
217 tagMap.put(tagname, nestedMap);
218 }
219
220
221 LinkedList<TagConstituent> tagsInSeries = nestedMap.get(seriesNo);
222 if (tagsInSeries == null) {
223
224 tagsInSeries = new LinkedList<TagConstituent>();
225 nestedMap.put(seriesNo, tagsInSeries);
226 }
227
228
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
249 SortedMap<Integer, LinkedList<TagConstituent>> nestedMap =
250 tagMap.get(tagname);
251 if (nestedMap == null) {
252 return false;
253 }
254
255
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
264 nestedMap.remove(seriesNo);
265 if (nestedMap.isEmpty()) {
266
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 }