001    /*
002     * Copyright 2010-2013 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.jet.cli.jvm.compiler;
018    
019    import com.intellij.codeInsight.ExternalAnnotationsManager;
020    import com.intellij.core.CoreApplicationEnvironment;
021    import com.intellij.core.CoreJavaFileManager;
022    import com.intellij.core.JavaCoreApplicationEnvironment;
023    import com.intellij.core.JavaCoreProjectEnvironment;
024    import com.intellij.lang.java.JavaParserDefinition;
025    import com.intellij.mock.MockApplication;
026    import com.intellij.mock.MockProject;
027    import com.intellij.openapi.Disposable;
028    import com.intellij.openapi.components.ServiceManager;
029    import com.intellij.openapi.extensions.Extensions;
030    import com.intellij.openapi.fileTypes.PlainTextFileType;
031    import com.intellij.openapi.project.Project;
032    import com.intellij.openapi.util.Disposer;
033    import com.intellij.openapi.vfs.VirtualFile;
034    import com.intellij.psi.PsiDocumentManager;
035    import com.intellij.psi.PsiElementFinder;
036    import com.intellij.psi.PsiFile;
037    import com.intellij.psi.PsiManager;
038    import com.intellij.psi.compiled.ClassFileDecompilers;
039    import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicy;
040    import com.intellij.psi.impl.file.impl.JavaFileManager;
041    import org.jetbrains.annotations.NotNull;
042    import org.jetbrains.annotations.TestOnly;
043    import org.jetbrains.jet.CompilerModeProvider;
044    import org.jetbrains.jet.OperationModeProvider;
045    import org.jetbrains.jet.asJava.JavaElementFinder;
046    import org.jetbrains.jet.asJava.LightClassGenerationSupport;
047    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
048    import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation;
049    import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity;
050    import org.jetbrains.jet.cli.common.messages.MessageCollector;
051    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
052    import org.jetbrains.jet.config.CommonConfigurationKeys;
053    import org.jetbrains.jet.config.CompilerConfiguration;
054    import org.jetbrains.jet.lang.parsing.JetParserDefinition;
055    import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
056    import org.jetbrains.jet.lang.psi.JetFile;
057    import org.jetbrains.jet.lang.resolve.java.JetFilesProvider;
058    import org.jetbrains.jet.lang.resolve.kotlin.KotlinBinaryClassCache;
059    import org.jetbrains.jet.lang.resolve.kotlin.VirtualFileFinder;
060    import org.jetbrains.jet.lang.resolve.lazy.declarations.CliDeclarationProviderFactoryService;
061    import org.jetbrains.jet.lang.resolve.lazy.declarations.DeclarationProviderFactoryService;
062    import org.jetbrains.jet.plugin.JetFileType;
063    import org.jetbrains.jet.utils.PathUtil;
064    
065    import java.io.File;
066    import java.util.ArrayList;
067    import java.util.List;
068    
069    import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.ERROR;
070    import static org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity.WARNING;
071    
072    @SuppressWarnings("AssignmentToStaticFieldFromInstanceMethod")
073    public class JetCoreEnvironment {
074    
075        private static final Object APPLICATION_LOCK = new Object();
076        private static JavaCoreApplicationEnvironment ourApplicationEnvironment;
077        private static int ourProjectCount = 0;
078    
079        @NotNull
080        public static JetCoreEnvironment createForProduction(
081                @NotNull Disposable parentDisposable,
082                @NotNull CompilerConfiguration configuration
083        ) {
084            // JPS may run many instances of the compiler in parallel (there's an option for compiling independent modules in parallel in IntelliJ)
085            // All projects share the same ApplicationEnvironment, and when the last project is disposed, the ApplicationEnvironment is disposed as well
086            Disposer.register(parentDisposable, new Disposable() {
087                @Override
088                public void dispose() {
089                    synchronized (APPLICATION_LOCK) {
090                        if (--ourProjectCount <= 0) {
091                            disposeApplicationEnvironment();
092                        }
093                    }
094                }
095            });
096            JetCoreEnvironment environment =
097                    new JetCoreEnvironment(parentDisposable, getOrCreateApplicationEnvironmentForProduction(), configuration);
098            synchronized (APPLICATION_LOCK) {
099                ourProjectCount++;
100            }
101            return environment;
102        }
103    
104        @TestOnly
105        @NotNull
106        public static JetCoreEnvironment createForTests(@NotNull Disposable parentDisposable, @NotNull CompilerConfiguration configuration) {
107            // Tests are supposed to create a single project and dispose it right after use
108            return new JetCoreEnvironment(parentDisposable, createApplicationEnvironment(parentDisposable), configuration);
109        }
110    
111        @NotNull
112        private static JavaCoreApplicationEnvironment getOrCreateApplicationEnvironmentForProduction() {
113            synchronized (APPLICATION_LOCK) {
114                if (ourApplicationEnvironment != null) return ourApplicationEnvironment;
115    
116                Disposable parentDisposable = Disposer.newDisposable();
117                ourApplicationEnvironment = createApplicationEnvironment(parentDisposable);
118                ourProjectCount = 0;
119                Disposer.register(parentDisposable, new Disposable() {
120                    @Override
121                    public void dispose() {
122                        synchronized (APPLICATION_LOCK) {
123                            ourApplicationEnvironment = null;
124                        }
125                    }
126                });
127                return ourApplicationEnvironment;
128            }
129        }
130    
131        public static void disposeApplicationEnvironment() {
132            synchronized (APPLICATION_LOCK) {
133                if (ourApplicationEnvironment == null) return;
134                JavaCoreApplicationEnvironment environment = ourApplicationEnvironment;
135                ourApplicationEnvironment = null;
136                Disposer.dispose(environment.getParentDisposable());
137            }
138        }
139    
140        private static JavaCoreApplicationEnvironment createApplicationEnvironment(Disposable parentDisposable) {
141            JavaCoreApplicationEnvironment applicationEnvironment = new JavaCoreApplicationEnvironment(parentDisposable);
142    
143            // ability to get text from annotations xml files
144            applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml");
145    
146            applicationEnvironment.registerFileType(JetFileType.INSTANCE, "kt");
147            applicationEnvironment.registerFileType(JetFileType.INSTANCE, "ktm");
148            applicationEnvironment.registerFileType(JetFileType.INSTANCE, JetParserDefinition.STD_SCRIPT_SUFFIX); // should be renamed to kts
149            applicationEnvironment.registerParserDefinition(new JavaParserDefinition());
150            applicationEnvironment.registerParserDefinition(new JetParserDefinition());
151    
152            applicationEnvironment.getApplication().registerService(OperationModeProvider.class, new CompilerModeProvider());
153            applicationEnvironment.getApplication().registerService(KotlinBinaryClassCache.class, new KotlinBinaryClassCache());
154            applicationEnvironment.getApplication().registerService(DeclarationProviderFactoryService.class,
155                                                                    new CliDeclarationProviderFactoryService());
156    
157            return applicationEnvironment;
158        }
159    
160        private final JavaCoreProjectEnvironment projectEnvironment;
161        private final List<JetFile> sourceFiles = new ArrayList<JetFile>();
162        private final ClassPath classPath = new ClassPath();
163    
164        private final CoreExternalAnnotationsManager annotationsManager;
165    
166        private final CompilerConfiguration configuration;
167    
168        private JetCoreEnvironment(
169                @NotNull Disposable parentDisposable,
170                @NotNull JavaCoreApplicationEnvironment applicationEnvironment,
171                @NotNull CompilerConfiguration configuration
172        ) {
173            this.configuration = configuration.copy();
174            this.configuration.setReadOnly(true);
175    
176            projectEnvironment = new JavaCoreProjectEnvironment(parentDisposable, applicationEnvironment);
177    
178            MockProject project = projectEnvironment.getProject();
179            project.registerService(JetScriptDefinitionProvider.class, new JetScriptDefinitionProvider());
180            project.registerService(JetFilesProvider.class, new CliJetFilesProvider(this));
181            project.registerService(CoreJavaFileManager.class, (CoreJavaFileManager) ServiceManager.getService(project, JavaFileManager.class));
182    
183            CliLightClassGenerationSupport cliLightClassGenerationSupport = new CliLightClassGenerationSupport();
184            project.registerService(LightClassGenerationSupport.class, cliLightClassGenerationSupport);
185            project.registerService(CliLightClassGenerationSupport.class, cliLightClassGenerationSupport);
186    
187            Extensions.getArea(project)
188                    .getExtensionPoint(PsiElementFinder.EP_NAME)
189                    .registerExtension(new JavaElementFinder(project, cliLightClassGenerationSupport));
190    
191            // This extension points should be registered in JavaCoreApplicationEnvironment
192            CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), ClsCustomNavigationPolicy.EP_NAME,
193                                                              ClsCustomNavigationPolicy.class);
194            CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), ClassFileDecompilers.EP_NAME,
195                                                              ClassFileDecompilers.Decompiler.class);
196    
197            annotationsManager = new CoreExternalAnnotationsManager(project.getComponent(PsiManager.class));
198            project.registerService(ExternalAnnotationsManager.class, annotationsManager);
199    
200            for (File path : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
201                addToClasspath(path);
202            }
203            for (File path : configuration.getList(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY)) {
204                addExternalAnnotationsRoot(path);
205            }
206            for (String path : configuration.getList(CommonConfigurationKeys.SOURCE_ROOTS_KEY)) {
207                addSources(path);
208            }
209    
210            JetScriptDefinitionProvider.getInstance(project).addScriptDefinitions(
211                    configuration.getList(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY));
212    
213            project.registerService(VirtualFileFinder.class, new CliVirtualFileFinder(classPath));
214        }
215    
216        public CompilerConfiguration getConfiguration() {
217            return configuration;
218        }
219    
220        @NotNull
221        private CoreApplicationEnvironment getMyApplicationEnvironment() {
222            return projectEnvironment.getEnvironment();
223        }
224    
225        @NotNull
226        public MockApplication getApplication() {
227            return getMyApplicationEnvironment().getApplication();
228        }
229    
230        @NotNull
231        public Project getProject() {
232            return projectEnvironment.getProject();
233        }
234    
235        private void addExternalAnnotationsRoot(File path) {
236            if (!path.exists()) {
237                report(WARNING, "Annotations path entry points to a non-existent location: " + path);
238                return;
239            }
240            annotationsManager.addExternalAnnotationsRoot(PathUtil.jarFileOrDirectoryToVirtualFile(path));
241        }
242    
243        private void addSources(File file) {
244            if (file.isDirectory()) {
245                File[] files = file.listFiles();
246                if (files != null) {
247                    for (File child : files) {
248                        addSources(child);
249                    }
250                }
251            }
252            else {
253                VirtualFile fileByPath = getMyApplicationEnvironment().getLocalFileSystem().findFileByPath(file.getAbsolutePath());
254                if (fileByPath != null) {
255                    PsiFile psiFile = PsiManager.getInstance(getProject()).findFile(fileByPath);
256                    if (psiFile instanceof JetFile) {
257                        sourceFiles.add((JetFile) psiFile);
258                    }
259                }
260            }
261        }
262    
263        private void addSources(String path) {
264            if (path == null) {
265                return;
266            }
267    
268            VirtualFile vFile = getMyApplicationEnvironment().getLocalFileSystem().findFileByPath(path);
269            if (vFile == null) {
270                report(ERROR, "Source file or directory not found: " + path);
271                return;
272            }
273            if (!vFile.isDirectory() && vFile.getFileType() != JetFileType.INSTANCE) {
274                report(ERROR, "Source entry is not a Kotlin file: " + path);
275                return;
276            }
277    
278            addSources(new File(path));
279        }
280    
281        private void addToClasspath(File path) {
282            if (path.isFile()) {
283                VirtualFile jarFile = getMyApplicationEnvironment().getJarFileSystem().findFileByPath(path + "!/");
284                if (jarFile == null) {
285                    report(WARNING, "Classpath entry points to a file that is not a JAR archive: " + path);
286                    return;
287                }
288                projectEnvironment.addJarToClassPath(path);
289                classPath.add(jarFile);
290            }
291            else {
292                VirtualFile root = getMyApplicationEnvironment().getLocalFileSystem().findFileByPath(path.getAbsolutePath());
293                if (root == null) {
294                    report(WARNING, "Classpath entry points to a non-existent location: " + path);
295                    return;
296                }
297                projectEnvironment.addSourcesToClasspath(root);
298                classPath.add(root);
299            }
300        }
301    
302        @NotNull
303        public List<JetFile> getSourceFiles() {
304            return sourceFiles;
305        }
306    
307        private void report(@NotNull CompilerMessageSeverity severity, @NotNull String message) {
308            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
309            if (messageCollector != null) {
310                messageCollector.report(severity, message, CompilerMessageLocation.NO_LOCATION);
311            }
312            else {
313                throw new CompileEnvironmentException(message);
314            }
315        }
316    }