001    package com.oxygenxml.validate.nvdl;
002    
003    import java.util.ArrayList;
004    import java.util.Enumeration;
005    import java.util.Hashtable;
006    import java.util.Iterator;
007    import java.util.List;
008    
009    import org.xml.sax.Locator;
010    import org.xml.sax.helpers.LocatorImpl;
011    
012    class Mode {
013      static final int ATTRIBUTE_PROCESSING_NONE = 0;
014      static final int ATTRIBUTE_PROCESSING_QUALIFIED = 1;
015      static final int ATTRIBUTE_PROCESSING_FULL = 2;
016      
017      /**
018       * A special mode. In a mode usage this will be 
019       * resolved by the mode usage to the actual current mode
020       * from that mode usage.
021       */
022      static final Mode CURRENT = new Mode("#current", null);
023    
024      /**
025       * Mode name prefix used for inline anonymous modes.
026       */
027      static final String ANONYMOUS_MODE_NAME_PREFIX = "#anonymous#";
028      
029      /**
030       * Inline anonymous modes counter.
031       */
032      static int anonymousModeCounter = 0;
033      
034      /**
035       * The mode name.
036       */
037      private final String name;
038      
039      /**
040       * The base mode.
041       */
042      private Mode baseMode;
043      
044      /**
045       * Flag indicating if this mode is defined by the user
046       * or is an automatically generated mode.
047       */
048      private boolean defined;
049      /**
050       * Locate the place where this mode is defined.
051       */
052      private Locator whereDefined;
053      
054      /**
055       * Locate the place this mode is first used.
056       * Useful to report with location errors like 
057       * 'Mode "xxx" not defined'.
058       */
059      private Locator whereUsed;
060      
061      
062      private final Hashtable elementMap = new Hashtable();
063      private final Hashtable attributeMap = new Hashtable();
064      private int attributeProcessing = -1;
065    
066      private final Hashtable nssElementMap = new Hashtable();
067      private final Hashtable nssAttributeMap = new Hashtable();
068      
069      /**
070       * List with included modes.
071       */
072      private List includedModes = new ArrayList();
073      
074      void addIncludedMode(Mode mode) {
075        includedModes.add(mode);
076      }
077      
078      /**
079       * Creates a mode extending a base mode.
080       * @param name The new mode name.
081       * @param baseMode The base mode.
082       */
083      Mode(String name, Mode baseMode) {
084        this.name = name;
085        this.baseMode = baseMode;
086      }
087    
088      /**
089       * Creates an anonymous mode.
090       * @param baseMode
091       */
092      public Mode(Mode baseMode) {
093        this(ANONYMOUS_MODE_NAME_PREFIX+anonymousModeCounter++, baseMode);
094      }
095    
096      /**
097       * Get this mode name.
098       * @return The name.
099       */
100      String getName() {
101        return name;
102      }
103    
104      /**
105       * Get the base mode.
106       * @return The base mode.
107       */
108      Mode getBaseMode() {
109        return baseMode;
110      }
111    
112      /**
113       * Set a base mode.
114       * @param baseMode The new base mode.
115       */
116      void setBaseMode(Mode baseMode) {
117        this.baseMode = baseMode;
118      }
119    
120      /**
121       * Get the set of element actions for a given namespace.
122       * If this mode has an explicit handling of that namespace then we get those
123       * actions, otherwise we get the actions for any namespace.
124       * @param ns The namespace we look for element actions for.
125       * @return A set of element actions.
126       */
127      ActionSet getElementActions(String ns) {
128        ActionSet actions = getElementActionsExplicit(ns);
129        if (actions == null) {
130          actions = getElementActionsExplicit(NamespaceSpecification.ANY_NAMESPACE);
131          // this is not correct: it breaks a derived mode that use anyNamespace
132          // elementMap.put(ns, actions);
133        }
134        return actions;
135      }
136    
137      /**
138       * Look for element actions specifically specified
139       * for this namespace. If the current mode does not have
140       * actions for that namespace look at base modes. If the actions 
141       * are defined in a base mode we need to get a copy of those actions
142       * associated with this mode, so we call changeCurrentMode on them.
143       * 
144       * @param ns The namespace
145       * @return A set of element actions.
146       */
147      private ActionSet getElementActionsExplicit(String ns) {
148        ActionSet actions = (ActionSet)elementMap.get(ns);
149        if (actions==null) {
150          // iterate namespace specifications.
151          for (Enumeration e = nssElementMap.keys(); e.hasMoreElements() && actions==null;) {
152            NamespaceSpecification nssI = (NamespaceSpecification)e.nextElement();
153            // If a namespace specification convers the current namespace URI then we get those actions.
154            if (nssI.covers(ns)) {
155              actions = (ActionSet)nssElementMap.get(nssI);
156            }
157          }
158          // Store them in the element Map for faster access next time.
159          if (actions!=null) {
160            elementMap.put(ns, actions);
161          }
162        }
163        // Look into the included modes
164        if (actions == null && includedModes != null) {
165          Iterator i = includedModes.iterator();
166          while (actions == null && i.hasNext()) {
167            Mode includedMode = (Mode)i.next();
168            actions = includedMode.getElementActionsExplicit(ns);
169          }
170          if (actions != null) {
171            actions = actions.changeCurrentMode(this);                    
172            elementMap.put(ns, actions);
173          }
174        }
175        
176        if (actions!=null && actions.getCancelNestedActions()) {
177          actions = null;
178        }
179        
180        // No actions specified, look into the base mode.
181        if (actions == null && baseMode != null) {
182          actions = baseMode.getElementActionsExplicit(ns);
183          if (actions != null) {
184            actions = actions.changeCurrentMode(this);
185            elementMap.put(ns, actions);
186          }
187        }
188        
189        return actions;
190      }
191    
192      /**
193       * Get the set of attribute actions for a given namespace.
194       * If this mode has an explicit handling of that namespace then we get those
195       * actions, otherwise we get the actions for any namespace.
196       * @param ns The namespace we look for attribute actions for.
197       * @return A set of attribute actions.
198       */
199      AttributeActionSet getAttributeActions(String ns) {
200        AttributeActionSet actions = getAttributeActionsExplicit(ns);
201        if (actions == null) {
202          actions = getAttributeActionsExplicit(NamespaceSpecification.ANY_NAMESPACE);
203          // this is not correct: it breaks a derived mode that use anyNamespace
204          // attributeMap.put(ns, actions);
205        }
206        return actions;
207      }
208    
209      /**
210       * Look for attribute actions specifically specified
211       * for this namespace. If the current mode does not have
212       * actions for that namespace look at base modes. If the actions 
213       * are defined in a base mode we need to get a copy of those actions
214       * associated with this mode, so we call changeCurrentMode on them.
215       * 
216       * @param ns The namespace
217       * @return A set of attribute actions.
218       */
219       private AttributeActionSet getAttributeActionsExplicit(String ns) {
220        AttributeActionSet actions = (AttributeActionSet)attributeMap.get(ns);
221        if (actions==null) {
222          // iterate namespace specifications.
223          for (Enumeration e = nssAttributeMap.keys(); e.hasMoreElements() && actions==null;) {
224            NamespaceSpecification nssI = (NamespaceSpecification)e.nextElement();
225            // If a namespace specification convers the current namespace URI then we get those actions.
226            if (nssI.covers(ns)) {
227              actions = (AttributeActionSet)nssAttributeMap.get(nssI);
228            }
229          }
230          // Store them in the element Map for faster access next time.
231          if (actions!=null) {
232            attributeMap.put(ns, actions);
233          }
234        }
235        
236        if (actions!=null && actions.getCancelNestedActions()) {
237          actions = null;
238        }
239        
240        if (actions == null && baseMode != null) {
241          actions = baseMode.getAttributeActionsExplicit(ns);
242          if (actions != null)
243            attributeMap.put(ns, actions);
244        }
245        return actions;
246      }
247    
248      /**
249       * Computes (if not already computed) the attributeProcessing
250       * for this mode and returns it.
251       * If it find anything different than attach then we need to perform 
252       * attribute processing.
253       * If only attributes for a specific namespace have actions then we only need to
254       * process qualified attributes, otherwise we need to process all attributes.
255       * 
256       * @return The attribute processing for this mode.
257       */
258      int getAttributeProcessing() {
259        if (attributeProcessing == -1) {
260          if (baseMode != null)
261            attributeProcessing = baseMode.getAttributeProcessing();
262          else
263            attributeProcessing = ATTRIBUTE_PROCESSING_NONE;
264          for (Enumeration en = nssAttributeMap.keys(); en.hasMoreElements() && attributeProcessing != ATTRIBUTE_PROCESSING_FULL;) {
265            NamespaceSpecification nss = (NamespaceSpecification)en.nextElement();
266            AttributeActionSet actions = (AttributeActionSet)nssAttributeMap.get(nss);
267            if (!actions.getAttach()
268                || actions.getReject()
269                || actions.getSchemas().length > 0)
270              attributeProcessing = ((nss.ns.equals("") || nss.ns.equals(NamespaceSpecification.ANY_NAMESPACE))
271                                    ? ATTRIBUTE_PROCESSING_FULL
272                                    : ATTRIBUTE_PROCESSING_QUALIFIED);
273          }
274        }
275        return attributeProcessing;
276      }
277    
278      /**
279       * Get the locator that points to the place the 
280       * mode is defined.
281       * @return a locator.
282       */
283      Locator getWhereDefined() {
284        return whereDefined;
285      }
286    
287      /**
288       * Getter for the defined flag.
289       * @return defined.
290       */
291      boolean isDefined() {
292        return defined;
293      }
294      
295      boolean isAnonymous() {
296        return name.startsWith(ANONYMOUS_MODE_NAME_PREFIX);
297      }
298    
299      /**
300       * Get a locator pointing to the first place this mode is used.
301       * @return a locator.
302       */
303      Locator getWhereUsed() {
304        return whereUsed;
305      }
306    
307      /**
308       * Record the locator if this is the first location this mode is used.
309       * @param locator Points to the location this mode is used from.
310       */
311      void noteUsed(Locator locator) {
312        if (whereUsed == null && locator != null)
313          whereUsed = new LocatorImpl(locator);
314      }
315    
316      /**
317       * Record the locator this mode is defined at.
318       * @param locator Points to the mode definition.
319       */
320      void noteDefined(Locator locator) {
321        defined = true;
322        if (whereDefined == null && locator != null)
323          whereDefined = new LocatorImpl(locator);
324      }
325    
326      /**
327       * Adds a set of element actions to be performed in this mode
328       * for elements in a specified namespace.
329       *  
330       * @param ns The namespace pattern.
331       * @param wildcard The wildcard character.
332       * @param actions The set of element actions.
333       * @return true if successfully added, that is the namespace was
334       * not already present in the elementMap, otherwise false, the 
335       * caller should signal a script error in this case.
336       */
337      boolean bindElement(String ns, String wildcard, ActionSet actions) {
338        NamespaceSpecification nss = new NamespaceSpecification(ns, wildcard);
339        if (nssElementMap.get(nss) != null)
340          return false;
341        for (Enumeration e = nssElementMap.keys(); e.hasMoreElements();) {
342          NamespaceSpecification nssI = (NamespaceSpecification)e.nextElement();
343          if (nss.compete(nssI)) {
344            return false;
345          }
346        }
347        nssElementMap.put(nss, actions);
348        return true;
349      }
350    
351      /**
352       * Adds a set of attribute actions to be performed in this mode
353       * for attributes in a specified namespace.
354       *  
355       * @param ns The namespace pattern.
356       * @param wildcard The wildcard character.
357       * @param actions The set of attribute actions.
358       * @return true if successfully added, that is the namespace was
359       * not already present in the attributeMap, otherwise false, the 
360       * caller should signal a script error in this case.
361       */
362      boolean bindAttribute(String ns, String wildcard, AttributeActionSet actions) {
363        NamespaceSpecification nss = new NamespaceSpecification(ns, wildcard);
364        if (nssAttributeMap.get(nss) != null)
365          return false;
366        for (Enumeration e = nssAttributeMap.keys(); e.hasMoreElements();) {
367          NamespaceSpecification nssI = (NamespaceSpecification)e.nextElement();
368          if (nss.compete(nssI)) {
369            return false;
370          }
371        }
372        nssAttributeMap.put(nss, actions);
373        return true;    
374      }
375    }