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