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 }