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 }