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