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
017package org.jetbrains.jet.cli.common.modules;
018
019import com.intellij.openapi.util.io.StreamUtil;
020import com.intellij.util.SmartList;
021import org.jetbrains.annotations.NotNull;
022import org.jetbrains.jet.cli.common.messages.*;
023import org.xml.sax.Attributes;
024import org.xml.sax.SAXException;
025import org.xml.sax.helpers.DefaultHandler;
026
027import javax.xml.parsers.ParserConfigurationException;
028import javax.xml.parsers.SAXParser;
029import javax.xml.parsers.SAXParserFactory;
030import java.io.*;
031import java.util.Collections;
032import java.util.List;
033
034import static org.jetbrains.jet.cli.common.messages.CompilerMessageLocation.*;
035import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.*;
036
037public 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}