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.Function1; 029 import kotlin.jvm.functions.Function2; 030 import org.jetbrains.annotations.NotNull; 031 import org.jetbrains.annotations.Nullable; 032 import org.jetbrains.kotlin.idea.JetFileType; 033 import org.jetbrains.kotlin.js.JavaScript; 034 import org.jetbrains.kotlin.psi.JetFile; 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.List; 042 043 import static org.jetbrains.kotlin.utils.LibraryUtils.isKotlinJavascriptLibraryWithMetadata; 044 import static org.jetbrains.kotlin.utils.LibraryUtils.isOldKotlinJavascriptLibrary; 045 import static org.jetbrains.kotlin.utils.PathUtil.getKotlinPathsForDistDirectory; 046 047 public class LibrarySourcesConfig extends Config { 048 public static final List<String> JS_STDLIB = 049 Collections.singletonList(getKotlinPathsForDistDirectory().getJsStdLibJarPath().getAbsolutePath()); 050 051 @NotNull 052 public static final Key<String> EXTERNAL_MODULE_NAME = Key.create("externalModule"); 053 @NotNull 054 public static final String UNKNOWN_EXTERNAL_MODULE_NAME = "<unknown>"; 055 056 public static final String STDLIB_JS_MODULE_NAME = "stdlib"; 057 public static final String BUILTINS_JS_MODULE_NAME = "builtins"; 058 public static final String BUILTINS_JS_FILE_NAME = BUILTINS_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 059 public static final String STDLIB_JS_FILE_NAME = STDLIB_JS_MODULE_NAME + JavaScript.DOT_EXTENSION; 060 061 private final boolean isUnitTestConfig; 062 063 @NotNull 064 private final List<String> files; 065 066 private LibrarySourcesConfig( 067 @NotNull Project project, 068 @NotNull String moduleId, 069 @NotNull List<String> files, 070 @NotNull EcmaVersion ecmaVersion, 071 boolean sourceMap, 072 boolean inlineEnabled, 073 boolean isUnitTestConfig, 074 boolean metaInfo 075 ) { 076 super(project, moduleId, ecmaVersion, sourceMap, inlineEnabled, metaInfo); 077 this.files = files; 078 this.isUnitTestConfig = isUnitTestConfig; 079 } 080 081 @Override 082 public boolean isTestConfig() { 083 return isUnitTestConfig; 084 } 085 086 @NotNull 087 public List<String> getLibraries() { 088 return files; 089 } 090 091 @Override 092 protected void init(@NotNull final List<JetFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) { 093 if (files.isEmpty()) return; 094 095 final PsiManager psiManager = PsiManager.getInstance(getProject()); 096 097 Function1<String, Unit> report = new Function1<String, Unit>() { 098 @Override 099 public Unit invoke(String message) { 100 throw new IllegalStateException(message); 101 } 102 }; 103 104 Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() { 105 @Override 106 public Unit invoke(String moduleName, VirtualFile file) { 107 if (moduleName != null) { 108 JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager); 109 VfsUtilCore.visitChildrenRecursively(file, jetFileCollector); 110 } 111 else { 112 String libraryPath = PathUtil.getLocalPath(file); 113 assert libraryPath != null : "libraryPath for " + file + " should not be null"; 114 metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath)); 115 } 116 117 return Unit.INSTANCE$; 118 } 119 }; 120 121 boolean hasErrors = checkLibFilesAndReportErrors(report, action); 122 assert !hasErrors : "hasErrors should be false"; 123 } 124 125 @Override 126 public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) { 127 return checkLibFilesAndReportErrors(report, null); 128 } 129 130 private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) { 131 if (files.isEmpty()) { 132 return false; 133 } 134 135 VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL); 136 VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL); 137 138 for (String path : files) { 139 VirtualFile file; 140 141 File filePath = new File(path); 142 if (!filePath.exists()) { 143 report.invoke("Path '" + path + "' does not exist"); 144 return true; 145 } 146 147 if (path.endsWith(".jar") || path.endsWith(".zip")) { 148 file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR); 149 } 150 else { 151 file = fileSystem.findFileByPath(path); 152 } 153 154 if (file == null) { 155 report.invoke("File '" + path + "' does not exist or could not be read"); 156 return true; 157 } 158 159 String moduleName; 160 161 if (isOldKotlinJavascriptLibrary(filePath)) { 162 moduleName = LibraryUtils.getKotlinJsModuleName(filePath); 163 } 164 else if (isKotlinJavascriptLibraryWithMetadata(filePath)) { 165 moduleName = null; 166 } 167 else { 168 report.invoke("'" + path + "' is not a valid Kotlin Javascript library"); 169 return true; 170 } 171 172 if (action != null) { 173 action.invoke(moduleName, file); 174 } 175 } 176 177 return false; 178 } 179 180 public static class Builder { 181 Project project; 182 String moduleId; 183 List<String> files; 184 @NotNull 185 EcmaVersion ecmaVersion = EcmaVersion.defaultVersion(); 186 boolean sourceMap = false; 187 boolean inlineEnabled = true; 188 boolean isUnitTestConfig = false; 189 boolean metaInfo = false; 190 191 public Builder(@NotNull Project project, @NotNull String moduleId, @NotNull List<String> files) { 192 this.project = project; 193 this.moduleId = moduleId; 194 this.files = files; 195 } 196 197 public Builder ecmaVersion(@NotNull EcmaVersion ecmaVersion) { 198 this.ecmaVersion = ecmaVersion; 199 return this; 200 } 201 202 public Builder sourceMap(boolean sourceMap) { 203 this.sourceMap = sourceMap; 204 return this; 205 } 206 207 public Builder inlineEnabled(boolean inlineEnabled) { 208 this.inlineEnabled = inlineEnabled; 209 return this; 210 } 211 212 public Builder isUnitTestConfig(boolean isUnitTestConfig) { 213 this.isUnitTestConfig = isUnitTestConfig; 214 return this; 215 } 216 217 public Builder metaInfo(boolean metaInfo) { 218 this.metaInfo = metaInfo; 219 return this; 220 } 221 222 public Config build() { 223 return new LibrarySourcesConfig(project, moduleId, files, ecmaVersion, sourceMap, inlineEnabled, isUnitTestConfig, metaInfo); 224 } 225 } 226 227 protected static JetFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) { 228 PsiFile psiFile = psiManager.findFile(file); 229 assert psiFile != null; 230 231 setupPsiFile(psiFile, moduleName); 232 return (JetFile) psiFile; 233 } 234 235 protected static void setupPsiFile(PsiFile psiFile, String moduleName) { 236 psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName); 237 } 238 239 private static class JetFileCollector extends VirtualFileVisitor { 240 private final List<JetFile> jetFiles; 241 private final String moduleName; 242 private final PsiManager psiManager; 243 244 private JetFileCollector(List<JetFile> files, String name, PsiManager manager) { 245 moduleName = name; 246 psiManager = manager; 247 jetFiles = files; 248 } 249 250 @Override 251 public boolean visitFile(@NotNull VirtualFile file) { 252 if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(JetFileType.EXTENSION)) { 253 jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager)); 254 } 255 return true; 256 } 257 } 258 }