001: /*
002: * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
003: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
004: *
005: * This code is free software; you can redistribute it and/or modify it
006: * under the terms of the GNU General Public License version 2 only, as
007: * published by the Free Software Foundation. Sun designates this
008: * particular file as subject to the "Classpath" exception as provided
009: * by Sun in the LICENSE file that accompanied this code.
010: *
011: * This code is distributed in the hope that it will be useful, but WITHOUT
012: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
013: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
014: * version 2 for more details (a copy is included in the LICENSE file that
015: * accompanied this code).
016: *
017: * You should have received a copy of the GNU General Public License version
018: * 2 along with this work; if not, write to the Free Software Foundation,
019: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
020: *
021: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
022: * CA 95054 USA or visit www.sun.com if you need additional information or
023: * have any questions.
024: */
025:
026: package com.sun.tools.internal.xjc.reader.internalizer;
027:
028: import java.net.MalformedURLException;
029: import java.net.URL;
030: import java.util.HashMap;
031: import java.util.HashSet;
032: import java.util.Map;
033: import java.util.Set;
034:
035: import javax.xml.xpath.XPath;
036: import javax.xml.xpath.XPathConstants;
037: import javax.xml.xpath.XPathExpressionException;
038: import javax.xml.xpath.XPathFactory;
039:
040: import com.sun.istack.internal.SAXParseException2;
041: import com.sun.tools.internal.xjc.ErrorReceiver;
042: import com.sun.tools.internal.xjc.reader.Const;
043: import com.sun.tools.internal.xjc.util.DOMUtils;
044: import com.sun.xml.internal.bind.v2.util.EditDistance;
045:
046: import org.w3c.dom.Attr;
047: import org.w3c.dom.Document;
048: import org.w3c.dom.Element;
049: import org.w3c.dom.NamedNodeMap;
050: import org.w3c.dom.Node;
051: import org.w3c.dom.NodeList;
052: import org.xml.sax.SAXParseException;
053:
054: /**
055: * Internalizes external binding declarations.
056: * <p>
057: * The static "transform" method is the entry point.
058: *
059: * @author
060: * Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com)
061: */
062: class Internalizer {
063:
064: private static final XPathFactory xpf = XPathFactory.newInstance();
065:
066: private final XPath xpath = xpf.newXPath();
067:
068: /**
069: * Internalize all <jaxb:bindings> customizations in the given forest.
070: */
071: static void transform(DOMForest forest) {
072: new Internalizer(forest).transform();
073: }
074:
075: private Internalizer(DOMForest forest) {
076: this .errorHandler = forest.getErrorHandler();
077: this .forest = forest;
078: }
079:
080: /**
081: * DOMForest object currently being processed.
082: */
083: private final DOMForest forest;
084:
085: /**
086: * All errors found during the transformation is sent to this object.
087: */
088: private ErrorReceiver errorHandler;
089:
090: private void transform() {
091:
092: Map<Element, Node> targetNodes = new HashMap<Element, Node>();
093:
094: //
095: // identify target nodes for all <jaxb:bindings>
096: //
097: for (Element jaxbBindings : forest.outerMostBindings) {
098: // initially, the inherited context is itself
099: buildTargetNodeMap(jaxbBindings, jaxbBindings, targetNodes);
100: }
101:
102: //
103: // then move them to their respective positions.
104: //
105: for (Element jaxbBindings : forest.outerMostBindings) {
106: move(jaxbBindings, targetNodes);
107: }
108: }
109:
110: /**
111: * Validates attributes of a <jaxb:bindings> element.
112: */
113: private void validate(Element bindings) {
114: NamedNodeMap atts = bindings.getAttributes();
115: for (int i = 0; i < atts.getLength(); i++) {
116: Attr a = (Attr) atts.item(i);
117: if (a.getNamespaceURI() != null)
118: continue; // all foreign namespace OK.
119: if (a.getLocalName().equals("node"))
120: continue;
121: if (a.getLocalName().equals("schemaLocation"))
122: continue;
123:
124: // TODO: flag error for this undefined attribute
125: }
126: }
127:
128: /**
129: * Determines the target node of the "bindings" element
130: * by using the inherited target node, then put
131: * the result into the "result" map.
132: */
133: private void buildTargetNodeMap(Element bindings,
134: Node inheritedTarget, Map<Element, Node> result) {
135: // start by the inherited target
136: Node target = inheritedTarget;
137:
138: validate(bindings); // validate this node
139:
140: // look for @schemaLocation
141: if (bindings.getAttributeNode("schemaLocation") != null) {
142: String schemaLocation = bindings
143: .getAttribute("schemaLocation");
144:
145: try {
146: // absolutize this URI.
147: // TODO: use the URI class
148: // TODO: honor xml:base
149: schemaLocation = new URL(new URL(forest
150: .getSystemId(bindings.getOwnerDocument())),
151: schemaLocation).toExternalForm();
152: } catch (MalformedURLException e) {
153: ; // continue with the original schemaLocation value
154: }
155:
156: target = forest.get(schemaLocation);
157: if (target == null) {
158: reportError(
159: bindings,
160: Messages
161: .format(
162: Messages.ERR_INCORRECT_SCHEMA_REFERENCE,
163: schemaLocation,
164: EditDistance.findNearest(
165: schemaLocation,
166: forest.listSystemIDs())));
167:
168: return; // abort processing this <jaxb:bindings>
169: }
170: }
171:
172: // look for @node
173: if (bindings.getAttributeNode("node") != null) {
174: String nodeXPath = bindings.getAttribute("node");
175:
176: // evaluate this XPath
177: NodeList nlst;
178: try {
179: xpath.setNamespaceContext(new NamespaceContextImpl(
180: bindings));
181: nlst = (NodeList) xpath.evaluate(nodeXPath, target,
182: XPathConstants.NODESET);
183: } catch (XPathExpressionException e) {
184: reportError(bindings, Messages.format(
185: Messages.ERR_XPATH_EVAL, e.getMessage()), e);
186: return; // abort processing this <jaxb:bindings>
187: }
188:
189: if (nlst.getLength() == 0) {
190: reportError(bindings, Messages.format(
191: Messages.NO_XPATH_EVAL_TO_NO_TARGET, nodeXPath));
192: return; // abort
193: }
194:
195: if (nlst.getLength() != 1) {
196: reportError(bindings, Messages.format(
197: Messages.NO_XPATH_EVAL_TOO_MANY_TARGETS,
198: nodeXPath, nlst.getLength()));
199: return; // abort
200: }
201:
202: Node rnode = nlst.item(0);
203: if (!(rnode instanceof Element)) {
204: reportError(bindings, Messages.format(
205: Messages.NO_XPATH_EVAL_TO_NON_ELEMENT,
206: nodeXPath));
207: return; // abort
208: }
209:
210: if (!forest.logic.checkIfValidTargetNode(forest, bindings,
211: (Element) rnode)) {
212: reportError(bindings, Messages.format(
213: Messages.XPATH_EVAL_TO_NON_SCHEMA_ELEMENT,
214: nodeXPath, rnode.getNodeName()));
215: return; // abort
216: }
217:
218: target = rnode;
219: }
220:
221: // update the result map
222: result.put(bindings, target);
223:
224: // look for child <jaxb:bindings> and process them recursively
225: Element[] children = DOMUtils.getChildElements(bindings,
226: Const.JAXB_NSURI, "bindings");
227: for (Element value : children)
228: buildTargetNodeMap(value, target, result);
229: }
230:
231: /**
232: * Moves JAXB customizations under their respective target nodes.
233: */
234: private void move(Element bindings, Map<Element, Node> targetNodes) {
235: Node target = targetNodes.get(bindings);
236: if (target == null)
237: // this must be the result of an error on the external binding.
238: // recover from the error by ignoring this node
239: return;
240:
241: Element[] children = DOMUtils.getChildElements(bindings);
242: for (Element item : children) {
243: if ("bindings".equals(item.getLocalName()))
244: // process child <jaxb:bindings> recursively
245: move(item, targetNodes);
246: else {
247: if (!(target instanceof Element)) {
248: if (target instanceof Document) {
249: // we set the context node to the document when @schemaLocation is used.
250: reportError(
251: item,
252: Messages
253: .format(Messages.NO_CONTEXT_NODE_SPECIFIED));
254: } else {
255: reportError(
256: item,
257: Messages
258: .format(Messages.CONTEXT_NODE_IS_NOT_ELEMENT));
259: }
260: return; // abort
261: }
262:
263: if (!forest.logic.checkIfValidTargetNode(forest, item,
264: (Element) target)) {
265: reportError(item, Messages.format(
266: Messages.ORPHANED_CUSTOMIZATION, item
267: .getNodeName()));
268: return; // abort
269: }
270: // move this node under the target
271: moveUnder(item, (Element) target);
272: }
273: }
274: }
275:
276: /**
277: * Moves the "decl" node under the "target" node.
278: *
279: * @param decl
280: * A JAXB customization element (e.g., <jaxb:class>)
281: *
282: * @param target
283: * XML Schema element under which the declaration should move.
284: * For example, <xs:element>
285: */
286: private void moveUnder(Element decl, Element target) {
287: Element realTarget = forest.logic.refineTarget(target);
288:
289: declExtensionNamespace(decl, target);
290:
291: // copy in-scope namespace declarations of the decl node
292: // to the decl node itself so that this move won't change
293: // the in-scope namespace bindings.
294: Element p = decl;
295: Set<String> inscopes = new HashSet<String>();
296: while (true) {
297: NamedNodeMap atts = p.getAttributes();
298: for (int i = 0; i < atts.getLength(); i++) {
299: Attr a = (Attr) atts.item(i);
300: if (Const.XMLNS_URI.equals(a.getNamespaceURI())) {
301: String prefix;
302: if (a.getName().indexOf(':') == -1)
303: prefix = "";
304: else
305: prefix = a.getLocalName();
306:
307: if (inscopes.add(prefix) && p != decl) {
308: // if this is the first time we see this namespace bindings,
309: // copy the declaration.
310: // if p==decl, there's no need to. Note that
311: // we want to add prefix to inscopes even if p==Decl
312:
313: decl.setAttributeNodeNS((Attr) a
314: .cloneNode(true));
315: }
316: }
317: }
318:
319: if (p.getParentNode() instanceof Document)
320: break;
321:
322: p = (Element) p.getParentNode();
323: }
324:
325: if (!inscopes.contains("")) {
326: // if the default namespace was undeclared in the context of decl,
327: // it must be explicitly set to "" since the new environment might
328: // have a different default namespace URI.
329: decl.setAttributeNS(Const.XMLNS_URI, "xmlns", "");
330: }
331:
332: // finally move the declaration to the target node.
333: if (realTarget.getOwnerDocument() != decl.getOwnerDocument()) {
334: // if they belong to different DOM documents, we need to clone them
335: Element original = decl;
336: decl = (Element) realTarget.getOwnerDocument().importNode(
337: decl, true);
338:
339: // this effectively clones a ndoe,, so we need to copy locators.
340: copyLocators(original, decl);
341: }
342:
343: realTarget.appendChild(decl);
344: }
345:
346: /**
347: * Recursively visits sub-elements and declare all used namespaces.
348: * TODO: the fact that we recognize all namespaces in the extension
349: * is a bad design.
350: */
351: private void declExtensionNamespace(Element decl, Element target) {
352: // if this comes from external namespaces, add the namespace to
353: // @extensionBindingPrefixes.
354: if (!Const.JAXB_NSURI.equals(decl.getNamespaceURI()))
355: declareExtensionNamespace(target, decl.getNamespaceURI());
356:
357: NodeList lst = decl.getChildNodes();
358: for (int i = 0; i < lst.getLength(); i++) {
359: Node n = lst.item(i);
360: if (n instanceof Element)
361: declExtensionNamespace((Element) n, target);
362: }
363: }
364:
365: /** Attribute name. */
366: private static final String EXTENSION_PREFIXES = "extensionBindingPrefixes";
367:
368: /**
369: * Adds the specified namespace URI to the jaxb:extensionBindingPrefixes
370: * attribute of the target document.
371: */
372: private void declareExtensionNamespace(Element target, String nsUri) {
373: // look for the attribute
374: Element root = target.getOwnerDocument().getDocumentElement();
375: Attr att = root.getAttributeNodeNS(Const.JAXB_NSURI,
376: EXTENSION_PREFIXES);
377: if (att == null) {
378: String jaxbPrefix = allocatePrefix(root, Const.JAXB_NSURI);
379: // no such attribute. Create one.
380: att = target.getOwnerDocument().createAttributeNS(
381: Const.JAXB_NSURI,
382: jaxbPrefix + ':' + EXTENSION_PREFIXES);
383: root.setAttributeNodeNS(att);
384: }
385:
386: String prefix = allocatePrefix(root, nsUri);
387: if (att.getValue().indexOf(prefix) == -1)
388: // avoid redeclaring the same namespace twice.
389: att.setValue(att.getValue() + ' ' + prefix);
390: }
391:
392: /**
393: * Declares a new prefix on the given element and associates it
394: * with the specified namespace URI.
395: * <p>
396: * Note that this method doesn't use the default namespace
397: * even if it can.
398: */
399: private String allocatePrefix(Element e, String nsUri) {
400: // look for existing namespaces.
401: NamedNodeMap atts = e.getAttributes();
402: for (int i = 0; i < atts.getLength(); i++) {
403: Attr a = (Attr) atts.item(i);
404: if (Const.XMLNS_URI.equals(a.getNamespaceURI())) {
405: if (a.getName().indexOf(':') == -1)
406: continue;
407:
408: if (a.getValue().equals(nsUri))
409: return a.getLocalName(); // found one
410: }
411: }
412:
413: // none found. allocate new.
414: while (true) {
415: String prefix = "p" + (int) (Math.random() * 1000000) + '_';
416: if (e.getAttributeNodeNS(Const.XMLNS_URI, prefix) != null)
417: continue; // this prefix is already allocated.
418:
419: e.setAttributeNS(Const.XMLNS_URI, "xmlns:" + prefix, nsUri);
420: return prefix;
421: }
422: }
423:
424: /**
425: * Copies location information attached to the "src" node to the "dst" node.
426: */
427: private void copyLocators(Element src, Element dst) {
428: forest.locatorTable.storeStartLocation(dst, forest.locatorTable
429: .getStartLocation(src));
430: forest.locatorTable.storeEndLocation(dst, forest.locatorTable
431: .getEndLocation(src));
432:
433: // recursively process child elements
434: Element[] srcChilds = DOMUtils.getChildElements(src);
435: Element[] dstChilds = DOMUtils.getChildElements(dst);
436:
437: for (int i = 0; i < srcChilds.length; i++)
438: copyLocators(srcChilds[i], dstChilds[i]);
439: }
440:
441: private void reportError(Element errorSource, String formattedMsg) {
442: reportError(errorSource, formattedMsg, null);
443: }
444:
445: private void reportError(Element errorSource, String formattedMsg,
446: Exception nestedException) {
447:
448: SAXParseException e = new SAXParseException2(formattedMsg,
449: forest.locatorTable.getStartLocation(errorSource),
450: nestedException);
451: errorHandler.error(e);
452: }
453: }
|