1/*
2 * Copyright (c) 2001-2004 World Wide Web Consortium,
3 * (Massachusetts Institute of Technology, Institut National de
4 * Recherche en Informatique et en Automatique, Keio University). All
5 * Rights Reserved. This program is distributed under the W3C's Software
6 * Intellectual Property License. This program is distributed in the
7 * hope that it will be useful, but WITHOUT ANY WARRANTY; without even
8 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
9 * PURPOSE.
10 * See W3C License http://www.w3.org/Consortium/Legal/ for more details.
11 */
12
13package org.w3c.domts;
14
15import java.lang.reflect.InvocationTargetException;
16import java.lang.reflect.Method;
17import java.util.HashMap;
18import java.util.Map;
19
20import org.w3c.dom.DOMImplementation;
21import org.w3c.dom.Document;
22
23/**
24 *   This class implements the generic parser and configuation
25 *   abstract class for the DOM L3 implementations
26 *
27 *   @author Curt Arnold
28 */
29public class LSDocumentBuilderFactory
30    extends DOMTestDocumentBuilderFactory {
31
32  private final Object parser;
33  private final Method parseURIMethod;
34  private final DOMImplementation impl;
35
36  /**
37   *
38   * Abstract class for a strategy to map a DocumentBuilderSetting
39   * to an action on LSParser.
40   */
41  private static abstract class LSStrategy {
42
43    /**
44     * Constructor.
45     */
46    protected LSStrategy() {
47    }
48
49    /**
50     * Applies setting to LSParser
51     *
52     * @param setting setting
53     * @param parser parser
54     * @throws DOMTestIncompatibleException if parser does not support setting
55     */
56    public abstract void applySetting(DocumentBuilderSetting setting,
57                                      Object parser) throws
58        DOMTestIncompatibleException;
59
60    /**
61     * Gets state of setting for parser
62     *
63     * @param parser parser
64     * @return state of setting
65     */
66    public abstract boolean hasSetting(Object parser);
67
68  }
69
70  /**
71   * Represents a fixed setting, for example, all Java implementations
72   * supported signed values.
73   *
74   */
75  private static class LSFixedStrategy
76      extends LSStrategy {
77    private final boolean fixedValue;
78
79    /**
80     * Constructor
81     *
82     * @param settingName setting name
83     * @param fixedValue fixed value
84     */
85    public LSFixedStrategy(boolean fixedValue) {
86      this.fixedValue = fixedValue;
87    }
88
89    /**
90     * Apply setting.  Throws exception if requested setting
91     * does not match fixed value.
92     */
93    public void applySetting(DocumentBuilderSetting setting, Object parser) throws
94        DOMTestIncompatibleException {
95      if (setting.getValue() != fixedValue) {
96        throw new DOMTestIncompatibleException(null, setting);
97      }
98    }
99
100    /**
101     * Gets fixed value for setting
102     */
103    public boolean hasSetting(Object parser) {
104      return fixedValue;
105    }
106  }
107
108  /**
109   * A strategy for a setting that can be applied by setting a DOMConfiguration
110   * parameter.
111   *
112   */
113  private static class LSParameterStrategy
114      extends LSStrategy {
115    private final String lsParameter;
116    private final boolean inverse;
117
118    /**
119     * Constructor
120     *
121     * @param lsParameter corresponding DOMConfiguration parameter
122     * @param inverse if true, DOMConfiguration value is the inverse
123     * of the setting value
124     */
125    public LSParameterStrategy(String lsParameter, boolean inverse) {
126      this.lsParameter = lsParameter;
127      this.inverse = inverse;
128    }
129
130    protected static void setParameter(DocumentBuilderSetting setting,
131                                       Object parser,
132                                       String parameter,
133                                       Object value) throws
134        DOMTestIncompatibleException {
135      try {
136        Method domConfigMethod = parser.getClass().getMethod("getDomConfig",
137            new Class[0]);
138        Object domConfig = domConfigMethod.invoke(parser, new Object[0]);
139        Method setParameterMethod = domConfig.getClass().getMethod(
140            "setParameter", new Class[] {String.class, Object.class});
141        setParameterMethod.invoke(domConfig, new Object[] {parameter, value});
142
143      }
144      catch (InvocationTargetException ex) {
145        throw new DOMTestIncompatibleException(ex.getTargetException(), setting);
146      }
147      catch (Exception ex) {
148        throw new DOMTestIncompatibleException(ex, setting);
149      }
150    }
151
152    protected static Object getParameter(Object parser,
153                                         String parameter) throws Exception {
154      Method domConfigMethod = parser.getClass().getMethod("getDomConfig",
155          new Class[0]);
156      Object domConfig = domConfigMethod.invoke(parser, new Object[0]);
157      Method getParameterMethod = domConfig.getClass().getMethod("getParameter",
158          new Class[] {String.class});
159      return getParameterMethod.invoke(domConfig, new Object[] {parameter});
160    }
161
162    /**
163     * Apply setting
164     */
165    public void applySetting(DocumentBuilderSetting setting, Object parser) throws
166        DOMTestIncompatibleException {
167      if (inverse) {
168        setParameter(setting, parser, lsParameter,
169                     new Boolean(!setting.getValue()));
170      }
171      else {
172        setParameter(setting, parser, lsParameter, new Boolean(setting.getValue()));
173      }
174    }
175
176    /**
177     * Get value of setting
178     */
179    public boolean hasSetting(Object parser) {
180      try {
181        if (inverse) {
182          return! ( (Boolean) getParameter(parser, lsParameter)).booleanValue();
183        }
184        else {
185          return ( (Boolean) getParameter(parser, lsParameter)).booleanValue();
186        }
187      }
188      catch (Exception ex) {
189        return false;
190      }
191    }
192  }
193
194  /**
195   * A strategy for the validation settings which require
196   * two DOMConfigurure parameters being set, 'validate' and 'schema-type'
197   *
198   */
199  private static class LSValidateStrategy
200      extends LSParameterStrategy {
201    private final String schemaType;
202
203    /**
204     * Constructor
205     * @param schemaType schema type
206     */
207    public LSValidateStrategy(String schemaType) {
208      super("validate", false);
209      this.schemaType = schemaType;
210    }
211
212    /**
213     * Apply setting
214     */
215    public void applySetting(DocumentBuilderSetting setting, Object parser) throws
216        DOMTestIncompatibleException {
217      super.applySetting(setting, parser);
218      setParameter(null, parser, "schema-type", schemaType);
219    }
220
221    /**
222     * Get setting value
223     */
224    public boolean hasSetting(Object parser) {
225      if (super.hasSetting(parser)) {
226        try {
227          String parserSchemaType = (String) getParameter(parser, "schema-type");
228          if (schemaType == null || schemaType.equals(parserSchemaType)) {
229            return true;
230          }
231        }
232        catch (Exception ex) {
233        }
234      }
235      return false;
236    }
237
238  }
239
240  /**
241   * Strategies for mapping DocumentBuilderSettings to
242   * actions on LSParser
243   */
244  private static final Map strategies;
245
246  static {
247    strategies = new HashMap();
248    strategies.put("coalescing", new LSParameterStrategy("cdata-sections", true));
249    strategies.put("expandEntityReferences", new LSParameterStrategy("entities", true));
250    strategies.put("ignoringElementContentWhitespace",
251                   new LSParameterStrategy("element-content-whitespace", true));
252    strategies.put("namespaceAware", new LSParameterStrategy("namespaces", false));
253    strategies.put("validating",
254                   new LSValidateStrategy("http://www.w3.org/TR/REC-xml"));
255    strategies.put("schemaValidating",
256                   new LSValidateStrategy("http://www.w3.org/2001/XMLSchema"));
257    strategies.put("ignoringComments", new LSParameterStrategy("comments", true));
258    strategies.put("signed", new LSFixedStrategy(true));
259    strategies.put("hasNullString", new LSFixedStrategy(true));
260  }
261
262  /**
263   * Creates a LS implementation of DOMTestDocumentBuilderFactory.
264   * @param settings array of settings, may be null.
265   * @throws DOMTestIncompatibleException
266   *     Thrown if implementation does not support the specified settings
267   */
268  public LSDocumentBuilderFactory(DocumentBuilderSetting[] settings) throws
269      DOMTestIncompatibleException {
270    super(settings);
271
272    try {
273      Class domImplRegistryClass = Class.forName(
274          "org.w3c.dom.bootstrap.DOMImplementationRegistry");
275      Method newInstanceMethod = domImplRegistryClass.getMethod("newInstance", (Class<?>) null);
276      Object domRegistry = newInstanceMethod.invoke(null, (Class<?>) null);
277      Method getDOMImplementationMethod = domImplRegistryClass.getMethod(
278          "getDOMImplementation", new Class[] {String.class});
279      impl = (DOMImplementation) getDOMImplementationMethod.invoke(domRegistry,
280          new Object[] {"LS"});
281      Method createLSParserMethod = impl.getClass().getMethod("createLSParser",
282          new Class[] {short.class, String.class});
283      parser = createLSParserMethod.invoke(impl,
284                                           new Object[] {new Short( (short) 1), null});
285      parseURIMethod = parser.getClass().getMethod("parseURI",
286          new Class[] {String.class});
287    }
288    catch (InvocationTargetException ex) {
289      throw new DOMTestIncompatibleException(ex.getTargetException(), null);
290    }
291    catch (Exception ex) {
292      throw new DOMTestIncompatibleException(ex, null);
293    }
294
295    if (settings != null) {
296      for (int i = 0; i < settings.length; i++) {
297        Object strategy = strategies.get(settings[i].getProperty());
298        if (strategy == null) {
299          throw new DOMTestIncompatibleException(null, settings[i]);
300        }
301        else {
302          ( (LSStrategy) strategy).applySetting(settings[i], parser);
303        }
304      }
305    }
306  }
307
308  /**
309   *    Create new instance of document builder factory
310   *    reflecting specified settings
311   *    @param newSettings new settings
312   *    @return New instance
313   *    @throws DOMTestIncompatibleException
314   *         if settings are not supported by implementation
315   */
316  public DOMTestDocumentBuilderFactory newInstance(
317      DocumentBuilderSetting[] newSettings) throws DOMTestIncompatibleException {
318    if (newSettings == null) {
319      return this;
320    }
321    DocumentBuilderSetting[] mergedSettings = mergeSettings(newSettings);
322    return new LSDocumentBuilderFactory(mergedSettings);
323  }
324
325  /**
326   *    Loads specified URL
327   *    @param url url to load
328   *    @return DOM document
329   *    @throws DOMTestLoadException if unable to load document
330   */
331  public Document load(java.net.URL url) throws DOMTestLoadException {
332    try {
333      return (Document) parseURIMethod.invoke(parser,
334                                              new Object[] {url.toString()});
335    }
336    catch (InvocationTargetException ex) {
337      throw new DOMTestLoadException(ex.getTargetException());
338    }
339    catch (Exception ex) {
340      throw new DOMTestLoadException(ex);
341    }
342  }
343
344  /**
345   *     Gets DOMImplementation
346   *     @return DOM implementation, may be null
347   */
348  public DOMImplementation getDOMImplementation() {
349    return impl;
350  }
351
352  /**
353   *   Determines if the implementation supports the specified feature
354   *   @param feature Feature
355   *   @param version Version
356   *   @return true if implementation supports the feature
357   */
358  public boolean hasFeature(String feature, String version) {
359    return getDOMImplementation().hasFeature(feature, version);
360  }
361
362  private boolean hasProperty(String parameter) {
363    try {
364      return ( (Boolean) LSParameterStrategy.getParameter(parser, parameter)).
365          booleanValue();
366    }
367    catch (Exception ex) {
368      return true;
369    }
370
371  }
372
373  /**
374   *   Indicates whether the implementation combines text and cdata nodes.
375   *   @return true if coalescing
376   */
377  public boolean isCoalescing() {
378    return!hasProperty("cdata-sections");
379  }
380
381  /**
382   *   Indicates whether the implementation expands entity references.
383   *   @return true if expanding entity references
384   */
385  public boolean isExpandEntityReferences() {
386    return!hasProperty("entities");
387  }
388
389  /**
390   *   Indicates whether the implementation ignores
391   *       element content whitespace.
392   *   @return true if ignoring element content whitespace
393   */
394  public boolean isIgnoringElementContentWhitespace() {
395    return!hasProperty("element-content-whitespace");
396  }
397
398  /**
399   *   Indicates whether the implementation is namespace aware.
400   *   @return true if namespace aware
401   */
402  public boolean isNamespaceAware() {
403    return hasProperty("namespaces");
404  }
405
406  /**
407   *   Indicates whether the implementation is validating.
408   *   @return true if validating
409   */
410  public boolean isValidating() {
411    return hasProperty("validate");
412  }
413
414}
415