View Javadoc
1   /*
2    * Copyright (c) 1997, 2011, 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   */
25  
26  package com.sun.tools.internal.xjc.reader.internalizer;
27  
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Iterator;
31  import java.util.List;
32  
33  import javax.xml.bind.JAXBException;
34  import javax.xml.bind.Unmarshaller;
35  import javax.xml.bind.UnmarshallerHandler;
36  import javax.xml.validation.ValidatorHandler;
37  
38  import com.sun.istack.internal.NotNull;
39  import com.sun.istack.internal.SAXParseException2;
40  import com.sun.tools.internal.xjc.ErrorReceiver;
41  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BIDeclaration;
42  import com.sun.tools.internal.xjc.reader.xmlschema.bindinfo.BindInfo;
43  import com.sun.tools.internal.xjc.util.ForkContentHandler;
44  import com.sun.tools.internal.xjc.util.DOMUtils;
45  import com.sun.xml.internal.xsom.SCD;
46  import com.sun.xml.internal.xsom.XSAnnotation;
47  import com.sun.xml.internal.xsom.XSComponent;
48  import com.sun.xml.internal.xsom.XSSchemaSet;
49  
50  import org.w3c.dom.Element;
51  import org.xml.sax.SAXException;
52  import org.xml.sax.SAXParseException;
53  
54  /**
55   * Set of binding nodes that have target nodes specified via SCD.
56   *
57   * This is parsed during {@link Internalizer} works on the tree,
58   * but applying this has to wait for {@link XSSchemaSet} to be parsed.
59   *
60   * @author Kohsuke Kawaguchi
61   * @see SCD
62   */
63  public final class SCDBasedBindingSet {
64  
65      /**
66       * Represents the target schema component of the
67       * customization identified by SCD.
68       *
69       * @author Kohsuke Kawaguchi
70       */
71      final class Target {
72          /**
73           * SCDs can be specified via multiple steps, like:
74           *
75           * <xmp>
76           * <bindings scd="foo/bar">
77           *   <bindings scd="zot/xyz">
78           * </xmp>
79           *
80           * This field and {@link #nextSibling} form a single-linked list that
81           * represent the children that shall be evaluated within this target.
82           * Think of it as {@code List<Target>}.
83           */
84          private Target firstChild;
85          private final Target nextSibling;
86  
87          /**
88           * Compiled SCD.
89           */
90          private final @NotNull SCD scd;
91  
92          /**
93           * The element on which SCD was found.
94           */
95          private final @NotNull Element src;
96  
97          /**
98           * Bindings that apply to this SCD.
99           */
100         private final List<Element> bindings = new ArrayList<Element>();
101 
102         private Target(Target parent, Element src, SCD scd) {
103             if(parent==null) {
104                 this.nextSibling = topLevel;
105                 topLevel = this;
106             } else {
107                 this.nextSibling = parent.firstChild;
108                 parent.firstChild = this;
109             }
110             this.src = src;
111             this.scd = scd;
112         }
113 
114         /**
115          * Adds a new binding declaration to be associated to the schema component
116          * identified by {@link #scd}.
117          */
118         void addBinidng(Element binding) {
119             bindings.add(binding);
120         }
121 
122         /**
123          * Applies bindings to the schema component for this and its siblings.
124          */
125         private void applyAll(Collection<? extends XSComponent> contextNode) {
126             for( Target self=this; self!=null; self=self.nextSibling )
127                 self.apply(contextNode);
128         }
129 
130         /**
131          * Applies bindings to the schema component for just this node.
132          */
133         private void apply(Collection<? extends XSComponent> contextNode) {
134             // apply the SCD...
135             Collection<XSComponent> childNodes = scd.select(contextNode);
136             if(childNodes.isEmpty()) {
137                 // no node matched
138                 if(src.getAttributeNode("if-exists")!=null) {
139                     // if this attribute exists, it's not an error if SCD didn't match.
140                     return;
141                 }
142 
143                 reportError( src, Messages.format(Messages.ERR_SCD_EVALUATED_EMPTY,scd) );
144                 return;
145             }
146 
147             if(firstChild!=null)
148                     firstChild.applyAll(childNodes);
149 
150             if(!bindings.isEmpty()) {
151                 // error to match more than one components
152                 Iterator<XSComponent> itr = childNodes.iterator();
153                 XSComponent target = itr.next();
154                 if(itr.hasNext()) {
155                     reportError( src, Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES,scd,childNodes.size()) );
156                     errorReceiver.error( target.getLocator(), Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES_FIRST) );
157                     errorReceiver.error( itr.next().getLocator(), Messages.format(Messages.ERR_SCD_MATCHED_MULTIPLE_NODES_SECOND) );
158                 }
159 
160                 // apply bindings to the target
161                 for (Element binding : bindings) {
162                     for (Element item : DOMUtils.getChildElements(binding)) {
163                         String localName = item.getLocalName();
164 
165                         if ("bindings".equals(localName))
166                             continue;   // this should be already in Target.bindings of some SpecVersion.
167 
168                         try {
169                             new DOMForestScanner(forest).scan(item,loader);
170                             BIDeclaration decl = (BIDeclaration)unmarshaller.getResult();
171 
172                             // add this binding to the target
173                             XSAnnotation ann = target.getAnnotation(true);
174                             BindInfo bi = (BindInfo)ann.getAnnotation();
175                             if(bi==null) {
176                                 bi = new BindInfo();
177                                 ann.setAnnotation(bi);
178                             }
179                             bi.addDecl(decl);
180                         } catch (SAXException e) {
181                             // the error should have already been reported.
182                         } catch (JAXBException e) {
183                             // if validation didn't fail, then unmarshalling can't go wrong
184                             throw new AssertionError(e);
185                         }
186                     }
187                 }
188             }
189         }
190     }
191 
192     private Target topLevel;
193 
194     /**
195      * The forest where binding elements came from. Needed to report line numbers for errors.
196      */
197     private final DOMForest forest;
198 
199 
200     // variables used only during the apply method
201     //
202     private ErrorReceiver errorReceiver;
203     private UnmarshallerHandler unmarshaller;
204     private ForkContentHandler loader; // unmarshaller+validator
205 
206     SCDBasedBindingSet(DOMForest forest) {
207         this.forest = forest;
208     }
209 
210     Target createNewTarget(Target parent, Element src, SCD scd) {
211         return new Target(parent,src,scd);
212     }
213 
214     /**
215      * Applies the additional binding customizations.
216      */
217     public void apply(XSSchemaSet schema, ErrorReceiver errorReceiver) {
218         if(topLevel!=null) {
219             this.errorReceiver = errorReceiver;
220             Unmarshaller u =  BindInfo.getCustomizationUnmarshaller();
221             this.unmarshaller = u.getUnmarshallerHandler();
222             ValidatorHandler v = BindInfo.bindingFileSchema.newValidator();
223             v.setErrorHandler(errorReceiver);
224             loader = new ForkContentHandler(v,unmarshaller);
225 
226             topLevel.applyAll(schema.getSchemas());
227 
228             this.loader = null;
229             this.unmarshaller = null;
230             this.errorReceiver = null;
231         }
232     }
233 
234     private void reportError( Element errorSource, String formattedMsg ) {
235         reportError( errorSource, formattedMsg, null );
236     }
237 
238     private void reportError( Element errorSource,
239                               String formattedMsg, Exception nestedException ) {
240 
241         SAXParseException e = new SAXParseException2( formattedMsg,
242             forest.locatorTable.getStartLocation(errorSource),
243             nestedException );
244         errorReceiver.error(e);
245     }
246 }