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