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