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