001    package com.thaiopensource.validate.nrl;
002    
003    import org.xml.sax.helpers.DefaultHandler;
004    import org.xml.sax.ErrorHandler;
005    import org.xml.sax.Attributes;
006    import org.xml.sax.SAXException;
007    import org.xml.sax.ContentHandler;
008    import org.xml.sax.Locator;
009    import org.xml.sax.SAXParseException;
010    import org.xml.sax.DTDHandler;
011    import com.thaiopensource.validate.Validator;
012    import com.thaiopensource.validate.Schema;
013    import com.thaiopensource.validate.ValidateProperty;
014    import com.thaiopensource.validate.nrl.ActionSet;
015    import com.thaiopensource.validate.nrl.AttributeActionSet;
016    import com.thaiopensource.validate.nrl.FilteredAttributes;
017    import com.thaiopensource.validate.nrl.Hashset;
018    import com.thaiopensource.validate.nrl.IntSet;
019    import com.thaiopensource.validate.nrl.Mode;
020    import com.thaiopensource.validate.nrl.ModeUsage;
021    import com.thaiopensource.validate.nrl.NoResultAction;
022    import com.thaiopensource.validate.nrl.ResultAction;
023    import com.thaiopensource.validate.nrl.SectionState;
024    import com.thaiopensource.util.Localizer;
025    import com.thaiopensource.util.PropertyMap;
026    
027    import java.util.Vector;
028    import java.util.Stack;
029    import java.util.Hashtable;
030    import java.util.Enumeration;
031    
032    class ValidatorImpl extends DefaultHandler implements Validator {
033      private static final String BEARER_URI = "";
034      private static final String BEARER_LOCAL_NAME = "#bearer";
035      private static final String NO_NS = "\0";
036      private final ErrorHandler eh;
037      private final PropertyMap properties;
038      private Locator locator;
039      private Section currentSection;
040      private PrefixMapping prefixMapping = null;
041      private final Hashtable validatorHandlerCache = new Hashtable();
042      private final Localizer localizer = new Localizer(ValidatorImpl.class);
043      private final Hashset noResultActions = new Hashset();
044      private final Hashtable attributeNamespaceIndexSets = new Hashtable();
045      private final Vector activeHandlersAttributeIndexSets = new Vector();
046      private final Hashset attributeSchemas = new Hashset();
047      private boolean attributeNamespaceRejected;
048      private Attributes filteredAttributes;
049      private final Mode startMode;
050    
051      static private class PrefixMapping {
052        final String prefix;
053        final String uri;
054        final PrefixMapping parent;
055    
056        PrefixMapping(String prefix, String uri, PrefixMapping parent) {
057          this.prefix = prefix;
058          this.uri = uri;
059          this.parent = parent;
060        }
061      }
062    
063      private class Section implements SectionState {
064        final Section parent;
065        /**
066         * Namespace of this section.  Empty string for absent.
067         */
068        final String ns;
069        /**
070         * Number of open elements in this section.
071         */
072        int depth = 0;
073        /**
074         * List of the Validators rooted in this section
075         */
076        final Vector validators = new Vector();
077        final Vector schemas = new Vector();
078        /**
079         * List of the ContentHandlers that want to see the elements in this section
080         */
081        final Vector activeHandlers = new Vector();
082        final Vector activeHandlersAttributeModeUsage = new Vector();
083        final Vector attributeValidationModeUsages = new Vector();
084        /**
085         * List of Programs saying what to do with child sections
086         */
087        final Vector childPrograms = new Vector();
088        final Stack context = new Stack();
089        boolean contextDependent = false;
090        int attributeProcessing = Mode.ATTRIBUTE_PROCESSING_NONE;
091    
092        Section(String ns, Section parent) {
093          this.ns = ns;
094          this.parent = parent;
095        }
096    
097        public void addChildMode(ModeUsage modeUsage, ContentHandler handler) {
098          childPrograms.addElement(new Program(modeUsage, handler));
099          if (modeUsage.isContextDependent())
100            contextDependent = true;
101        }
102    
103        public void addValidator(Schema schema, ModeUsage modeUsage) {
104          schemas.addElement(schema);
105          Validator validator = createValidator(schema);
106          validators.addElement(validator);
107          activeHandlers.addElement(validator.getContentHandler());
108          activeHandlersAttributeModeUsage.addElement(modeUsage);
109          attributeProcessing = Math.max(attributeProcessing,
110                                         modeUsage.getAttributeProcessing());
111          childPrograms.addElement(new Program(modeUsage, validator.getContentHandler()));
112          if (modeUsage.isContextDependent())
113            contextDependent = true;
114        }
115    
116        public void addActiveHandler(ContentHandler handler, ModeUsage attributeModeUsage) {
117          activeHandlers.addElement(handler);
118          activeHandlersAttributeModeUsage.addElement(attributeModeUsage);
119          attributeProcessing = Math.max(attributeProcessing,
120                                         attributeModeUsage.getAttributeProcessing());
121          if (attributeModeUsage.isContextDependent())
122            contextDependent = true;
123        }
124    
125        public void addAttributeValidationModeUsage(ModeUsage modeUsage) {
126          int ap = modeUsage.getAttributeProcessing();
127          if (ap != Mode.ATTRIBUTE_PROCESSING_NONE) {
128            attributeValidationModeUsages.addElement(modeUsage);
129            attributeProcessing = Math.max(ap, attributeProcessing);
130            if (modeUsage.isContextDependent())
131              contextDependent = true;
132          }
133        }
134    
135        public void reject() throws SAXException {
136          if (eh != null)
137            eh.error(new SAXParseException(localizer.message("reject_element", ns),
138                                           locator));
139        }
140    
141      }
142    
143      static private class Program {
144        final ModeUsage modeUsage;
145        final ContentHandler handler;
146    
147        Program(ModeUsage modeUsage, ContentHandler handler) {
148          this.modeUsage = modeUsage;
149          this.handler = handler;
150        }
151      }
152    
153      ValidatorImpl(Mode mode, PropertyMap properties) {
154        this.properties = properties;
155        this.eh = ValidateProperty.ERROR_HANDLER.get(properties);
156        this.startMode = mode;
157        initCurrentSection();
158      }
159    
160      private void initCurrentSection() {
161        currentSection = new Section(NO_NS, null);
162        currentSection.addChildMode(new ModeUsage(startMode, startMode), null);
163      }
164    
165      public void setDocumentLocator(Locator locator) {
166        this.locator = locator;
167      }
168    
169      public void characters(char ch[], int start, int length)
170              throws SAXException {
171        for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
172          ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).characters(ch, start, length);
173    
174      }
175    
176      public void ignorableWhitespace(char ch[], int start, int length)
177              throws SAXException {
178        for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
179          ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).ignorableWhitespace(ch, start, length);
180      }
181    
182      public void startElement(String uri, String localName,
183                               String qName, Attributes attributes)
184              throws SAXException {
185        if (!uri.equals(currentSection.ns))
186          startSection(uri);
187        currentSection.depth++;
188        if (currentSection.contextDependent)
189          currentSection.context.push(localName);
190        boolean transformAttributes = processAttributes(attributes);
191        for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++) {
192          ContentHandler handler = (ContentHandler)(currentSection.activeHandlers.elementAt(i));
193          handler.startElement(uri, localName, qName,
194                               transformAttributes
195                               ? filterAttributes((IntSet)activeHandlersAttributeIndexSets.elementAt(i),
196                                                  attributes)
197                               : attributes);
198        }
199      }
200    
201      private static Attributes filterAttributes(IntSet indexSet, Attributes attributes) {
202        if (indexSet.size() == attributes.getLength())
203          return attributes;
204        return new FilteredAttributes(indexSet, attributes);
205      }
206    
207      private boolean processAttributes(Attributes attributes) throws SAXException {
208        if (currentSection.attributeProcessing == Mode.ATTRIBUTE_PROCESSING_NONE
209            || attributes.getLength() == 0)
210          return false;
211        attributeNamespaceIndexSets.clear();
212        for (int i = 0, len = attributes.getLength(); i < len; i++) {
213          String ns = attributes.getURI(i);
214          IntSet indexSet = (IntSet)attributeNamespaceIndexSets.get(ns);
215          if (indexSet == null) {
216            indexSet = new IntSet();
217            attributeNamespaceIndexSets.put(ns, indexSet);
218          }
219          indexSet.add(i);
220        }
221        if (currentSection.attributeProcessing == Mode.ATTRIBUTE_PROCESSING_QUALIFIED
222            && attributeNamespaceIndexSets.size() == 1
223            && attributeNamespaceIndexSets.get("") != null)
224          return false;
225        Vector handlerModes = currentSection.activeHandlersAttributeModeUsage;
226        activeHandlersAttributeIndexSets.setSize(handlerModes.size());
227        for (int i = 0, len = handlerModes.size(); i < len; i++)
228          activeHandlersAttributeIndexSets.setElementAt(new IntSet(), i);
229        boolean transform = false;
230        Vector validationModes = currentSection.attributeValidationModeUsages;
231        for (Enumeration e = attributeNamespaceIndexSets.keys(); e.hasMoreElements();) {
232          String ns = (String)e.nextElement();
233          IntSet indexSet = (IntSet)attributeNamespaceIndexSets.get(ns);
234          attributeSchemas.clear();
235          filteredAttributes = null;
236          attributeNamespaceRejected = false;
237          for (int i = 0, len = handlerModes.size(); i < len; i++) {
238            ModeUsage modeUsage = (ModeUsage)handlerModes.elementAt(i);
239            AttributeActionSet actions = processAttributeSection(modeUsage, ns, indexSet, attributes);
240            if (actions.getAttach())
241              ((IntSet)activeHandlersAttributeIndexSets.get(i)).addAll(indexSet);
242            else
243              transform = true;
244          }
245          for (int i = 0, len = validationModes.size(); i < len; i++) {
246            ModeUsage modeUsage = (ModeUsage)validationModes.elementAt(i);
247            processAttributeSection(modeUsage, ns, indexSet, attributes);
248          }
249        }
250        return transform;
251      }
252    
253      private AttributeActionSet processAttributeSection(ModeUsage modeUsage,
254                                                         String ns,
255                                                         IntSet indexSet,
256                                                         Attributes attributes)
257              throws SAXException {
258        Mode mode = modeUsage.getMode(currentSection.context);
259        AttributeActionSet actions = mode.getAttributeActions(ns);
260        if (actions.getReject() && !attributeNamespaceRejected) {
261          attributeNamespaceRejected = true;
262          if (eh != null)
263            eh.error(new SAXParseException(localizer.message("reject_attribute", ns),
264                                           locator));
265        }
266        Schema[] schemas = actions.getSchemas();
267        for (int j = 0; j < schemas.length; j++) {
268          if (attributeSchemas.contains(schemas[j]))
269            continue;
270          attributeSchemas.add(schemas[j]);
271          if (filteredAttributes == null)
272            filteredAttributes = filterAttributes(indexSet, attributes);
273          validateAttributes(schemas[j], filteredAttributes);
274        }
275        return actions;
276      }
277    
278      private void validateAttributes(Schema schema, Attributes attributes) throws SAXException {
279        Validator validator = createValidator(schema);
280        ContentHandler ch = validator.getContentHandler();
281        initHandler(ch);
282        ch.startElement(BEARER_URI, BEARER_LOCAL_NAME, BEARER_LOCAL_NAME, attributes);
283        ch.endElement(BEARER_URI, BEARER_LOCAL_NAME, BEARER_LOCAL_NAME);
284        cleanupHandler(ch);
285        releaseValidator(schema, validator);
286      }
287    
288      private void startSection(String uri) throws SAXException {
289        Section section = new Section(uri, currentSection);
290        Vector childPrograms = currentSection.childPrograms;
291        noResultActions.clear();
292        for (int i = 0, len = childPrograms.size(); i < len; i++) {
293          Program program = (Program)childPrograms.elementAt(i);
294          ActionSet actions = program.modeUsage.getMode(currentSection.context).getElementActions(uri);
295          ResultAction resultAction = actions.getResultAction();
296          if (resultAction != null)
297            resultAction.perform(program.handler, section);
298          NoResultAction[] nra = actions.getNoResultActions();
299          for (int j = 0; j < nra.length; j++) {
300            NoResultAction tem = nra[j];
301            if (!noResultActions.contains(tem)) {
302              nra[j].perform(section);
303              noResultActions.add(tem);
304            }
305          }
306        }
307        for (int i = 0, len = section.validators.size(); i < len; i++)
308          initHandler(((Validator)section.validators.elementAt(i)).getContentHandler());
309        currentSection = section;
310      }
311    
312      private void initHandler(ContentHandler ch) throws SAXException {
313        if (locator != null)
314          ch.setDocumentLocator(locator);
315        ch.startDocument();
316        for (PrefixMapping pm = prefixMapping; pm != null; pm = pm.parent)
317          ch.startPrefixMapping(pm.prefix, pm.uri);
318      }
319    
320      public void endElement(String uri, String localName, String qName)
321              throws SAXException {
322        for (int i = 0, len = currentSection.activeHandlers.size(); i < len; i++)
323          ((ContentHandler)(currentSection.activeHandlers.elementAt(i))).endElement(uri, localName, qName);
324        currentSection.depth--;
325        if (currentSection.contextDependent)
326          currentSection.context.pop();
327        if (currentSection.depth == 0)
328          endSection();
329      }
330    
331      private void endSection() throws SAXException {
332        for (int i = 0, len = currentSection.validators.size(); i < len; i++) {
333          Validator validator = (Validator)currentSection.validators.elementAt(i);
334          cleanupHandler(validator.getContentHandler());
335          releaseValidator((Schema)currentSection.schemas.elementAt(i), validator);
336          // endDocument() on one of the validators may throw an exception
337          // in this case we don't want to release the validator twice
338          currentSection.validators.setElementAt(null, i);
339        }
340        currentSection = currentSection.parent;
341      }
342    
343      private void cleanupHandler(ContentHandler vh) throws SAXException {
344        for (PrefixMapping pm = prefixMapping; pm != null; pm = pm.parent)
345          vh.endPrefixMapping(pm.prefix);
346        vh.endDocument();
347      }
348    
349      public void endDocument()
350              throws SAXException {
351      }
352    
353      public void startPrefixMapping(String prefix, String uri)
354              throws SAXException {
355        super.startPrefixMapping(prefix, uri);
356        prefixMapping = new PrefixMapping(prefix, uri, prefixMapping);
357      }
358    
359      public void endPrefixMapping(String prefix)
360              throws SAXException {
361        super.endPrefixMapping(prefix);
362        prefixMapping = prefixMapping.parent;
363      }
364    
365      private Validator createValidator(Schema schema) {
366        Stack stack = (Stack)validatorHandlerCache.get(schema);
367        if (stack == null) {
368          stack = new Stack();
369          validatorHandlerCache.put(schema, stack);
370        }
371        if (stack.empty())
372          return schema.createValidator(properties);
373        return (Validator)stack.pop();
374      }
375    
376      private void releaseValidator(Schema schema, Validator vh) {
377        if (vh == null)
378          return;
379        vh.reset();
380        ((Stack)validatorHandlerCache.get(schema)).push(vh);
381      }
382    
383      public void reset() {
384        for (; currentSection != null; currentSection = currentSection.parent) {
385          for (int i = 0, len = currentSection.validators.size(); i < len; i++)
386            releaseValidator((Schema)currentSection.schemas.elementAt(i),
387                             (Validator)currentSection.validators.elementAt(i));
388        }
389        initCurrentSection();
390      }
391    
392      public ContentHandler getContentHandler() {
393        return this;
394      }
395    
396      public DTDHandler getDTDHandler() {
397        return this;
398      }
399    }