001    package com.thaiopensource.relaxng;
002    
003    import com.thaiopensource.validate.auto.AutoSchemaReader;
004    import com.thaiopensource.util.PropertyMapBuilder;
005    import com.thaiopensource.validate.Flag;
006    import com.thaiopensource.validate.IncorrectSchemaException;
007    import com.thaiopensource.validate.SchemaReader;
008    import com.thaiopensource.xml.sax.XMLReaderCreator;
009    import com.thaiopensource.validate.Schema;
010    import com.thaiopensource.validate.ValidateProperty;
011    import com.thaiopensource.validate.rng.CompactSchemaReader;
012    import com.thaiopensource.validate.rng.RngProperty;
013    import org.relaxng.datatype.DatatypeLibraryFactory;
014    import org.xml.sax.ErrorHandler;
015    import org.xml.sax.InputSource;
016    import org.xml.sax.SAXException;
017    
018    import java.io.IOException;
019    
020    /**
021     * A factory for RELAX NG schemas.  The factory creates <code>Schema</code> objects from their
022     * XML representation.
023     *
024     * A single <code>SchemaFactory</code> is <em>not</em> safe for concurrent
025     * access by multiple threads; it must be accessed by at most one thread at a time.
026     * Schemas can be created concurrently by using a distinct <code>SchemaFactory</code> for each
027     * thread.  However, the <code>Schema</code> objects created <em>are</em> safe for concurrent
028     * access by multiple threads.
029     *
030     * @author <a href="mailto:jjc@jclark.com">James Clark</a>
031     */
032    public class SchemaFactory {
033      private PropertyMapBuilder properties = new PropertyMapBuilder();
034      private boolean compactSyntax = false;
035      private SchemaReader autoSchemaLanguage = new AutoSchemaReader();
036    
037      /**
038       * Constructs a schema factory.
039       */
040      public SchemaFactory() {
041      }
042    
043      /**
044       * Creates a schema by parsing an XML document.  A non-null <code>XMLReaderCreator</code> must be specified
045       * with <code>setXMLReaderCreator</code> before calling <code>createSchema</code>.  The <code>ErrorHandler</code>
046       * is allowed to be <code>null</code>. The <code>DatatypeLibraryFactory</code> is allowed to be <code>null</code>.
047       *
048       * <p>Normally, if a schema cannot be created, <code>createSchema</code> will throw
049       * a <code>IncorrectSchemaException</code>; however,
050       * before doing so, one or more errors will be reported using the <code>ErrorHandler</code> if it is non-null.  If the
051       * <code>ErrorHandler</code> throws a <code>SAXException</code>, then <code>createSchema</code> will pass this
052       * through rather than throwing a <code>IncorrectSchemaException</code>. Similarly, if <code>XMLReader.parse</code>
053       * throws a <code>SAXException</code> or <code>IOException</code>, then <code>createSchema</code> will pass
054       * this through rather than throwing a <code>IncorrectSchemaException</code>. Thus, if an error handler
055       * is specified that reports errors to the user, there is no need to report any additional message to the
056       * user if <code>createSchema</code> throws <code>IncorrectSchemaException</code>.
057       *
058       * @param in the <code>InputSource</code> containing the XML document to be parsed;
059       * must not be <code>null</code>
060       * @return the <code>Schema</code> constructed from the XML document;
061       * never <code>null</code>.
062       *
063       * @throws IOException if an I/O error occurs
064       * @throws SAXException if there is an XML parsing error and the XMLReader or ErrorHandler
065       * throws a SAXException
066       * @throws com.thaiopensource.validate.IncorrectSchemaException if the XML document was not a correct RELAX NG schema
067       * @throws NullPointerException if the current XMLReaderCreator is <code>null</code>
068       */
069      public Schema createSchema(InputSource in) throws IOException, SAXException, IncorrectSchemaException {
070        SchemaReader r = compactSyntax ? CompactSchemaReader.getInstance() : autoSchemaLanguage;
071        return r.createSchema(in, properties.toPropertyMap());
072      }
073    
074      /**
075       * Specifies the XMLReaderCreator to be used for creating <code>XMLReader</code>s for parsing
076       * the XML document.  Because of <code>include</code> and <code>externalRef</code> elements,
077       * parsing a single RELAX NG may require the creation of multiple more than one <code>XMLReader</code>.
078       * A non-null XMLReaderCreator must be specified before calling <code>createSchema</code>.
079       *
080       * @param xrc the <code>XMLReaderCreator</code> to be used for parsing the XML document containing
081       * the schema; may be <code>null</code>
082       * @see #getXMLReaderCreator
083       */
084      public void setXMLReaderCreator(XMLReaderCreator xrc) {
085        properties.put(ValidateProperty.XML_READER_CREATOR, xrc);
086      }
087    
088      /**
089       * Returns the current <code>XMLReaderCreator</code> as specified by <code>setXMLReaderCreator</code>.
090       * If <code>XMLReaderCreator</code> has never been called, then <code>getXMLReaderCreator</code>
091       * returns null.
092       *
093       * @return the <code>XMLReaderCreator</code> that will be used for parsing the XML document containing
094       * the schema; may be <code>null</code>
095       *
096       * @see #setXMLReaderCreator
097       */
098      public XMLReaderCreator getXMLReaderCreator() {
099        return (XMLReaderCreator)properties.get(ValidateProperty.XML_READER_CREATOR);
100      }
101    
102      /**
103       * Specifies the <code>ErrorHandler</code> to be used for reporting errors while creating the schema.
104       * This does not affect the error handler used for validation.
105       *
106       * @param eh the <code>ErrorHandler</code> to be used for reporting errors while creating the schema;
107       * may be <code>null</code>.
108       * @see #getErrorHandler
109       */
110      public void setErrorHandler(ErrorHandler eh) {
111        properties.put(ValidateProperty.ERROR_HANDLER, eh);
112      }
113    
114      /**
115       * Returns the <code>ErrorHandler</code> that will be used for reporting errors while creating the
116       * schema. If <code>setErrorHandler</code> has not been called for this <code>SchemaFactory</code>,
117       * then <code>getErrorHandler</code> returns <code>null</code>.
118       *
119       * @return the <code>ErrorHandler</code> to be used for reporting errors while creating the schema;
120       * may be <code>null</code>.
121       * @see #setErrorHandler
122       */
123      public ErrorHandler getErrorHandler() {
124        return (ErrorHandler)properties.get(ValidateProperty.ERROR_HANDLER);
125      }
126    
127      /**
128       * Specifies the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema.
129       * This also determines how datatypes are handled during validation.  If <code>null</code> is
130       * specified then only the builtin datatypes will be supported.
131       *
132       * @param dlf the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema
133       * @see #getDatatypeLibraryFactory
134       */
135      public void setDatatypeLibraryFactory(DatatypeLibraryFactory dlf) {
136        properties.put(RngProperty.DATATYPE_LIBRARY_FACTORY, dlf);
137      }
138    
139      /**
140       * Returns the <code>DatatypeLibraryFactory</code> that will be used for handling datatypes in the
141       * schema. If <code>setDatatypeLibraryFactory</code> has not been called for this <code>SchemaFactory</code>,
142       * then <code>getDatatypeLibraryFactory</code> returns <code>null</code>.
143       *
144       * @return the <code>DatatypeLibraryFactory</code> to be used for handling datatypes in the schema;
145       * may be null.
146       * @see #setDatatypeLibraryFactory
147       */
148      public DatatypeLibraryFactory getDatatypeLibraryFactory() {
149        return (DatatypeLibraryFactory)properties.get(RngProperty.DATATYPE_LIBRARY_FACTORY);
150      }
151    
152      /**
153       * Specifies whether to perform checking of ID/IDREF/IDREFS attributes in accordance with
154       * RELAX NG DTD Compatibility.
155       *
156       * @param checkIdIdref <code>true</code> if ID/IDREF/IDREFS checking should be performed;
157       * <code>false</code> otherwise
158       *
159       * @see #getCheckIdIdref
160       * @see <a href="http://www.oasis-open.org/committees/relax-ng/compatibility.html#id">RELAX NG DTD Compatibility</a>
161       */
162      public void setCheckIdIdref(boolean checkIdIdref) {
163        properties.put(RngProperty.CHECK_ID_IDREF, checkIdIdref ? Flag.PRESENT : null);
164      }
165    
166      /**
167       * Indicates whether ID/IDREF/IDREFS attributes will be checked in accordance RELAX NG DTD
168       * Compatibility.  If <code>setCheckIdIdref</code> has not been called for this <code>SchemaFactory</code>,
169       * then <code>getCheckIdref</code> will return <code>false</code>.
170       *
171       * @return <code>true</code> if ID/IDREF/IDREFS attributes will be checked;
172       * <code>false</code> otherwise.
173       *
174       * @see #setCheckIdIdref
175       * @see <a href="http://www.oasis-open.org/committees/relax-ng/compatibility.html#id">RELAX NG DTD Compatibility</a>
176       */
177      public boolean getCheckIdIdref() {
178        return properties.contains(RngProperty.CHECK_ID_IDREF);
179      }
180    
181      /**
182       * Specifies whether to use the compact syntax to parse the RELAX NG schema rather than the normal XML syntax.
183       *
184       * @param compactSyntax <code>true</code> if the compact syntax should be used; <code>false</code>
185       * if the XML syntax should be used
186       * @see #getCompactSyntax
187       */
188      public void setCompactSyntax(boolean compactSyntax) {
189        this.compactSyntax = compactSyntax;
190      }
191    
192      /**
193       * Indicates whether the compact syntax will be used to parse the RELAX NG schema rather than
194       * the normal XML syntax.
195       *
196       * @return <code>true</code> if the compact syntax will be used; <code>false</code> if the XML
197       * syntax will be used
198       */
199      public boolean getCompactSyntax() {
200        return compactSyntax;
201      }
202    
203      public void setFeasible(boolean feasible) {
204        properties.put(RngProperty.FEASIBLE, feasible ? Flag.PRESENT : null);
205      }
206    
207      public boolean getFeasible() {
208        return properties.contains(RngProperty.FEASIBLE);
209      }
210    }