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.KotlinFileType;
033    import org.jetbrains.kotlin.js.JavaScript;
034    import org.jetbrains.kotlin.psi.KtFile;
035    import org.jetbrains.kotlin.serialization.js.ModuleKind;
036    import org.jetbrains.kotlin.utils.KotlinJavascriptMetadata;
037    import org.jetbrains.kotlin.utils.KotlinJavascriptMetadataUtils;
038    import org.jetbrains.kotlin.utils.LibraryUtils;
039    
040    import java.io.File;
041    import java.util.Collections;
042    import java.util.List;
043    
044    import static org.jetbrains.kotlin.utils.LibraryUtils.isOldKotlinJavascriptLibrary;
045    import static org.jetbrains.kotlin.utils.PathUtil.getKotlinPathsForDistDirectory;
046    
047    public class LibrarySourcesConfig extends JsConfig {
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 ModuleKind moduleKind,
070                @NotNull List<String> files,
071                @NotNull EcmaVersion ecmaVersion,
072                boolean sourceMap,
073                boolean inlineEnabled,
074                boolean isUnitTestConfig,
075                boolean metaInfo,
076                boolean kjsm
077        ) {
078            super(project, moduleId, moduleKind, ecmaVersion, sourceMap, inlineEnabled, metaInfo, kjsm);
079            this.files = files;
080            this.isUnitTestConfig = isUnitTestConfig;
081        }
082    
083        @Override
084        public boolean isTestConfig() {
085            return isUnitTestConfig;
086        }
087    
088        @NotNull
089        public List<String> getLibraries() {
090            return files;
091        }
092    
093        @Override
094        protected void init(@NotNull final List<KtFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) {
095            if (files.isEmpty()) return;
096    
097            final PsiManager psiManager = PsiManager.getInstance(getProject());
098    
099            Function1<String, Unit> report = new Function1<String, Unit>() {
100                @Override
101                public Unit invoke(String message) {
102                    throw new IllegalStateException(message);
103                }
104            };
105    
106            Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() {
107                @Override
108                public Unit invoke(String moduleName, VirtualFile file) {
109                    if (moduleName != null) {
110                        JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager);
111                        VfsUtilCore.visitChildrenRecursively(file, jetFileCollector);
112                    }
113                    else {
114                        String libraryPath = PathUtil.getLocalPath(file);
115                        assert libraryPath != null : "libraryPath for " + file + " should not be null";
116                        metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath));
117                    }
118    
119                    return Unit.INSTANCE;
120                }
121            };
122    
123            boolean hasErrors = checkLibFilesAndReportErrors(report, action);
124            assert !hasErrors : "hasErrors should be false";
125        }
126    
127        @Override
128        public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) {
129            return checkLibFilesAndReportErrors(report, null);
130        }
131    
132        private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) {
133            if (files.isEmpty()) {
134                return false;
135            }
136    
137            VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL);
138            VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL);
139    
140            for (String path : files) {
141                VirtualFile file;
142    
143                File filePath = new File(path);
144                if (!filePath.exists()) {
145                    report.invoke("Path '" + path + "' does not exist");
146                    return true;
147                }
148    
149                if (path.endsWith(".jar") || path.endsWith(".zip")) {
150                    file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR);
151                }
152                else {
153                    file = fileSystem.findFileByPath(path);
154                }
155    
156                if (file == null) {
157                    report.invoke("File '" + path + "' does not exist or could not be read");
158                    return true;
159                }
160    
161                String moduleName;
162    
163                if (isOldKotlinJavascriptLibrary(filePath)) {
164                    moduleName = LibraryUtils.getKotlinJsModuleName(filePath);
165                }
166                else {
167                    List<KotlinJavascriptMetadata> metadataList = KotlinJavascriptMetadataUtils.loadMetadata(filePath);
168                    if (metadataList.isEmpty()) {
169                        report.invoke("'" + path + "' is not a valid Kotlin Javascript library");
170                        return true;
171                    }
172    
173                    for (KotlinJavascriptMetadata metadata : metadataList) {
174                        if (!metadata.isAbiVersionCompatible()) {
175                            report.invoke("File '" + path + "' was compiled with an incompatible version of Kotlin. " +
176                                          "Its ABI version is " + metadata.getAbiVersion() +
177                                          ", expected ABI version is " + KotlinJavascriptMetadataUtils.ABI_VERSION);
178                            return true;
179                        }
180                    }
181    
182                    moduleName = null;
183                }
184    
185                if (action != null) {
186                    action.invoke(moduleName, file);
187                }
188            }
189    
190            return false;
191        }
192    
193        public static class Builder {
194            Project project;
195            String moduleId;
196            @NotNull
197            ModuleKind moduleKind = ModuleKind.PLAIN;
198            List<String> files;
199            @NotNull
200            EcmaVersion ecmaVersion = EcmaVersion.defaultVersion();
201            boolean sourceMap = false;
202            boolean inlineEnabled = true;
203            boolean isUnitTestConfig = false;
204            boolean metaInfo = false;
205            boolean kjsm = false;
206    
207            public Builder(@NotNull Project project, @NotNull String moduleId, @NotNull List<String> files) {
208                this.project = project;
209                this.moduleId = moduleId;
210                this.files = files;
211            }
212    
213            public Builder ecmaVersion(@NotNull EcmaVersion ecmaVersion) {
214                this.ecmaVersion = ecmaVersion;
215                return this;
216            }
217    
218            public Builder sourceMap(boolean sourceMap) {
219                this.sourceMap = sourceMap;
220                return this;
221            }
222    
223            public Builder inlineEnabled(boolean inlineEnabled) {
224                this.inlineEnabled = inlineEnabled;
225                return this;
226            }
227    
228            public Builder isUnitTestConfig(boolean isUnitTestConfig) {
229                this.isUnitTestConfig = isUnitTestConfig;
230                return this;
231            }
232    
233            public Builder metaInfo(boolean metaInfo) {
234                this.metaInfo = metaInfo;
235                return this;
236            }
237    
238            public Builder kjsm(boolean kjsm) {
239                this.kjsm = kjsm;
240                return this;
241            }
242    
243            @NotNull
244            public Builder moduleKind(@NotNull ModuleKind moduleKind) {
245                this.moduleKind = moduleKind;
246                return this;
247            }
248    
249            public JsConfig build() {
250                return new LibrarySourcesConfig(project, moduleId, moduleKind, files, ecmaVersion, sourceMap, inlineEnabled, isUnitTestConfig,
251                                                metaInfo, kjsm);
252            }
253        }
254    
255        protected static KtFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) {
256            PsiFile psiFile = psiManager.findFile(file);
257            assert psiFile != null;
258    
259            setupPsiFile(psiFile, moduleName);
260            return (KtFile) psiFile;
261        }
262    
263        protected static void setupPsiFile(PsiFile psiFile, String moduleName) {
264            psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName);
265        }
266    
267        private static class JetFileCollector extends VirtualFileVisitor {
268            private final List<KtFile> jetFiles;
269            private final String moduleName;
270            private final PsiManager psiManager;
271    
272            private JetFileCollector(List<KtFile> files, String name, PsiManager manager) {
273                moduleName = name;
274                psiManager = manager;
275                jetFiles = files;
276            }
277    
278            @Override
279            public boolean visitFile(@NotNull VirtualFile file) {
280                if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(KotlinFileType.EXTENSION)) {
281                    jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager));
282                }
283                return true;
284            }
285        }
286    }