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.LinkedList;
25 import java.util.List;
26 import java.util.SortedMap;
27
28 import org.apache.commons.collections.MultiHashMap;
29 import org.apache.commons.collections.MultiMap;
30 import org.apache.commons.collections.map.LinkedMap;
31
32 /***
33 * A container used to manage the currently open start tags during XML
34 * adjustment. Adds stack-like push and pop functionality to the superclass
35 * and allows checking specifically for
36 * {@link de.fu_berlin.ties.xml.TagVariety#TENTATIVE tentative} tag.
37 *
38 * @author Christian Siefkes
39 * @version $Revision: 1.3 $, $Date: 2004/08/23 17:11:06 $, $Author: siefkes $
40 */
41 public class OpenTags extends TagContainer {
42
43 /***
44 * A dummy object stored as map value where no value is required.
45 */
46 protected static final Object DUMMY = new Object();
47
48 /***
49 * Contains all tentative tags (checking the variety at insertion
50 * time), mapping from tag name Strings to Collections of TagConstituents.
51 */
52 private final MultiMap tentativeTags = new MultiHashMap();
53
54 /***
55 * Contains all non-tentative tags (checking the variety at insertion
56 * time), mapping from tag name Strings to Collections of TagConstituents.
57 */
58 private final MultiMap nonTentativeTags = new MultiHashMap();
59
60 /***
61 * Additionally preserves order of insertion for all TagConstituents,
62 * for push/pop/peek operations. We would prefer to use an ordered
63 * <em>set</em>, but there is no "LinkedSet" class and
64 * {@link java.util.LinkedHashSet} doesn't provide fast access to the
65 * <em>last</em> element (only to the first ones). Thus we just store
66 * a {@link #DUMMY} reference in each value, effectively using the set
67 * part of this map.
68 */
69 private final LinkedMap tagSequence = new LinkedMap();
70
71 /***
72 * Creates a new instance.
73 */
74 public OpenTags() {
75 super();
76 }
77
78 /***
79 * Checks whether this instance contains at least one appearance of the
80 * specified tag that is not {@link TagVariety#TENTATIVE}.
81 *
82 * @param tagname the name of the tag to check
83 * @return <code>true</code> iff the container contains one or more
84 * non-tenatative (at insertion time) appearances of the specified tag
85 */
86 public boolean containsNonTentative(final String tagname) {
87
88 final List appearances = (List) nonTentativeTags.get(tagname);
89 return (appearances != null) && !appearances.isEmpty();
90 }
91
92 /***
93 * Finds a tentative appearance of a tag, if any exists.
94 * If several tentative appearances exist, the <em>last</em> of them is
95 * returned.
96 *
97 * @param tagname the name of the tag to check
98 * @return the found apearance of the tag, or <code>null</code> if no
99 * matching appearance was found
100 */
101 public TagConstituent findTentativeTag(final String tagname) {
102
103 final List appearances = (List) tentativeTags.get(tagname);
104 final TagConstituent result;
105
106 if ((appearances != null) && !appearances.isEmpty()) {
107
108 result =
109 (TagConstituent) appearances.get(appearances.size() - 1);
110
111 } else {
112 result = null;
113 }
114 return result;
115 }
116
117 /***
118 * Checks whether a tag is the root tag (the first tag inserted into this
119 * container).
120 *
121 * @param tag the tag to check
122 * @return <code>true</code> if the tag is the root tag
123 */
124 public boolean isRoot(final TagConstituent tag) {
125 return (tag != null) && (tag == peekFirst());
126 }
127
128 /***
129 * Returns the first tag appearance that was initially
130 * {@link #push(TagConstituent) push}ed into this container.
131 *
132 * @return the first undeleted tag pushed into this container,
133 * or <code>null</code> if the container is empty
134 */
135 protected TagConstituent peekFirst() {
136 return (tagSequence.isEmpty())
137 ? null : (TagConstituent) tagSequence.firstKey();
138 }
139
140 /***
141 * Returns the last tag appearance that was
142 * {@link #push(TagConstituent) push}ed into this container.
143 *
144 * @return the last undeleted tag pushed into this container,
145 * or <code>null</code> if the container is empty
146 */
147 public TagConstituent peek() {
148 return (tagSequence.isEmpty())
149 ? null : (TagConstituent) tagSequence.lastKey();
150 }
151
152 /***
153 * Removes and returns the last tag appearance that was
154 * {@link #push(TagConstituent) push}ed into this container.
155 *
156 * @return the removed tag
157 * @throws NullPointerException if the container is empty (i.e. a
158 * call to {@link #peek()} would return <code>null</code>)
159 */
160 public TagConstituent pop() throws NullPointerException {
161 final TagConstituent tagToRemove = peek();
162 final String tagname = tagToRemove.getName();
163 final Integer seriesNo = new Integer(tagToRemove.getMarkupSeriesNo());
164 tagSequence.remove(tagToRemove);
165
166
167 tentativeTags.remove(tagname, tagToRemove);
168 nonTentativeTags.remove(tagname, tagToRemove);
169
170
171 final SortedMap nestedMap = (SortedMap) getTagMap().get(tagname);
172
173
174 final LinkedList tagsInSeries =
175 (LinkedList) nestedMap.get(seriesNo);
176 final TagConstituent removedTag =
177 (TagConstituent) tagsInSeries.removeLast();
178
179 if (tagsInSeries.isEmpty()) {
180
181 nestedMap.remove(seriesNo);
182 if (nestedMap.isEmpty()) {
183
184 getTagMap().remove(tagname);
185 }
186 }
187
188 if (removedTag != tagToRemove) {
189
190 throw new RuntimeException("Implementation error: "
191 + tagToRemove + " and " + removedTag
192 + " should be the same object, but the aren't!");
193 }
194 return tagToRemove;
195 }
196
197 /***
198 * Removes and returns the last tag appearance that was
199 * {@link #push(TagConstituent) push}ed into this container. The variety of
200 * the removed tag is set to {@link TagVariety#REGULAR} (independently of
201 * its previous value).
202 *
203 * @return <code>true</code> if the tag was irregular and thus had to be
204 * regularized; <code>false</code> if it was a regular tag
205 * @throws NullPointerException if the container is empty (i.e. a
206 * call to {@link #peek()} would return <code>null</code>)
207 */
208 public boolean popAndRegularize() throws NullPointerException {
209 final TagConstituent popped = pop();
210 final boolean result;
211 if (popped.getVariety() != TagVariety.REGULAR) {
212
213 popped.setVariety(TagVariety.REGULAR);
214 result = true;
215 } else {
216 result = false;
217 }
218 return result;
219 }
220
221 /***
222 * Inserts an appearance of a tag into the container. The name of the
223 * tag is used as key; the whole constituent is appended to the list of
224 * appearances stored for this tag name. This implementation doesn't support
225 * inserting at the begin of af the list because it would hinder the stack
226 * operations. Instances of this class accept only start tags.
227 *
228 * @param tag the tag to insert
229 * @param appendAtEnd must always be <code>true</code>
230 * @throws IllegalArgumentException if the specified tag is not a start tag
231 * @throws UnsupportedOperationException if <code>appendAtEnd</code> is
232 * <code>false</code> (prepending is not supported)
233 */
234 public void push(final TagConstituent tag, final boolean appendAtEnd)
235 throws IllegalArgumentException, UnsupportedOperationException {
236
237 if (tag.getType() != TagConstituent.START_TAG) {
238 throw new IllegalArgumentException(
239 "OpenTags allows only start tags, not tags of type "
240 + tag.getType() + " (" + tag + ")");
241 }
242 if (!appendAtEnd) {
243 throw new UnsupportedOperationException("OpenTags.push cannot "
244 + "handle prepending -- would destroy stack operations");
245 }
246
247
248 super.push(tag, appendAtEnd);
249
250
251 tagSequence.put(tag, DUMMY);
252
253
254 if (tag.getVariety() == TagVariety.TENTATIVE) {
255 tentativeTags.put(tag.getName(), tag);
256 } else {
257 nonTentativeTags.put(tag.getName(), tag);
258 }
259 }
260
261 /***
262 * Removes a single appearance of a tag from the container.
263 *
264 * @param tag the tag to remove
265 * @return <code>true</code> if the specified TagConstituent was
266 * removed successfully; <code>false</code> otherwise (the specified
267 * constituent wasn't found in the container)
268 */
269 public boolean remove(final TagConstituent tag) {
270
271
272
273 tentativeTags.remove(tag.getName(), tag);
274 nonTentativeTags.remove(tag.getName(), tag);
275
276
277 final boolean superResult = super.remove(tag);
278
279
280 return superResult && (tagSequence.remove(tag) == DUMMY);
281 }
282
283 }