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.js.config;
018
019 import com.intellij.openapi.project.Project;
020 import com.intellij.openapi.util.Key;
021 import com.intellij.openapi.util.text.StringUtil;
022 import com.intellij.openapi.vfs.*;
023 import com.intellij.psi.PsiFile;
024 import com.intellij.psi.PsiManager;
025 import com.intellij.util.PathUtil;
026 import com.intellij.util.io.URLUtil;
027 import kotlin.Unit;
028 import kotlin.jvm.functions.Function2;
029 import org.jetbrains.annotations.NotNull;
030 import org.jetbrains.annotations.Nullable;
031 import org.jetbrains.kotlin.config.CompilerConfiguration;
032 import org.jetbrains.kotlin.idea.KotlinFileType;
033 import org.jetbrains.kotlin.psi.KtFile;
034 import org.jetbrains.kotlin.utils.JsMetadataVersion;
035 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata;
036 import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils;
037 import org.jetbrains.kotlin.utils.LibraryUtils;
038
039 import java.io.File;
040 import java.util.Collections;
041 import java.util.HashSet;
042 import java.util.List;
043 import java.util.Set;
044
045 import static org.jetbrains.kotlin.utils.LibraryUtils.isOldKotlinJavascriptLibrary;
046 import static org.jetbrains.kotlin.utils.PathUtil.getKotlinPathsForDistDirectory;
047
048 public class LibrarySourcesConfig extends JsConfig {
049 public static final List<String> JS_STDLIB =
050 Collections.singletonList(getKotlinPathsForDistDirectory().getJsStdLibJarPath().getAbsolutePath());
051
052 public static final List<String> JS_KOTLIN_TEST =
053 Collections.singletonList(getKotlinPathsForDistDirectory().getJsKotlinTestJarPath().getAbsolutePath());
054
055 public static final Key<String> EXTERNAL_MODULE_NAME = Key.create("externalModule");
056 public static final String UNKNOWN_EXTERNAL_MODULE_NAME = "<unknown>";
057
058 public LibrarySourcesConfig(@NotNull Project project, @NotNull CompilerConfiguration configuration) {
059 super(project, configuration);
060 }
061
062 @NotNull
063 public List<String> getLibraries() {
064 return getConfiguration().getList(JSConfigurationKeys.LIBRARIES);
065 }
066
067 @Override
068 protected void init(@NotNull final List<KtFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) {
069 if (getLibraries().isEmpty()) return;
070
071 final PsiManager psiManager = PsiManager.getInstance(getProject());
072
073 JsConfig.Reporter report = new JsConfig.Reporter() {
074 @Override
075 public void error(@NotNull String message) {
076 throw new IllegalStateException(message);
077 }
078 };
079
080 Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() {
081 @Override
082 public Unit invoke(String moduleName, VirtualFile file) {
083 if (moduleName != null) {
084 JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager);
085 VfsUtilCore.visitChildrenRecursively(file, jetFileCollector);
086 }
087 else {
088 String libraryPath = PathUtil.getLocalPath(file);
089 assert libraryPath != null : "libraryPath for " + file + " should not be null";
090 metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath));
091 }
092
093 return Unit.INSTANCE;
094 }
095 };
096
097 boolean hasErrors = checkLibFilesAndReportErrors(report, action);
098 assert !hasErrors : "hasErrors should be false";
099 }
100
101 @Override
102 public boolean checkLibFilesAndReportErrors(@NotNull JsConfig.Reporter report) {
103 return checkLibFilesAndReportErrors(report, null);
104 }
105
106 private boolean checkLibFilesAndReportErrors(@NotNull JsConfig.Reporter report, @Nullable Function2<String, VirtualFile, Unit> action) {
107 List<String> libraries = getLibraries();
108 if (libraries.isEmpty()) {
109 return false;
110 }
111
112 VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL);
113 VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL);
114
115 Set<String> modules = new HashSet<String>();
116
117 for (String path : libraries) {
118 VirtualFile file;
119
120 File filePath = new File(path);
121 if (!filePath.exists()) {
122 report.error("Path '" + path + "' does not exist");
123 return true;
124 }
125
126 if (path.endsWith(".jar") || path.endsWith(".zip")) {
127 file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR);
128 }
129 else {
130 file = fileSystem.findFileByPath(path);
131 }
132
133 if (file == null) {
134 report.error("File '" + path + "' does not exist or could not be read");
135 return true;
136 }
137
138 String moduleName;
139
140 if (isOldKotlinJavascriptLibrary(filePath)) {
141 moduleName = LibraryUtils.getKotlinJsModuleName(filePath);
142 if (!modules.add(moduleName)) {
143 report.warning("Module \"" + moduleName + "\" is defined in more, than one file");
144 }
145 }
146 else {
147 List<KotlinJavascriptMetadata> metadataList = KotlinJavascriptMetadataUtils.loadMetadata(filePath);
148 if (metadataList.isEmpty()) {
149 report.warning("'" + path + "' is not a valid Kotlin Javascript library");
150 continue;
151 }
152
153 for (KotlinJavascriptMetadata metadata : metadataList) {
154 if (!metadata.getVersion().isCompatible()) {
155 report.error("File '" + path + "' was compiled with an incompatible version of Kotlin. " +
156 "The binary version of its metadata is " + metadata.getVersion() +
157 ", expected version is " + JsMetadataVersion.INSTANCE);
158 return true;
159 }
160 if (!modules.add(metadata.getModuleName())) {
161 report.warning("Module \"" + metadata.getModuleName() + "\" is defined in more, than one file");
162 }
163 }
164
165 moduleName = null;
166 }
167
168 if (action != null) {
169 action.invoke(moduleName, file);
170 }
171 }
172
173 return false;
174 }
175
176 private static KtFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) {
177 PsiFile psiFile = psiManager.findFile(file);
178 assert psiFile != null;
179
180 setupPsiFile(psiFile, moduleName);
181 return (KtFile) psiFile;
182 }
183
184 private static void setupPsiFile(PsiFile psiFile, String moduleName) {
185 psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName);
186 }
187
188 private static class JetFileCollector extends VirtualFileVisitor {
189 private final List<KtFile> jetFiles;
190 private final String moduleName;
191 private final PsiManager psiManager;
192
193 private JetFileCollector(List<KtFile> files, String name, PsiManager manager) {
194 moduleName = name;
195 psiManager = manager;
196 jetFiles = files;
197 }
198
199 @Override
200 public boolean visitFile(@NotNull VirtualFile file) {
201 if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(KotlinFileType.EXTENSION)) {
202 jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager));
203 }
204 return true;
205 }
206 }
207 }