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.jxc;
  27 
  28 import com.sun.tools.internal.jxc.ap.Options;
  29 import java.io.File;
  30 import java.io.IOException;
  31 import java.util.Collection;
  32 import java.util.HashMap;
  33 import java.util.HashSet;
  34 import java.util.List;
  35 import java.util.Map;
  36 import java.util.Set;
  37 import java.util.regex.Matcher;
  38 import java.util.regex.Pattern;
  39 
  40 import javax.xml.bind.SchemaOutputResolver;
  41 import javax.xml.parsers.ParserConfigurationException;
  42 import javax.xml.parsers.SAXParserFactory;
  43 import javax.xml.transform.Result;
  44 import javax.xml.transform.stream.StreamResult;
  45 import javax.xml.validation.ValidatorHandler;
  46 
  47 import javax.annotation.processing.ProcessingEnvironment;
  48 import javax.lang.model.element.TypeElement;
  49 import com.sun.tools.internal.jxc.gen.config.Config;
  50 import com.sun.tools.internal.jxc.gen.config.Schema;
  51 import com.sun.tools.internal.xjc.SchemaCache;
  52 import com.sun.tools.internal.xjc.api.Reference;
  53 import com.sun.tools.internal.xjc.util.ForkContentHandler;
  54 
  55 import com.sun.xml.internal.bind.v2.util.XmlFactory;
  56 import org.xml.sax.ErrorHandler;
  57 import org.xml.sax.InputSource;
  58 import org.xml.sax.SAXException;
  59 import org.xml.sax.XMLReader;
  60 
  61 
  62 /**
  63  * This reads the config files passed by the user to annotation processing
  64  * and obtains a list of classes that need to be included
  65  * for a particular config from the set of classes passed
  66  * by the user to annotation processing.
  67  *
  68  * @author Bhakti Mehta (bhakti.mehta@sun.com)
  69  */
  70 public final class ConfigReader  {
  71 
  72     /**
  73      * The set of classes to be passed to XJC
  74      *
  75      */
  76     private final Set<Reference> classesToBeIncluded = new HashSet<Reference>();
  77 
  78 
  79     /**
  80      *  The SchemaOutputResolver used to generate the schemas
  81      */
  82     private final SchemaOutputResolver schemaOutputResolver;
  83 
  84     private final ProcessingEnvironment env;
  85 
  86     /**
  87      *
  88      * @param classes
  89      *      The set of classes passed to the AnnotationProcessor
  90      * @param xmlFile
  91      *      The configuration file.
  92      * @throws SAXException
  93      *      If this is thrown, the error has already been reported.
  94      * @throws IOException
  95      *     If any IO errors occur.
  96      */
  97     public ConfigReader(ProcessingEnvironment env, Collection<? extends TypeElement> classes, File xmlFile, ErrorHandler errorHandler) throws SAXException, IOException {
  98         this.env = env;
  99         Config config = parseAndGetConfig(xmlFile, errorHandler, env.getOptions().containsKey(Options.DISABLE_XML_SECURITY));
 100         checkAllClasses(config,classes);
 101         String path =   xmlFile.getAbsolutePath();
 102         String xmlPath = path.substring(0,path.lastIndexOf(File.separatorChar));
 103         schemaOutputResolver = createSchemaOutputResolver(config,xmlPath);
 104 
 105     }
 106 
 107 
 108     /**
 109      * This creates a regular expression
 110      * for the user pattern , matches the input classes
 111      * passed by the user and returns the final
 112      * list of classes that need to be included for a config file
 113      * after applying those patterns
 114      *
 115      */
 116     public Collection<Reference> getClassesToBeIncluded() {
 117         return classesToBeIncluded;
 118     }
 119 
 120     private void checkAllClasses(Config config, Collection<? extends TypeElement> rootClasses) {
 121 
 122         List<Pattern> includeRegexList = config.getClasses().getIncludes();
 123         List<Pattern>  excludeRegexList = config.getClasses().getExcludes();
 124 
 125         OUTER:
 126         for (TypeElement typeDecl : rootClasses) {
 127 
 128             String qualifiedName = typeDecl.getQualifiedName().toString();
 129 
 130             for (Pattern pattern : excludeRegexList) {
 131                 boolean match = checkPatternMatch(qualifiedName, pattern);
 132                 if (match)
 133                     continue OUTER; // excluded
 134             }
 135 
 136             for (Pattern pattern : includeRegexList) {
 137                 boolean match = checkPatternMatch(qualifiedName, pattern);
 138                 if (match) {
 139                     classesToBeIncluded.add(new Reference(typeDecl,env));
 140                     break;
 141                 }
 142             }
 143         }
 144     }
 145 
 146     /**
 147      * This returns the SchemaOutputResolver to generate the schemas
 148      */
 149     public SchemaOutputResolver getSchemaOutputResolver(){
 150         return schemaOutputResolver;
 151     }
 152 
 153     private SchemaOutputResolver createSchemaOutputResolver(Config config, String xmlpath) {
 154         File baseDir = new File(xmlpath, config.getBaseDir().getPath());
 155         SchemaOutputResolverImpl outResolver = new SchemaOutputResolverImpl (baseDir);
 156 
 157         for( Schema schema : (List<Schema>)config.getSchema() ) {
 158             String namespace = schema.getNamespace();
 159             File location = schema.getLocation();
 160             outResolver.addSchemaInfo(namespace,location);
 161         }
 162         return outResolver;
 163     }
 164 
 165     /**
 166      * This will  check if the qualified name matches the pattern
 167      *
 168      * @param qualifiedName
 169      *      The qualified name of the TypeDeclaration
 170      * @param pattern
 171      *       The  pattern obtained from the users input
 172      *
 173      */
 174     private boolean checkPatternMatch(String qualifiedName, Pattern pattern) {
 175         Matcher matcher = pattern.matcher(qualifiedName);
 176         return matcher.matches();
 177     }
 178 
 179 
 180 
 181     /**
 182      * Lazily parsed schema for the binding file.
 183      */
 184     private static SchemaCache configSchema = new SchemaCache("config.xsd", Config.class);
 185 
 186 
 187     /**
 188      * Parses an xml config file and returns a Config object.
 189      *
 190      * @param xmlFile
 191      *        The xml config file which is passed by the user to annotation processing
 192      * @return
 193      *        A non null Config object
 194      */
 195     private Config parseAndGetConfig (File xmlFile, ErrorHandler errorHandler, boolean disableSecureProcessing) throws SAXException, IOException {
 196         XMLReader reader;
 197         try {
 198             SAXParserFactory factory = XmlFactory.createParserFactory(disableSecureProcessing);
 199             reader = factory.newSAXParser().getXMLReader();
 200         } catch (ParserConfigurationException e) {
 201             // in practice this will never happen
 202             throw new Error(e);
 203         }
 204         NGCCRuntimeEx runtime = new NGCCRuntimeEx(errorHandler);
 205 
 206         // set up validator
 207         ValidatorHandler validator = configSchema.newValidator();
 208         validator.setErrorHandler(errorHandler);
 209 
 210         // the validator will receive events first, then the parser.
 211         reader.setContentHandler(new ForkContentHandler(validator,runtime));
 212 
 213         reader.setErrorHandler(errorHandler);
 214         Config config = new Config(runtime);
 215         runtime.setRootHandler(config);
 216         reader.parse(new InputSource(xmlFile.toURL().toExternalForm()));
 217         runtime.reset();
 218 
 219         return config;
 220     }
 221     /**
 222      * Controls where the JAXB RI puts the generates
 223      * schema files.
 224      * @author
 225      *     Bhakti Mehta (bhakti.mehta@sun.com)
 226      */
 227     private static final class SchemaOutputResolverImpl extends SchemaOutputResolver{
 228 
 229         /**
 230          * Directory to which we put the rest of the files.
 231          * Never be null.
 232          */
 233         private final File baseDir;
 234 
 235         /**
 236          * Namespace URI to the location of the schema.
 237          * This captures what the user specifies.
 238          */
 239         private final Map<String,File> schemas = new HashMap<String,File>();
 240 
 241 
 242         /**
 243          * Decides where the schema file (of the given namespace URI)
 244          * will be written, and return it as a {@link Result} object.
 245          *
 246          */
 247         public Result createOutput( String namespaceUri, String suggestedFileName ) {
 248 
 249             // the user's preference takes a precedence
 250             if(schemas.containsKey(namespaceUri)) {
 251                 File loc = schemas.get(namespaceUri);
 252                 if(loc==null)   return null;    // specifically not to generate a schema
 253 
 254                 // create directories if necessary. we've already checked that the baseDir
 255                 // exists, so this should be no surprise to users.
 256                 loc.getParentFile().mkdirs();
 257 
 258                 return new StreamResult(loc);   // generate into a file the user specified.
 259             }
 260 
 261             // if the user didn't say anything about this namespace,
 262             // generate it into the default directory with a default name.
 263 
 264              File schemaFile = new File (baseDir, suggestedFileName);
 265              // The systemId for the result will be schemaFile
 266              return new StreamResult(schemaFile);
 267         }
 268 
 269 
 270         public SchemaOutputResolverImpl(File baseDir) {
 271             assert baseDir!=null;
 272             this.baseDir = baseDir;
 273         }
 274 
 275         public void addSchemaInfo(String namespaceUri, File location) {
 276             if (namespaceUri == null )
 277                 //generate elements in no namespace
 278                 namespaceUri = "";
 279             schemas.put(namespaceUri, location);
 280 
 281         }
 282 
 283     }
 284 }