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    }