aoqi@0: /*
aoqi@0: * Copyright (c) 1997, 2011, Oracle and/or its affiliates. All rights reserved.
aoqi@0: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
aoqi@0: *
aoqi@0: * This code is free software; you can redistribute it and/or modify it
aoqi@0: * under the terms of the GNU General Public License version 2 only, as
aoqi@0: * published by the Free Software Foundation. Oracle designates this
aoqi@0: * particular file as subject to the "Classpath" exception as provided
aoqi@0: * by Oracle in the LICENSE file that accompanied this code.
aoqi@0: *
aoqi@0: * This code is distributed in the hope that it will be useful, but WITHOUT
aoqi@0: * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
aoqi@0: * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
aoqi@0: * version 2 for more details (a copy is included in the LICENSE file that
aoqi@0: * accompanied this code).
aoqi@0: *
aoqi@0: * You should have received a copy of the GNU General Public License version
aoqi@0: * 2 along with this work; if not, write to the Free Software Foundation,
aoqi@0: * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
aoqi@0: *
aoqi@0: * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
aoqi@0: * or visit www.oracle.com if you need additional information or have any
aoqi@0: * questions.
aoqi@0: */
aoqi@0:
aoqi@0: package com.sun.tools.internal.xjc.reader.internalizer;
aoqi@0:
aoqi@0: import java.util.ArrayList;
aoqi@0: import java.util.Collection;
aoqi@0: import java.util.Iterator;
aoqi@0: import java.util.List;
aoqi@0:
aoqi@0: import javax.xml.bind.JAXBException;
aoqi@0: import javax.xml.bind.Unmarshaller;
aoqi@0: import javax.xml.bind.UnmarshallerHandler;
aoqi@0: import javax.xml.validation.ValidatorHandler;
aoqi@0:
aoqi@0: import com.sun.istack.internal.NotNull;
aoqi@0: import com.sun.istack.internal.SAXParseException2;
aoqi@0: import com.sun.tools.internal.xjc.ErrorReceiver;
aoqi@0: import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
aoqi@0: import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo;
aoqi@0: import com.sun.tools.internal.xjc.util.ForkContentHandler;
aoqi@0: import com.sun.tools.internal.xjc.util.DOMUtils;
aoqi@0: import com.sun.xml.internal.xsom.SCD;
aoqi@0: import com.sun.xml.internal.xsom.XSAnnotation;
aoqi@0: import com.sun.xml.internal.xsom.XSComponent;
aoqi@0: import com.sun.xml.internal.xsom.XSSchemaSet;
aoqi@0:
aoqi@0: import org.w3c.dom.Element;
aoqi@0: import org.xml.sax.SAXException;
aoqi@0: import org.xml.sax.SAXParseException;
aoqi@0:
aoqi@0: /**
aoqi@0: * Set of binding nodes that have target nodes specified via SCD.
aoqi@0: *
aoqi@0: * This is parsed during {@link Internalizer} works on the tree,
aoqi@0: * but applying this has to wait for {@link XSSchemaSet} to be parsed.
aoqi@0: *
aoqi@0: * @author Kohsuke Kawaguchi
aoqi@0: * @see SCD
aoqi@0: */
aoqi@0: public final class SCDBasedBindingSet {
aoqi@0:
aoqi@0: /**
aoqi@0: * Represents the target schema component of the
aoqi@0: * customization identified by SCD.
aoqi@0: *
aoqi@0: * @author Kohsuke Kawaguchi
aoqi@0: */
aoqi@0: final class Target {
aoqi@0: /**
aoqi@0: * SCDs can be specified via multiple steps, like:
aoqi@0: *
aoqi@0: *
aoqi@0: *
aoqi@0: *
aoqi@0: *
aoqi@0: *
aoqi@0: * This field and {@link #nextSibling} form a single-linked list that
aoqi@0: * represent the children that shall be evaluated within this target.
aoqi@0: * Think of it as {@code List}.
aoqi@0: */
aoqi@0: private Target firstChild;
aoqi@0: private final Target nextSibling;
aoqi@0:
aoqi@0: /**
aoqi@0: * Compiled SCD.
aoqi@0: */
aoqi@0: private final @NotNull SCD scd;
aoqi@0:
aoqi@0: /**
aoqi@0: * The element on which SCD was found.
aoqi@0: */
aoqi@0: private final @NotNull Element src;
aoqi@0:
aoqi@0: /**
aoqi@0: * Bindings that apply to this SCD.
aoqi@0: */
aoqi@0: private final List bindings = new ArrayList();
aoqi@0:
aoqi@0: private Target(Target parent, Element src, SCD scd) {
aoqi@0: if(parent==null) {
aoqi@0: this.nextSibling = topLevel;
aoqi@0: topLevel = this;
aoqi@0: } else {
aoqi@0: this.nextSibling = parent.firstChild;
aoqi@0: parent.firstChild = this;
aoqi@0: }
aoqi@0: this.src = src;
aoqi@0: this.scd = scd;
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Adds a new binding declaration to be associated to the schema component
aoqi@0: * identified by {@link #scd}.
aoqi@0: */
aoqi@0: void addBinidng(Element binding) {
aoqi@0: bindings.add(binding);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Applies bindings to the schema component for this and its siblings.
aoqi@0: */
aoqi@0: private void applyAll(Collection extends XSComponent> contextNode) {
aoqi@0: for( Target self=this; self!=null; self=self.nextSibling )
aoqi@0: self.apply(contextNode);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Applies bindings to the schema component for just this node.
aoqi@0: */
aoqi@0: private void apply(Collection extends XSComponent> contextNode) {
aoqi@0: // apply the SCD...
aoqi@0: Collection childNodes = scd.select(contextNode);
aoqi@0: if(childNodes.isEmpty()) {
aoqi@0: // no node matched
aoqi@0: if(src.getAttributeNode("if-exists")!=null) {
aoqi@0: // if this attribute exists, it's not an error if SCD didn't match.
aoqi@0: return;
aoqi@0: }
aoqi@0:
aoqi@0: reportError( src, Messages.format(Messages.ERR_SCD_EVALUATED_EMPTY,scd) );
aoqi@0: return;
aoqi@0: }
aoqi@0:
aoqi@0: if(firstChild!=null)
aoqi@0: firstChild.applyAll(childNodes);
aoqi@0:
aoqi@0: if(!bindings.isEmpty()) {
aoqi@0: // error to match more than one components
aoqi@0: Iterator itr = childNodes.iterator();
aoqi@0: XSComponent target = itr.next();
aoqi@0: if(itr.hasNext()) {
aoqi@0: reportError( src, Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES,scd,childNodes.size()) );
aoqi@0: errorReceiver.error( target.getLocator(), Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES_FIRST) );
aoqi@0: errorReceiver.error( itr.next().getLocator(), Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES_SECOND) );
aoqi@0: }
aoqi@0:
aoqi@0: // apply bindings to the target
aoqi@0: for (Element binding : bindings) {
aoqi@0: for (Element item : DOMUtils.getChildElements(binding)) {
aoqi@0: String localName = item.getLocalName();
aoqi@0:
aoqi@0: if ("bindings".equals(localName))
aoqi@0: continue; // this should be already in Target.bindings of some SpecVersion.
aoqi@0:
aoqi@0: try {
aoqi@0: new DOMForestScanner(forest).scan(item,loader);
aoqi@0: BIDeclaration decl = (BIDeclaration)unmarshaller.getResult();
aoqi@0:
aoqi@0: // add this binding to the target
aoqi@0: XSAnnotation ann = target.getAnnotation(true);
aoqi@0: BindInfo bi = (BindInfo)ann.getAnnotation();
aoqi@0: if(bi==null) {
aoqi@0: bi = new BindInfo();
aoqi@0: ann.setAnnotation(bi);
aoqi@0: }
aoqi@0: bi.addDecl(decl);
aoqi@0: } catch (SAXException e) {
aoqi@0: // the error should have already been reported.
aoqi@0: } catch (JAXBException e) {
aoqi@0: // if validation didn't fail, then unmarshalling can't go wrong
aoqi@0: throw new AssertionError(e);
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: private Target topLevel;
aoqi@0:
aoqi@0: /**
aoqi@0: * The forest where binding elements came from. Needed to report line numbers for errors.
aoqi@0: */
aoqi@0: private final DOMForest forest;
aoqi@0:
aoqi@0:
aoqi@0: // variables used only during the apply method
aoqi@0: //
aoqi@0: private ErrorReceiver errorReceiver;
aoqi@0: private UnmarshallerHandler unmarshaller;
aoqi@0: private ForkContentHandler loader; // unmarshaller+validator
aoqi@0:
aoqi@0: SCDBasedBindingSet(DOMForest forest) {
aoqi@0: this.forest = forest;
aoqi@0: }
aoqi@0:
aoqi@0: Target createNewTarget(Target parent, Element src, SCD scd) {
aoqi@0: return new Target(parent,src,scd);
aoqi@0: }
aoqi@0:
aoqi@0: /**
aoqi@0: * Applies the additional binding customizations.
aoqi@0: */
aoqi@0: public void apply(XSSchemaSet schema, ErrorReceiver errorReceiver) {
aoqi@0: if(topLevel!=null) {
aoqi@0: this.errorReceiver = errorReceiver;
aoqi@0: Unmarshaller u = BindInfo.getCustomizationUnmarshaller();
aoqi@0: this.unmarshaller = u.getUnmarshallerHandler();
aoqi@0: ValidatorHandler v = BindInfo.bindingFileSchema.newValidator();
aoqi@0: v.setErrorHandler(errorReceiver);
aoqi@0: loader = new ForkContentHandler(v,unmarshaller);
aoqi@0:
aoqi@0: topLevel.applyAll(schema.getSchemas());
aoqi@0:
aoqi@0: this.loader = null;
aoqi@0: this.unmarshaller = null;
aoqi@0: this.errorReceiver = null;
aoqi@0: }
aoqi@0: }
aoqi@0:
aoqi@0: private void reportError( Element errorSource, String formattedMsg ) {
aoqi@0: reportError( errorSource, formattedMsg, null );
aoqi@0: }
aoqi@0:
aoqi@0: private void reportError( Element errorSource,
aoqi@0: String formattedMsg, Exception nestedException ) {
aoqi@0:
aoqi@0: SAXParseException e = new SAXParseException2( formattedMsg,
aoqi@0: forest.locatorTable.getStartLocation(errorSource),
aoqi@0: nestedException );
aoqi@0: errorReceiver.error(e);
aoqi@0: }
aoqi@0: }