001 package com.oxygenxml.validate.nvdl;
002
003 import com.thaiopensource.util.Equal;
004
005 import java.util.Vector;
006 import java.util.Hashtable;
007 import java.util.Enumeration;
008 import java.util.NoSuchElementException;
009
010 /**
011 * Keeps modes depending on context.
012 * The structure of the context map is
013 *
014 * stores the mode for
015 * / in rootValue
016 * "" in otherValue (this is for relative paths)
017 * stores a hash with the last path elements as key and
018 * ContextMap objects as values.
019 *
020 * A path like a/b and mode x
021 * will be represented by 3 ContextMap objects
022 * ContextMap b ---> ContextMap a ---> ContextMap otherValue=x
023 *
024 * Addind also /a/b and mode y will give
025 *
026 * ContextMap b ---> ContextMap a ---> ContextMap (otherValue=x, rootValue=y)
027 *
028 * Adding a2/b and mode w will give
029 *
030 * ContextMap b ---> ContextMap a ---> ContextMap (otherValue=x, rootValue=y)
031 * a2 ---> ContextMap otherValue=w
032 */
033 class ContextMap {
034 /**
035 * Stores the mode associated with an absolute path.
036 */
037 private Object rootValue;
038 /**
039 * Stores a mode associated with a relative path.
040 */
041 private Object otherValue;
042 /**
043 * Stores a hash map with with the key the last local name and
044 * as values other ContextMap objects.
045 */
046 private final Hashtable nameTable = new Hashtable();
047
048 /**
049 * Get the mode matching a list of local names.
050 * A root more returned means an exact matching of the given local names
051 * with the local names from the context map. Otherwise we can get either
052 * a mode stored as otherValue or null if the given context does not match
053 * any of the stored paths.
054 * @param context The list of local names that represent a section context
055 * (path from root local element names from the same namespace).
056 * @return A mode or null.
057 */
058 Object get(Vector context) {
059 return get(context, context.size());
060 }
061
062 /**
063 * Adds a single path (isRoot, names) and a mode to be used for this path = context.
064 * @param isRoot True if the path starts with /
065 * @param names The local names that form the path.
066 * @param value The mode.
067 * @return true if there is no duplicate path, false otherwise.
068 */
069 boolean put(boolean isRoot, Vector names, Object value) {
070 return put(isRoot, names, names.size(), value);
071 }
072
073 /**
074 * Get the mode matching a list of local names.
075 * A root more returned means an exact matching of the given local names
076 * with the local names from the context map. Otherwise we can get either
077 * a mode stored as otherValue or null if the given context does not match
078 * any of the stored paths.
079 * @param context The list of local names that represent a section context
080 * (path from root local element names from the same namespace).
081 * @param len The lenght we should take from the list.
082 * @return A mode or null.
083 */
084 private Object get(Vector context, int len) {
085 if (len > 0) {
086 ContextMap nestedMap = (ContextMap)nameTable.get(context.elementAt(len - 1));
087 if (nestedMap != null) {
088 Object value = nestedMap.get(context, len - 1);
089 if (value != null)
090 return value;
091 }
092 }
093 if (rootValue != null && len == 0)
094 return rootValue;
095 return otherValue;
096 }
097
098 /**
099 * Adds a single path (isRoot, names) and a mode to be used for this path = context.
100 * @param isRoot True if the path starts with /
101 * @param names The local names that form the path.
102 * @param len The length if the names vector.
103 * @param value The mode.
104 * @return true if there is no duplicate path, false otherwise.
105 */
106 private boolean put(boolean isRoot, Vector names, int len, Object value) {
107 if (len == 0) {
108 // if we have only /
109 if (isRoot) {
110 if (rootValue != null)
111 return false;
112 rootValue = value;
113 }
114 // We followed all the paths, it is not root,
115 // then we store the mode as the other value.
116 else {
117 if (otherValue != null)
118 return false;
119 otherValue = value;
120 }
121 return true;
122 }
123 else {
124 // get the last local name from the path
125 Object name = names.elementAt(len - 1);
126 // Get the context map mapped in nameTable to that name.
127 ContextMap nestedMap = (ContextMap)nameTable.get(name);
128 // Not preset then create it.
129 if (nestedMap == null) {
130 nestedMap = new ContextMap();
131 nameTable.put(name, nestedMap);
132 }
133 // Add the rest of the path names in the nested context map.
134 return nestedMap.put(isRoot, names, len - 1, value);
135 }
136 }
137
138 /**
139 * Chek that this context map is equals with
140 * a specified context map.
141 */
142 public boolean equals(Object obj) {
143 if (!(obj instanceof ContextMap))
144 return false;
145 ContextMap other = (ContextMap)obj;
146 if (!Equal.equal(this.rootValue, other.rootValue)
147 || !Equal.equal(this.otherValue, other.otherValue))
148 return false;
149 // We want jing to work with JDK 1.1 so we cannot use Hashtable.equals
150 if (this.nameTable.size() != other.nameTable.size())
151 return false;
152 for (Enumeration e = nameTable.keys(); e.hasMoreElements();) {
153 Object key = e.nextElement();
154 if (!nameTable.get(key).equals(other.nameTable.get(key)))
155 return false;
156 }
157 return true;
158 }
159
160 /**
161 * Get a hashcode for this context map.
162 */
163 public int hashCode() {
164 int hc = 0;
165 if (rootValue != null)
166 hc ^= rootValue.hashCode();
167 if (otherValue != null)
168 hc ^= otherValue.hashCode();
169 for (Enumeration e = nameTable.keys(); e.hasMoreElements();) {
170 Object key = e.nextElement();
171 hc ^= key.hashCode();
172 hc ^= nameTable.get(key).hashCode();
173 }
174 return hc;
175 }
176
177 /**
178 * Creates an Enumeration implementation that enumerates all the
179 * modes stored in this context map and in the nested context maps.
180 */
181 static private class Enumerator implements Enumeration {
182 /**
183 * Store this context map root value.
184 */
185 private Object rootValue;
186
187 /**
188 * Store this context map other value.
189 */
190 private Object otherValue;
191
192 /**
193 * Stores the enumeration of modes of the current subMap.
194 */
195 private Enumeration subMapValues;
196
197 /**
198 * Stores the ContextMap objects from the nameTable.
199 */
200 private final Enumeration subMaps;
201
202 private Enumerator(ContextMap map) {
203 rootValue = map.rootValue;
204 otherValue = map.otherValue;
205 subMaps = map.nameTable.elements();
206 }
207
208 /**
209 * Advance to the next context map values
210 * in subMapValues and to the next element
211 * in subMap enumeration, if needed.
212 */
213 private void prep() {
214 while ((subMapValues == null || !subMapValues.hasMoreElements()) && subMaps.hasMoreElements())
215 subMapValues = ((ContextMap)subMaps.nextElement()).values();
216 }
217
218 /**
219 * True if we have more elements.
220 */
221 public boolean hasMoreElements() {
222 prep();
223 return rootValue != null || otherValue != null || (subMapValues != null && subMapValues.hasMoreElements());
224 }
225
226 /**
227 * Get the next element (mode in this case).
228 */
229 public Object nextElement() {
230 if (rootValue != null) {
231 Object tem = rootValue;
232 rootValue = null;
233 return tem;
234 }
235 if (otherValue != null) {
236 Object tem = otherValue;
237 otherValue = null;
238 return tem;
239 }
240 prep();
241 if (subMapValues == null)
242 throw new NoSuchElementException();
243 return subMapValues.nextElement();
244 }
245 }
246
247 /**
248 * Get an enumeration with all the modes in this context map.
249 * @return An enumeration containing Mode objects.
250 */
251 Enumeration values() {
252 return new Enumerator(this);
253 }
254 }