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 
13 package org.w3c.domts;
14 
15 import java.lang.reflect.InvocationTargetException;
16 import java.lang.reflect.Method;
17 import java.util.HashMap;
18 import java.util.Map;
19 
20 import org.w3c.dom.DOMImplementation;
21 import 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  */
29 public 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      */
LSStrategy()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      */
applySetting(DocumentBuilderSetting setting, Object parser)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      */
hasSetting(Object parser)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      */
LSFixedStrategy(boolean fixedValue)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      */
applySetting(DocumentBuilderSetting setting, Object parser)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      */
hasSetting(Object parser)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      */
LSParameterStrategy(String lsParameter, boolean inverse)125     public LSParameterStrategy(String lsParameter, boolean inverse) {
126       this.lsParameter = lsParameter;
127       this.inverse = inverse;
128     }
129 
setParameter(DocumentBuilderSetting setting, Object parser, String parameter, Object value)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 
getParameter(Object parser, String parameter)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      */
applySetting(DocumentBuilderSetting setting, Object parser)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      */
hasSetting(Object parser)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      */
LSValidateStrategy(String schemaType)207     public LSValidateStrategy(String schemaType) {
208       super("validate", false);
209       this.schemaType = schemaType;
210     }
211 
212     /**
213      * Apply setting
214      */
applySetting(DocumentBuilderSetting setting, Object parser)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      */
hasSetting(Object parser)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    */
LSDocumentBuilderFactory(DocumentBuilderSetting[] settings)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    */
newInstance( DocumentBuilderSetting[] newSettings)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    */
load(java.net.URL url)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    */
getDOMImplementation()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    */
hasFeature(String feature, String version)358   public boolean hasFeature(String feature, String version) {
359     return getDOMImplementation().hasFeature(feature, version);
360   }
361 
hasProperty(String parameter)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    */
isCoalescing()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    */
isExpandEntityReferences()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    */
isIgnoringElementContentWhitespace()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    */
isNamespaceAware()402   public boolean isNamespaceAware() {
403     return hasProperty("namespaces");
404   }
405 
406   /**
407    *   Indicates whether the implementation is validating.
408    *   @return true if validating
409    */
isValidating()410   public boolean isValidating() {
411     return hasProperty("validate");
412   }
413 
414 }
415