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
26package com.sun.tools.internal.jxc;
27
28import com.sun.tools.internal.jxc.ap.Options;
29import java.io.File;
30import java.io.IOException;
31import java.util.Collection;
32import java.util.HashMap;
33import java.util.HashSet;
34import java.util.List;
35import java.util.Map;
36import java.util.Set;
37import java.util.regex.Matcher;
38import java.util.regex.Pattern;
39
40import javax.xml.bind.SchemaOutputResolver;
41import javax.xml.parsers.ParserConfigurationException;
42import javax.xml.parsers.SAXParserFactory;
43import javax.xml.transform.Result;
44import javax.xml.transform.stream.StreamResult;
45import javax.xml.validation.ValidatorHandler;
46
47import javax.annotation.processing.ProcessingEnvironment;
48import javax.lang.model.element.TypeElement;
49import com.sun.tools.internal.jxc.gen.config.Config;
50import com.sun.tools.internal.jxc.gen.config.Schema;
51import com.sun.tools.internal.xjc.SchemaCache;
52import com.sun.tools.internal.xjc.api.Reference;
53import com.sun.tools.internal.xjc.util.ForkContentHandler;
54
55import com.sun.xml.internal.bind.v2.util.XmlFactory;
56import org.xml.sax.ErrorHandler;
57import org.xml.sax.InputSource;
58import org.xml.sax.SAXException;
59import 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 */
70public 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, true);
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}
285