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.Function1;
028    import kotlin.Function2;
029    import kotlin.Unit;
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.List;
041    
042    import static org.jetbrains.kotlin.utils.LibraryUtils.*;
043    
044    public class LibrarySourcesConfig extends Config {
045        @NotNull
046        public static final Key<String> EXTERNAL_MODULE_NAME = Key.create("externalModule");
047        @NotNull
048        public static final String UNKNOWN_EXTERNAL_MODULE_NAME = "<unknown>";
049    
050        public static final String STDLIB_JS_MODULE_NAME = "stdlib";
051        public static final String BUILTINS_JS_MODULE_NAME = "builtins";
052        public static final String BUILTINS_JS_FILE_NAME = BUILTINS_JS_MODULE_NAME + JavaScript.DOT_EXTENSION;
053        public static final String STDLIB_JS_FILE_NAME = STDLIB_JS_MODULE_NAME + JavaScript.DOT_EXTENSION;
054    
055        @NotNull
056        private final List<String> files;
057    
058        public LibrarySourcesConfig(
059                @NotNull Project project,
060                @NotNull String moduleId,
061                @NotNull List<String> files,
062                @NotNull EcmaVersion ecmaVersion,
063                boolean sourcemap,
064                boolean inlineEnabled
065        ) {
066            super(project, moduleId, ecmaVersion, sourcemap, inlineEnabled);
067            this.files = files;
068        }
069    
070        @NotNull
071        public List<String> getLibraries() {
072            return files;
073        }
074    
075        @Override
076        protected void init(@NotNull final List<JetFile> sourceFilesInLibraries, @NotNull final List<KotlinJavascriptMetadata> metadata) {
077            if (files.isEmpty()) return;
078    
079            final PsiManager psiManager = PsiManager.getInstance(getProject());
080    
081            Function1<String, Unit> report = new Function1<String, Unit>() {
082                @Override
083                public Unit invoke(String message) {
084                    throw new IllegalStateException(message);
085                }
086            };
087    
088            Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() {
089                @Override
090                public Unit invoke(String moduleName, VirtualFile file) {
091                    if (moduleName != null) {
092                        JetFileCollector jetFileCollector = new JetFileCollector(sourceFilesInLibraries, moduleName, psiManager);
093                        VfsUtilCore.visitChildrenRecursively(file, jetFileCollector);
094                    }
095                    else {
096                        String libraryPath = PathUtil.getLocalPath(file);
097                        assert libraryPath != null : "libraryPath for " + file + " should not be null";
098                        metadata.addAll(KotlinJavascriptMetadataUtils.loadMetadata(libraryPath));
099                    }
100    
101                    return Unit.INSTANCE$;
102                }
103            };
104    
105            boolean hasErrors = checkLibFilesAndReportErrors(report, action);
106            assert !hasErrors : "hasErrors should be false";
107        }
108    
109        @Override
110        public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) {
111            return checkLibFilesAndReportErrors(report, null);
112        }
113    
114        private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) {
115            if (files.isEmpty()) {
116                return false;
117            }
118    
119            VirtualFileSystem fileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.FILE_PROTOCOL);
120            VirtualFileSystem jarFileSystem = VirtualFileManager.getInstance().getFileSystem(StandardFileSystems.JAR_PROTOCOL);
121    
122            String moduleName = null;
123    
124            for (String path : files) {
125                VirtualFile file;
126                if (path.charAt(0) == '@') {
127                    moduleName = path.substring(1);
128                    continue;
129                }
130    
131                File filePath = new File(path);
132                if (!filePath.exists()) {
133                    report.invoke("Path '" + path + "'does not exist");
134                    return true;
135                }
136    
137                if (path.endsWith(".jar") || path.endsWith(".zip")) {
138                    file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR);
139                }
140                else {
141                    file = fileSystem.findFileByPath(path);
142                }
143    
144                if (file == null) {
145                    report.invoke("File '" + path + "'does not exist or could not be read");
146                    return true;
147                }
148                else {
149                    String actualModuleName;
150    
151                    if (moduleName != null) {
152                        actualModuleName = moduleName;
153                    }
154                    else if (isOldKotlinJavascriptLibrary(filePath)) {
155                        actualModuleName = LibraryUtils.getKotlinJsModuleName(filePath);
156                    }
157                    else if (isKotlinJavascriptLibraryWithMetadata(filePath)) {
158                        actualModuleName = null;
159                    }
160                    else {
161                        report.invoke("'" + path + "' is not a valid Kotlin Javascript library");
162                        return true;
163                    }
164    
165                    if (action != null) {
166                        action.invoke(actualModuleName, file);
167                    }
168                }
169                moduleName = null;
170            }
171    
172            return false;
173        }
174    
175        protected static JetFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) {
176            PsiFile psiFile = psiManager.findFile(file);
177            assert psiFile != null;
178    
179            setupPsiFile(psiFile, moduleName);
180            return (JetFile) psiFile;
181        }
182    
183        protected static void setupPsiFile(PsiFile psiFile, String moduleName) {
184            psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName);
185        }
186    
187        private static class JetFileCollector extends VirtualFileVisitor {
188            private final List<JetFile> jetFiles;
189            private final String moduleName;
190            private final PsiManager psiManager;
191    
192            private JetFileCollector(List<JetFile> files, String name, PsiManager manager) {
193                moduleName = name;
194                psiManager = manager;
195                jetFiles = files;
196            }
197    
198            @Override
199            public boolean visitFile(@NotNull VirtualFile file) {
200                if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(JetFileType.EXTENSION)) {
201                    jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager));
202                }
203                return true;
204            }
205        }
206    }