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