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.annotations.Nullable;
023    import org.jetbrains.kotlin.cli.common.messages.MessageCollector;
024    import org.jetbrains.kotlin.cli.common.messages.MessageCollectorUtil;
025    import org.jetbrains.kotlin.cli.common.messages.OutputMessageUtil;
026    import org.jetbrains.kotlin.modules.JavaRootPath;
027    import org.jetbrains.kotlin.modules.Module;
028    import org.xml.sax.Attributes;
029    import org.xml.sax.SAXException;
030    import org.xml.sax.helpers.DefaultHandler;
031    
032    import javax.xml.parsers.ParserConfigurationException;
033    import javax.xml.parsers.SAXParser;
034    import javax.xml.parsers.SAXParserFactory;
035    import java.io.*;
036    import java.util.List;
037    
038    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation.NO_LOCATION;
039    import static org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity.ERROR;
040    
041    public class ModuleXmlParser {
042    
043        public static final String MODULES = "modules";
044        public static final String MODULE = "module";
045        public static final String NAME = "name";
046        public static final String TYPE = "type";
047        public static final String TYPE_PRODUCTION = "java-production";
048        public static final String TYPE_TEST = "java-test";
049        public static final String OUTPUT_DIR = "outputDir";
050        public static final String SOURCES = "sources";
051        public static final String JAVA_SOURCE_ROOTS = "javaSourceRoots";
052        public static final String JAVA_SOURCE_PACKAGE_PREFIX = "packagePrefix";
053        public static final String PATH = "path";
054        public static final String CLASSPATH = "classpath";
055    
056        @NotNull
057        public static ModuleScriptData parseModuleScript(
058                @NotNull String xmlFile,
059                @NotNull MessageCollector messageCollector
060        ) {
061            FileInputStream stream = null;
062            try {
063                //noinspection IOResourceOpenedButNotSafelyClosed
064                stream = new FileInputStream(xmlFile);
065                return new ModuleXmlParser(messageCollector).parse(new BufferedInputStream(stream));
066            }
067            catch (FileNotFoundException e) {
068                MessageCollectorUtil.reportException(messageCollector, e);
069                return ModuleScriptData.EMPTY;
070            }
071            finally {
072                StreamUtil.closeStream(stream);
073            }
074        }
075    
076        private final MessageCollector messageCollector;
077        private final List<Module> modules = new SmartList<Module>();
078        private DefaultHandler currentState;
079    
080        private ModuleXmlParser(@NotNull MessageCollector messageCollector) {
081            this.messageCollector = messageCollector;
082        }
083    
084        private void setCurrentState(@NotNull DefaultHandler currentState) {
085            this.currentState = currentState;
086        }
087    
088        private ModuleScriptData parse(@NotNull InputStream xml) {
089            try {
090                setCurrentState(initial);
091                SAXParser saxParser = SAXParserFactory.newInstance().newSAXParser();
092                saxParser.parse(xml, new DelegatedSaxHandler() {
093                    @NotNull
094                    @Override
095                    protected DefaultHandler getDelegate() {
096                        return currentState;
097                    }
098                });
099                return new ModuleScriptData(modules);
100            }
101            catch (ParserConfigurationException e) {
102                MessageCollectorUtil.reportException(messageCollector, e);
103            }
104            catch (SAXException e) {
105                messageCollector.report(ERROR, OutputMessageUtil.renderException(e), NO_LOCATION);
106            }
107            catch (IOException e) {
108                MessageCollectorUtil.reportException(messageCollector, e);
109            }
110            return ModuleScriptData.EMPTY;
111        }
112    
113        private final DefaultHandler initial = new DefaultHandler() {
114            @Override
115            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
116                    throws SAXException {
117                if (!MODULES.equalsIgnoreCase(qName)) {
118                    throw createError(qName);
119                }
120    
121                setCurrentState(insideModules);
122            }
123        };
124    
125        private final DefaultHandler insideModules = new DefaultHandler() {
126            @Override
127            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
128                    throws SAXException {
129                if (!MODULE.equalsIgnoreCase(qName)) {
130                    throw createError(qName);
131                }
132    
133                String moduleType = getAttribute(attributes, TYPE, qName);
134                assert(TYPE_PRODUCTION.equals(moduleType) || TYPE_TEST.equals(moduleType)): "Unknown module type: " + moduleType;
135                setCurrentState(new InsideModule(
136                        getAttribute(attributes, NAME, qName),
137                        getAttribute(attributes, OUTPUT_DIR, qName),
138                        moduleType
139                ));
140            }
141    
142            @Override
143            public void endElement(String uri, @NotNull String localName, @NotNull String qName) throws SAXException {
144                if (MODULE.equalsIgnoreCase(qName) || MODULES.equalsIgnoreCase(qName)) {
145                    setCurrentState(insideModules);
146                }
147            }
148        };
149    
150        private class InsideModule extends DefaultHandler {
151    
152            private final ModuleBuilder moduleBuilder;
153            private InsideModule(String name, String outputDir, @NotNull String type) {
154                this.moduleBuilder = new ModuleBuilder(name, outputDir, type);
155                modules.add(moduleBuilder);
156            }
157    
158            @Override
159            public void startElement(@NotNull String uri, @NotNull String localName, @NotNull String qName, @NotNull Attributes attributes)
160                    throws SAXException {
161                if (SOURCES.equalsIgnoreCase(qName)) {
162                    String path = getAttribute(attributes, PATH, qName);
163                    moduleBuilder.addSourceFiles(path);
164                }
165                else if (CLASSPATH.equalsIgnoreCase(qName)) {
166                    String path = getAttribute(attributes, PATH, qName);
167                    moduleBuilder.addClasspathEntry(path);
168                }
169                else if (JAVA_SOURCE_ROOTS.equalsIgnoreCase(qName)) {
170                    String path = getAttribute(attributes, PATH, qName);
171                    String packagePrefix = getNullableAttribute(attributes, JAVA_SOURCE_PACKAGE_PREFIX);
172                    moduleBuilder.addJavaSourceRoot(new JavaRootPath(path, packagePrefix));
173                }
174                else {
175                    throw createError(qName);
176                }
177            }
178    
179            @Override
180            public void endElement(String uri, @NotNull String localName, @NotNull String qName) throws SAXException {
181                if (MODULE.equalsIgnoreCase(qName)) {
182                    setCurrentState(insideModules);
183                }
184            }
185        }
186    
187        @NotNull
188        private static String getAttribute(Attributes attributes, String qName, String tag) throws SAXException {
189            String name = attributes.getValue(qName);
190            if (name == null) {
191                throw new SAXException("No '" + qName + "' attribute for " + tag);
192            }
193            return name;
194        }
195    
196        @Nullable
197        private static String getNullableAttribute(Attributes attributes, String qName) throws SAXException {
198            return attributes.getValue(qName);
199        }
200    
201    
202        private static SAXException createError(String qName) throws SAXException {
203            return new SAXException("Unexpected tag: " + qName);
204        }
205    }