001    /*
002     * Copyright 2010-2015 JetBrains s.r.o.
003     *
004     * Licensed under the Apache License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.apache.org/licenses/LICENSE-2.0
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    
017    package org.jetbrains.kotlin.cli.common.modules;
018    
019    import com.intellij.openapi.util.io.StreamUtil;
020    import com.intellij.util.SmartList;
021    import org.jetbrains.annotations.NotNull;
022    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
023    import org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil;
024    import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil;
025    import org.jetbrains.kotlin.modules.Module;
026    import org.xml.sax.Attributes;
027    import org.xml.sax.SAXException;
028    import org.xml.sax.helpers.DefaultHandler;
029    
030    import javax.xml.parsers.ParserConfigurationException;
031    import javax.xml.parsers.SAXParser;
032    import javax.xml.parsers.SAXParserFactory;
033    import java.io.*;
034    import java.util.List;
035    
036    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
037    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR;
038    
039    public class ModuleXmlParser {
040    
041        public static final String MODULES = "modules";
042        public static final String MODULE = "module";
043        public static final String NAME = "name";
044        public static final String TYPE = "type";
045        public static final String TYPE_PRODUCTION = "java-production";
046        public static final String TYPE_TEST = "java-test";
047        public static final String OUTPUT_DIR = "outputDir";
048        public static final String SOURCES = "sources";
049        public static final String JAVA_SOURCE_ROOTS = "javaSourceRoots";
050        public static final String PATH = "path";
051        public static final String CLASSPATH = "classpath";
052        public static final String EXTERNAL_ANNOTATIONS = "externalAnnotations";
053    
054        @NotNull
055        public static ModuleScriptData parseModuleScript(
056                @NotNull String xmlFile,
057                @NotNull MessageCollector messageCollector
058        ) {
059            FileInputStream stream = null;
060            try {
061                stream = new FileInputStream(xmlFile);
062                //noinspection IOResourceOpenedButNotSafelyClosed
063                return new ModuleXmlParser(messageCollector).parse(new BufferedInputStream(stream));
064            }
065            catch (FileNotFoundException e) {
066                MessageCollectorUtil.reportException(messageCollector, e);
067                return ModuleScriptData.EMPTY;
068            }
069            finally {
070                StreamUtil.closeStream(stream);
071            }
072        }
073    
074        private final MessageCollector messageCollector;
075        private final List<Module> modules = new SmartList<Module>();
076        private DefaultHandler currentState;
077    
078        private ModuleXmlParser(@NotNull MessageCollector messageCollector) {
079            this.messageCollector = messageCollector;
080        }
081    
082        private void setCurrentState(@NotNull DefaultHandler currentState) {
083            this.currentState = currentState;
084        }
085    
086        private ModuleScriptData parse(@NotNull InputStream xml) {
087            try {
088                setCurrentState(initial);
089                SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
090                saxParser.parse(xml, new DelegatedSaxHandler() {
091                    @NotNull
092                    @Override
093                    protected DefaultHandler getDelegate() {
094                        return currentState;
095                    }
096                });
097                return new ModuleScriptData(modules);
098            }
099            catch (ParserConfigurationException e) {
100                MessageCollectorUtil.reportException(messageCollector, e);
101            }
102            catch (SAXException e) {
103                messageCollector.report(ERROR, OutputMessageUtil.renderException(e), NO_LOCATION);
104            }
105            catch (IOException e) {
106                MessageCollectorUtil.reportException(messageCollector, e);
107            }
108            return ModuleScriptData.EMPTY;
109        }
110    
111        private final DefaultHandler initial = new DefaultHandler() {
112            @Override
113            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
114                    throws SAXException {
115                if (!MODULES.equalsIgnoreCase(qName)) {
116                    throw createError(qName);
117                }
118    
119                setCurrentState(insideModules);
120            }
121        };
122    
123        private final DefaultHandler insideModules = new DefaultHandler() {
124            @Override
125            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
126                    throws SAXException {
127                if (!MODULE.equalsIgnoreCase(qName)) {
128                    throw createError(qName);
129                }
130    
131                String moduleType = getAttribute(attributes, TYPE, qName);
132                assert(TYPE_PRODUCTION.equals(moduleType) || TYPE_TEST.equals(moduleType)): "Unknown module type: " + moduleType;
133                setCurrentState(new InsideModule(
134                        getAttribute(attributes, NAME, qName),
135                        getAttribute(attributes, OUTPUT_DIR, qName),
136                        moduleType
137                ));
138            }
139    
140            @Override
141            public void endElement(String uri, @NotNull String localName, @NotNull String qName) throws SAXException {
142                if (MODULE.equalsIgnoreCase(qName) || MODULES.equalsIgnoreCase(qName)) {
143                    setCurrentState(insideModules);
144                }
145            }
146        };
147    
148        private class InsideModule extends DefaultHandler {
149    
150            private final ModuleBuilder moduleBuilder;
151            private InsideModule(String name, String outputDir, @NotNull String type) {
152                this.moduleBuilder = new ModuleBuilder(name, outputDir, type);
153                modules.add(moduleBuilder);
154            }
155    
156            @Override
157            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
158                    throws SAXException {
159                if (SOURCES.equalsIgnoreCase(qName)) {
160                    String path = getAttribute(attributes, PATH, qName);
161                    moduleBuilder.addSourceFiles(path);
162                }
163                else if (CLASSPATH.equalsIgnoreCase(qName)) {
164                    String path = getAttribute(attributes, PATH, qName);
165                    moduleBuilder.addClasspathEntry(path);
166                }
167                else if (EXTERNAL_ANNOTATIONS.equalsIgnoreCase(qName)) {
168                    String path = getAttribute(attributes, PATH, qName);
169                    moduleBuilder.addAnnotationsPathEntry(path);
170                }
171                else if (JAVA_SOURCE_ROOTS.equalsIgnoreCase(qName)) {
172                    String path = getAttribute(attributes, PATH, qName);
173                    moduleBuilder.addJavaSourceRoot(path);
174                }
175                else {
176                    throw createError(qName);
177                }
178            }
179    
180            @Override
181            public void endElement(String uri, @NotNull String localName, @NotNull String qName) throws SAXException {
182                if (MODULE.equalsIgnoreCase(qName)) {
183                    setCurrentState(insideModules);
184                }
185            }
186        }
187    
188        @NotNull
189        private static String getAttribute(Attributes attributes, String qName, String tag) throws SAXException {
190            String name = attributes.getValue(qName);
191            if (name == null) {
192                throw new SAXException("No '" + qName + "' attribute for " + tag);
193            }
194            return name;
195        }
196    
197        private static SAXException createError(String qName) throws SAXException {
198            return new SAXException("Unexpected tag: " + qName);
199        }
200    }