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