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