1 /*
   2  * Copyright (c) 1997, 2015, 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.xmlschema.bindinfo;
  27 
  28 import java.io.FilterWriter;
  29 import java.io.IOException;
  30 import java.io.StringWriter;
  31 import java.io.Writer;
  32 import java.util.ArrayList;
  33 import java.util.Iterator;
  34 import java.util.List;
  35 
  36 import javax.xml.bind.JAXBContext;
  37 import javax.xml.bind.JAXBException;
  38 import javax.xml.bind.Unmarshaller;
  39 import javax.xml.bind.annotation.XmlAnyElement;
  40 import javax.xml.bind.annotation.XmlElement;
  41 import javax.xml.bind.annotation.XmlMixed;
  42 import javax.xml.bind.annotation.XmlRootElement;
  43 import javax.xml.bind.annotation.XmlType;
  44 import javax.xml.transform.Transformer;
  45 import javax.xml.transform.TransformerException;
  46 import javax.xml.transform.dom.DOMSource;
  47 import javax.xml.transform.stream.StreamResult;
  48 
  49 import com.sun.codemodel.internal.JDocComment;
  50 import com.sun.xml.internal.bind.v2.WellKnownNamespace;
  51 import com.sun.tools.internal.xjc.SchemaCache;
  52 import com.sun.tools.internal.xjc.model.CCustomizations;
  53 import com.sun.tools.internal.xjc.model.CPluginCustomization;
  54 import com.sun.tools.internal.xjc.model.Model;
  55 import com.sun.tools.internal.xjc.reader.Ring;
  56 import com.sun.tools.internal.xjc.reader.xmlschema.BGMBuilder;
  57 import com.sun.xml.internal.bind.annotation.XmlLocation;
  58 import com.sun.xml.internal.bind.marshaller.MinimumEscapeHandler;
  59 import com.sun.xml.internal.xsom.XSComponent;
  60 
  61 import org.w3c.dom.Element;
  62 import org.xml.sax.Locator;
  63 
  64 /**
  65  * Container for customization declarations.
  66  *
  67  * We use JAXB ourselves and parse this object from "xs:annotation".
  68  *
  69  * @author
  70  *     Kohsuke Kawaguchi (kohsuke,kawaguchi@sun.com)
  71  */
  72 @XmlRootElement(namespace= WellKnownNamespace.XML_SCHEMA,name="annotation")
  73 @XmlType(namespace=WellKnownNamespace.XML_SCHEMA,name="foobar")
  74 public final class BindInfo implements Iterable<BIDeclaration> {
  75 
  76     private BGMBuilder builder;
  77 
  78     @XmlLocation
  79     private Locator location;
  80 
  81     /**
  82      * Documentation taken from {@code <xs:documentation>s}.
  83      */
  84     @XmlElement(namespace=WellKnownNamespace.XML_SCHEMA)
  85     private Documentation documentation;
  86 
  87     /**
  88      * Returns true if this {@link BindInfo} doesn't contain any useful
  89      * information.
  90      *
  91      * This flag is used to discard unused {@link BindInfo}s early to save memory footprint.
  92      */
  93     public boolean isPointless() {
  94         if(size()>0)     return false;
  95         if(documentation!=null && !documentation.contents.isEmpty())
  96             return false;
  97 
  98         return true;
  99     }
 100 
 101     private static final class Documentation {
 102         @XmlAnyElement
 103         @XmlMixed
 104         List<Object> contents = new ArrayList<Object>();
 105 
 106         void addAll(Documentation rhs) {
 107             if(rhs==null)   return;
 108 
 109             if(contents==null)
 110                 contents = new ArrayList<Object>();
 111             if(!contents.isEmpty())
 112                 contents.add("\n\n");
 113             contents.addAll(rhs.contents);
 114         }
 115     }
 116 
 117     /** list of individual declarations. */
 118     private final List<BIDeclaration> decls = new ArrayList<BIDeclaration>();
 119 
 120     private static final class AppInfo {
 121         /**
 122          * Receives {@link BIDeclaration}s and other DOMs.
 123          */
 124         @XmlAnyElement(lax=true,value=DomHandlerEx.class)
 125         List<Object> contents = new ArrayList<Object>();
 126 
 127         public void addTo(BindInfo bi) {
 128             if(contents==null)  return;
 129 
 130             for (Object o : contents) {
 131                 if(o instanceof BIDeclaration)
 132                     bi.addDecl((BIDeclaration)o);
 133                 // this is really PITA! I can't get the source location
 134                 if(o instanceof DomHandlerEx.DomAndLocation) {
 135                     DomHandlerEx.DomAndLocation e = (DomHandlerEx.DomAndLocation)o;
 136                     String nsUri = e.element.getNamespaceURI();
 137                     if(nsUri==null || nsUri.equals("")
 138                     || nsUri.equals(WellKnownNamespace.XML_SCHEMA))
 139                         continue;   // this is definitely not a customization
 140                     bi.addDecl(new BIXPluginCustomization(e.element,e.loc));
 141                 }
 142             }
 143         }
 144     }
 145 
 146 
 147     // only used by JAXB
 148     @XmlElement(namespace=WellKnownNamespace.XML_SCHEMA)
 149     void setAppinfo(AppInfo aib) {
 150         aib.addTo(this);
 151     }
 152 
 153 
 154 
 155     /**
 156      * Gets the location of this annotation in the source file.
 157      *
 158      * @return
 159      *      If the declarations are in fact specified in the source
 160      *      code, a non-null valid object will be returned.
 161      *      If this BindInfo is generated internally by XJC, then
 162      *      null will be returned.
 163      */
 164     public Locator getSourceLocation() { return location; }
 165 
 166 
 167     private XSComponent owner;
 168     /**
 169      * Sets the owner schema component and a reference to BGMBuilder.
 170      * This method is called from the BGMBuilder before
 171      * any BIDeclaration inside it is used.
 172      */
 173     public void setOwner( BGMBuilder _builder, XSComponent _owner ) {
 174         this.owner = _owner;
 175         this.builder = _builder;
 176         for (BIDeclaration d : decls)
 177             d.onSetOwner();
 178     }
 179     public XSComponent getOwner() { return owner; }
 180 
 181     /**
 182      * Back pointer to the BGMBuilder which is building
 183      * a BGM from schema components including this customization.
 184      */
 185     public BGMBuilder getBuilder() { return builder; }
 186 
 187     /** Adds a new declaration. */
 188     public void addDecl( BIDeclaration decl ) {
 189         if(decl==null)  throw new IllegalArgumentException();
 190         decl.setParent(this);
 191         decls.add(decl);
 192     }
 193 
 194     /**
 195      * Gets the first declaration with a given name, or null
 196      * if none is found.
 197      */
 198     public <T extends BIDeclaration>
 199     T get( Class<T> kind ) {
 200         for( BIDeclaration decl : decls ) {
 201             if( kind.isInstance(decl) )
 202                 return kind.cast(decl);
 203         }
 204         return null; // not found
 205     }
 206 
 207     /**
 208      * Gets all the declarations
 209      */
 210     public BIDeclaration[] getDecls() {
 211         return decls.toArray(new BIDeclaration[decls.size()]);
 212     }
 213 
 214     /**
 215      * Gets the documentation parsed from {@code <xs:documentation>}s.
 216      * The returned collection is to be added to {@link JDocComment#append(Object)}.
 217      * @return  maybe null.
 218      */
 219     public String getDocumentation() {
 220         // TODO: FIXME: correctly turn individual items to String including DOM
 221         if(documentation==null || documentation.contents==null) return null;
 222 
 223         StringBuilder buf = new StringBuilder();
 224         for (Object c : documentation.contents) {
 225             if(c instanceof String) {
 226                 buf.append(c.toString());
 227             }
 228             if(c instanceof Element) {
 229                 Transformer t = builder.getIdentityTransformer();
 230                 StringWriter w = new StringWriter();
 231                 try {
 232                     Writer fw = new FilterWriter(w) {
 233                         char[] buf = new char[1];
 234 
 235                         public void write(int c) throws IOException {
 236                             buf[0] = (char)c;
 237                             write(buf,0,1);
 238                         }
 239 
 240                         public void write(char[] cbuf, int off, int len) throws IOException {
 241                             MinimumEscapeHandler.theInstance.escape(cbuf,off,len,false,out);
 242                         }
 243 
 244                         public void write(String str, int off, int len) throws IOException {
 245                             write(str.toCharArray(),off,len);
 246                         }
 247                     };
 248                     t.transform(new DOMSource((Element)c),new StreamResult(fw));
 249                 } catch (TransformerException e) {
 250                     throw new Error(e); // impossible
 251                 }
 252                 buf.append("\n<pre>\n");
 253                 buf.append(w);
 254                 buf.append("\n</pre>\n");
 255             }
 256         }
 257         return buf.toString();
 258     }
 259 
 260     /**
 261      * Merges all the declarations inside the given BindInfo
 262      * to this BindInfo.
 263      */
 264     public void absorb( BindInfo bi ) {
 265         for( BIDeclaration d : bi )
 266             d.setParent(this);
 267         this.decls.addAll( bi.decls );
 268 
 269         if(this.documentation==null)
 270             this.documentation = bi.documentation;
 271         else
 272             this.documentation.addAll(bi.documentation);
 273     }
 274 
 275     /** Gets the number of declarations. */
 276     public int size() { return decls.size(); }
 277 
 278     public BIDeclaration get( int idx ) { return decls.get(idx); }
 279 
 280     public Iterator<BIDeclaration> iterator() {
 281         return decls.iterator();
 282     }
 283 
 284     /**
 285      * Gets the list of {@link CPluginCustomization}s from this.
 286      *
 287      * <p>
 288      * Note that calling this method marks all those plug-in customizations
 289      * as 'used'. So call it only when it's really necessary.
 290      */
 291     public CCustomizations toCustomizationList() {
 292         CCustomizations r=null;
 293         for( BIDeclaration d : this ) {
 294             if(d instanceof BIXPluginCustomization) {
 295                 BIXPluginCustomization pc = (BIXPluginCustomization) d;
 296                 pc.markAsAcknowledged();
 297                 if(!Ring.get(Model.class).options.pluginURIs.contains(pc.getName().getNamespaceURI()))
 298                     continue;   // this isn't a plugin customization
 299                 if(r==null)
 300                     r = new CCustomizations();
 301                 r.add(new CPluginCustomization(pc.element,pc.getLocation()));
 302             }
 303         }
 304 
 305         if(r==null)     r = CCustomizations.EMPTY;
 306         return new CCustomizations(r);
 307     }
 308     /** An instance with the empty contents. */
 309     public final static BindInfo empty = new BindInfo();
 310 
 311     /**
 312      * Lazily prepared {@link JAXBContext}.
 313      */
 314     private static volatile JAXBContext customizationContext;
 315 
 316     public static JAXBContext getCustomizationContext() {
 317         try {
 318             if (customizationContext == null) {
 319                 synchronized (BindInfo.class) {
 320                     if (customizationContext == null) {
 321                         customizationContext = JAXBContext.newInstance(
 322                                 BindInfo.class, // for xs:annotation
 323                                 BIClass.class,
 324                                 BIConversion.User.class,
 325                                 BIConversion.UserAdapter.class,
 326                                 BIDom.class,
 327                                 BIFactoryMethod.class,
 328                                 BIInlineBinaryData.class,
 329                                 BIXDom.class,
 330                                 BIXSubstitutable.class,
 331                                 BIEnum.class,
 332                                 BIEnumMember.class,
 333                                 BIGlobalBinding.class,
 334                                 BIProperty.class,
 335                                 BISchemaBinding.class);
 336                     }
 337                 }
 338             }
 339             return customizationContext;
 340         } catch (JAXBException e) {
 341             throw new AssertionError(e);
 342         }
 343     }
 344 
 345     public static Unmarshaller getCustomizationUnmarshaller() {
 346         try {
 347             return getCustomizationContext().createUnmarshaller();
 348         } catch (JAXBException e) {
 349             throw new AssertionError(e);
 350         }
 351     }
 352 
 353     /**
 354      * Lazily parsed schema for the binding file.
 355      */
 356     public static final SchemaCache bindingFileSchema = new SchemaCache("binding.xsd", BindInfo.class, true);
 357 }