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.io.URLUtil;
026    import kotlin.Function1;
027    import kotlin.Function2;
028    import kotlin.Unit;
029    import org.jetbrains.annotations.NotNull;
030    import org.jetbrains.annotations.Nullable;
031    import org.jetbrains.kotlin.js.JavaScript;
032    import org.jetbrains.kotlin.idea.JetFileType;
033    import org.jetbrains.kotlin.psi.JetFile;
034    import org.jetbrains.kotlin.utils.LibraryUtils;
035    
036    import java.io.File;
037    import java.util.ArrayList;
038    import java.util.Collections;
039    import java.util.List;
040    
041    import static org.jetbrains.kotlin.utils.LibraryUtils.isKotlinJavascriptLibrary;
042    import static org.jetbrains.kotlin.utils.LibraryUtils.isKotlinJavascriptStdLibrary;
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        @Override
072        protected List<JetFile> generateLibFiles() {
073            if (files.isEmpty()) {
074                return Collections.emptyList();
075            }
076    
077            final List<JetFile> jetFiles = new ArrayList<JetFile>();
078            final PsiManager psiManager = PsiManager.getInstance(getProject());
079    
080            Function1<String, Unit> report = new Function1<String, Unit>() {
081                @Override
082                public Unit invoke(String message) {
083                    throw new IllegalStateException(message);
084                }
085            };
086    
087            Function2<String, VirtualFile, Unit> action = new Function2<String, VirtualFile, Unit>() {
088                @Override
089                public Unit invoke(String moduleName, VirtualFile file) {
090                    JetFileCollector jetFileCollector = new JetFileCollector(jetFiles, moduleName, psiManager);
091                    VfsUtilCore.visitChildrenRecursively(file, jetFileCollector);
092                    return Unit.INSTANCE$;
093                }
094            };
095    
096            boolean hasErrors = checkLibFilesAndReportErrors(report, action);
097            assert !hasErrors : "hasErrors should be false";
098    
099            return jetFiles;
100        }
101    
102        @Override
103        public boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report) {
104            return checkLibFilesAndReportErrors(report, null);
105        }
106    
107        private boolean checkLibFilesAndReportErrors(@NotNull Function1<String, Unit> report, @Nullable Function2<String, VirtualFile, Unit> action) {
108            if (files.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            String moduleName = null;
116    
117            for (String path : files) {
118                VirtualFile file;
119                String actualModuleName = moduleName;
120                if (path.charAt(0) == '@') {
121                    moduleName = path.substring(1);
122                    continue;
123                }
124    
125                File filePath = new File(path);
126                if (!filePath.exists()) {
127                    report.invoke("Path '" + path + "'does not exist");
128                    return true;
129                }
130    
131                if (path.endsWith(".jar") || path.endsWith(".zip")) {
132                    file = jarFileSystem.findFileByPath(path + URLUtil.JAR_SEPARATOR);
133                }
134                else {
135                    file = fileSystem.findFileByPath(path);
136                }
137    
138                if (file == null) {
139                    report.invoke("File '" + path + "'does not exist or could not be read");
140                    return true;
141                }
142                else {
143                    if (isKotlinJavascriptStdLibrary(filePath)) {
144                        actualModuleName = STDLIB_JS_MODULE_NAME;
145                    }
146                    else if (isKotlinJavascriptLibrary(filePath)) {
147                        actualModuleName = LibraryUtils.getKotlinJsModuleName(filePath);
148                    }
149                    else if (actualModuleName == null) {
150                        report.invoke("'" + path + "' is not a valid Kotlin Javascript library");
151                        return true;
152                    }
153                    if (actualModuleName == null) {
154                        report.invoke("Could not find " + LibraryUtils.KOTLIN_JS_MODULE_NAME + " for '" + path + "'");
155                        return true;
156                    }
157                    if (action != null) {
158                        action.invoke(actualModuleName, file);
159                    }
160                }
161                moduleName = null;
162            }
163    
164            return false;
165        }
166    
167        protected JetFile getJetFileByVirtualFile(VirtualFile file, String moduleName, PsiManager psiManager) {
168            PsiFile psiFile = psiManager.findFile(file);
169            assert psiFile != null;
170    
171            setupPsiFile(psiFile, moduleName);
172            return (JetFile) psiFile;
173        }
174    
175        protected static void setupPsiFile(PsiFile psiFile, String moduleName) {
176            psiFile.putUserData(EXTERNAL_MODULE_NAME, moduleName);
177        }
178    
179        private class JetFileCollector extends VirtualFileVisitor {
180            private final List<JetFile> jetFiles;
181            private final String moduleName;
182            private final PsiManager psiManager;
183    
184            private JetFileCollector(List<JetFile> files, String name, PsiManager manager) {
185                moduleName = name;
186                psiManager = manager;
187                jetFiles = files;
188            }
189    
190            @Override
191            public boolean visitFile(@NotNull VirtualFile file) {
192                if (!file.isDirectory() && StringUtil.notNullize(file.getExtension()).equalsIgnoreCase(JetFileType.EXTENSION)) {
193                    jetFiles.add(getJetFileByVirtualFile(file, moduleName, psiManager));
194                }
195                return true;
196            }
197        }
198    }