1/*
2 * Copyright (c) 2008, 2012, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25package com.sun.beans.decoder;
26
27import com.sun.beans.finder.ClassFinder;
28
29import java.beans.ExceptionListener;
30
31import java.io.IOException;
32import java.io.StringReader;
33
34import java.lang.ref.Reference;
35import java.lang.ref.WeakReference;
36
37import java.util.ArrayList;
38import java.util.HashMap;
39import java.util.List;
40import java.util.Map;
41import java.security.AccessControlContext;
42import java.security.AccessController;
43import java.security.PrivilegedAction;
44
45import javax.xml.parsers.ParserConfigurationException;
46import javax.xml.parsers.SAXParserFactory;
47
48import org.xml.sax.Attributes;
49import org.xml.sax.InputSource;
50import org.xml.sax.SAXException;
51import org.xml.sax.helpers.DefaultHandler;
52
53import jdk.internal.misc.SharedSecrets;
54
55/**
56 * The main class to parse JavaBeans XML archive.
57 *
58 * @since 1.7
59 *
60 * @author Sergey A. Malenkov
61 *
62 * @see ElementHandler
63 */
64public final class DocumentHandler extends DefaultHandler {
65    private final AccessControlContext acc = AccessController.getContext();
66    private final Map<String, Class<? extends ElementHandler>> handlers = new HashMap<>();
67    private final Map<String, Object> environment = new HashMap<>();
68    private final List<Object> objects = new ArrayList<>();
69
70    private Reference<ClassLoader> loader;
71    private ExceptionListener listener;
72    private Object owner;
73
74    private ElementHandler handler;
75
76    /**
77     * Creates new instance of document handler.
78     */
79    public DocumentHandler() {
80        setElementHandler("java", JavaElementHandler.class); // NON-NLS: the element name
81        setElementHandler("null", NullElementHandler.class); // NON-NLS: the element name
82        setElementHandler("array", ArrayElementHandler.class); // NON-NLS: the element name
83        setElementHandler("class", ClassElementHandler.class); // NON-NLS: the element name
84        setElementHandler("string", StringElementHandler.class); // NON-NLS: the element name
85        setElementHandler("object", ObjectElementHandler.class); // NON-NLS: the element name
86
87        setElementHandler("void", VoidElementHandler.class); // NON-NLS: the element name
88        setElementHandler("char", CharElementHandler.class); // NON-NLS: the element name
89        setElementHandler("byte", ByteElementHandler.class); // NON-NLS: the element name
90        setElementHandler("short", ShortElementHandler.class); // NON-NLS: the element name
91        setElementHandler("int", IntElementHandler.class); // NON-NLS: the element name
92        setElementHandler("long", LongElementHandler.class); // NON-NLS: the element name
93        setElementHandler("float", FloatElementHandler.class); // NON-NLS: the element name
94        setElementHandler("double", DoubleElementHandler.class); // NON-NLS: the element name
95        setElementHandler("boolean", BooleanElementHandler.class); // NON-NLS: the element name
96
97        // some handlers for new elements
98        setElementHandler("new", NewElementHandler.class); // NON-NLS: the element name
99        setElementHandler("var", VarElementHandler.class); // NON-NLS: the element name
100        setElementHandler("true", TrueElementHandler.class); // NON-NLS: the element name
101        setElementHandler("false", FalseElementHandler.class); // NON-NLS: the element name
102        setElementHandler("field", FieldElementHandler.class); // NON-NLS: the element name
103        setElementHandler("method", MethodElementHandler.class); // NON-NLS: the element name
104        setElementHandler("property", PropertyElementHandler.class); // NON-NLS: the element name
105    }
106
107    /**
108     * Returns the class loader used to instantiate objects.
109     * If the class loader has not been explicitly set
110     * then {@code null} is returned.
111     *
112     * @return the class loader used to instantiate objects
113     */
114    public ClassLoader getClassLoader() {
115        return (this.loader != null)
116                ? this.loader.get()
117                : null;
118    }
119
120    /**
121     * Sets the class loader used to instantiate objects.
122     * If the class loader is not set
123     * then default class loader will be used.
124     *
125     * @param loader  a classloader to use
126     */
127    public void setClassLoader(ClassLoader loader) {
128        this.loader = new WeakReference<ClassLoader>(loader);
129    }
130
131    /**
132     * Returns the exception listener for parsing.
133     * The exception listener is notified
134     * when handler catches recoverable exceptions.
135     * If the exception listener has not been explicitly set
136     * then default exception listener is returned.
137     *
138     * @return the exception listener for parsing
139     */
140    public ExceptionListener getExceptionListener() {
141        return this.listener;
142    }
143
144    /**
145     * Sets the exception listener for parsing.
146     * The exception listener is notified
147     * when handler catches recoverable exceptions.
148     *
149     * @param listener  the exception listener for parsing
150     */
151    public void setExceptionListener(ExceptionListener listener) {
152        this.listener = listener;
153    }
154
155    /**
156     * Returns the owner of this document handler.
157     *
158     * @return the owner of this document handler
159     */
160    public Object getOwner() {
161        return this.owner;
162    }
163
164    /**
165     * Sets the owner of this document handler.
166     *
167     * @param owner  the owner of this document handler
168     */
169    public void setOwner(Object owner) {
170        this.owner = owner;
171    }
172
173    /**
174     * Returns the handler for the element with specified name.
175     *
176     * @param name  the name of the element
177     * @return the corresponding element handler
178     */
179    public Class<? extends ElementHandler> getElementHandler(String name) {
180        Class<? extends ElementHandler> type = this.handlers.get(name);
181        if (type == null) {
182            throw new IllegalArgumentException("Unsupported element: " + name);
183        }
184        return type;
185    }
186
187    /**
188     * Sets the handler for the element with specified name.
189     *
190     * @param name     the name of the element
191     * @param handler  the corresponding element handler
192     */
193    public void setElementHandler(String name, Class<? extends ElementHandler> handler) {
194        this.handlers.put(name, handler);
195    }
196
197    /**
198     * Indicates whether the variable with specified identifier is defined.
199     *
200     * @param id  the identifier
201     * @return @{code true} if the variable is defined;
202     *         @{code false} otherwise
203     */
204    public boolean hasVariable(String id) {
205        return this.environment.containsKey(id);
206    }
207
208    /**
209     * Returns the value of the variable with specified identifier.
210     *
211     * @param id  the identifier
212     * @return the value of the variable
213     */
214    public Object getVariable(String id) {
215        if (!this.environment.containsKey(id)) {
216            throw new IllegalArgumentException("Unbound variable: " + id);
217        }
218        return this.environment.get(id);
219    }
220
221    /**
222     * Sets new value of the variable with specified identifier.
223     *
224     * @param id     the identifier
225     * @param value  new value of the variable
226     */
227    public void setVariable(String id, Object value) {
228        this.environment.put(id, value);
229    }
230
231    /**
232     * Returns the array of readed objects.
233     *
234     * @return the array of readed objects
235     */
236    public Object[] getObjects() {
237        return this.objects.toArray();
238    }
239
240    /**
241     * Adds the object to the list of readed objects.
242     *
243     * @param object  the object that is readed from XML document
244     */
245    void addObject(Object object) {
246        this.objects.add(object);
247    }
248
249    /**
250     * Disables any external entities.
251     */
252    @Override
253    public InputSource resolveEntity(String publicId, String systemId) {
254        return new InputSource(new StringReader(""));
255    }
256
257    /**
258     * Prepares this handler to read objects from XML document.
259     */
260    @Override
261    public void startDocument() {
262        this.objects.clear();
263        this.handler = null;
264    }
265
266    /**
267     * Parses opening tag of XML element
268     * using corresponding element handler.
269     *
270     * @param uri         the namespace URI, or the empty string
271     *                    if the element has no namespace URI or
272     *                    if namespace processing is not being performed
273     * @param localName   the local name (without prefix), or the empty string
274     *                    if namespace processing is not being performed
275     * @param qName       the qualified name (with prefix), or the empty string
276     *                    if qualified names are not available
277     * @param attributes  the attributes attached to the element
278     */
279    @Override
280    @SuppressWarnings("deprecation")
281    public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
282        ElementHandler parent = this.handler;
283        try {
284            this.handler =
285                getElementHandler(qName).newInstance();
286            this.handler.setOwner(this);
287            this.handler.setParent(parent);
288        }
289        catch (Exception exception) {
290            throw new SAXException(exception);
291        }
292        for (int i = 0; i < attributes.getLength(); i++)
293            try {
294                String name = attributes.getQName(i);
295                String value = attributes.getValue(i);
296                this.handler.addAttribute(name, value);
297            }
298            catch (RuntimeException exception) {
299                handleException(exception);
300            }
301
302        this.handler.startElement();
303    }
304
305    /**
306     * Parses closing tag of XML element
307     * using corresponding element handler.
308     *
309     * @param uri        the namespace URI, or the empty string
310     *                   if the element has no namespace URI or
311     *                   if namespace processing is not being performed
312     * @param localName  the local name (without prefix), or the empty string
313     *                   if namespace processing is not being performed
314     * @param qName      the qualified name (with prefix), or the empty string
315     *                   if qualified names are not available
316     */
317    @Override
318    public void endElement(String uri, String localName, String qName) {
319        try {
320            this.handler.endElement();
321        }
322        catch (RuntimeException exception) {
323            handleException(exception);
324        }
325        finally {
326            this.handler = this.handler.getParent();
327        }
328    }
329
330    /**
331     * Parses character data inside XML element.
332     *
333     * @param chars   the array of characters
334     * @param start   the start position in the character array
335     * @param length  the number of characters to use
336     */
337    @Override
338    public void characters(char[] chars, int start, int length) {
339        if (this.handler != null) {
340            try {
341                while (0 < length--) {
342                    this.handler.addCharacter(chars[start++]);
343                }
344            }
345            catch (RuntimeException exception) {
346                handleException(exception);
347            }
348        }
349    }
350
351    /**
352     * Handles an exception using current exception listener.
353     *
354     * @param exception  an exception to handle
355     * @see #setExceptionListener
356     */
357    public void handleException(Exception exception) {
358        if (this.listener == null) {
359            throw new IllegalStateException(exception);
360        }
361        this.listener.exceptionThrown(exception);
362    }
363
364    /**
365     * Starts parsing of the specified input source.
366     *
367     * @param input  the input source to parse
368     */
369    public void parse(final InputSource input) {
370        if ((this.acc == null) && (null != System.getSecurityManager())) {
371            throw new SecurityException("AccessControlContext is not set");
372        }
373        AccessControlContext stack = AccessController.getContext();
374        SharedSecrets.getJavaSecurityAccess().doIntersectionPrivilege(new PrivilegedAction<Void>() {
375            public Void run() {
376                try {
377                    SAXParserFactory.newInstance().newSAXParser().parse(input, DocumentHandler.this);
378                }
379                catch (ParserConfigurationException exception) {
380                    handleException(exception);
381                }
382                catch (SAXException wrapper) {
383                    Exception exception = wrapper.getException();
384                    if (exception == null) {
385                        exception = wrapper;
386                    }
387                    handleException(exception);
388                }
389                catch (IOException exception) {
390                    handleException(exception);
391                }
392                return null;
393            }
394        }, stack, this.acc);
395    }
396
397    /**
398     * Resolves class by name using current class loader.
399     * This method handles exception using current exception listener.
400     *
401     * @param name  the name of the class
402     * @return the object that represents the class
403     */
404    public Class<?> findClass(String name) {
405        try {
406            return ClassFinder.resolveClass(name, getClassLoader());
407        }
408        catch (ClassNotFoundException exception) {
409            handleException(exception);
410            return null;
411        }
412    }
413}
414