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.compiled.ClassFileDecompilers;
037    import com.intellij.psi.impl.compiled.ClsCustomNavigationPolicy;
038    import com.intellij.psi.impl.file.impl.JavaFileManager;
039    import kotlin.Function1;
040    import kotlin.Unit;
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.KotlinLightClassForPackage;
047    import org.jetbrains.jet.asJava.LightClassGenerationSupport;
048    import org.jetbrains.jet.cli.common.CLIConfigurationKeys;
049    import org.jetbrains.jet.cli.common.messages.CompilerMessageLocation;
050    import org.jetbrains.jet.cli.common.messages.CompilerMessageSeverity;
051    import org.jetbrains.jet.cli.common.messages.MessageCollector;
052    import org.jetbrains.jet.cli.jvm.JVMConfigurationKeys;
053    import org.jetbrains.jet.config.CommonConfigurationKeys;
054    import org.jetbrains.jet.config.CompilerConfiguration;
055    import org.jetbrains.jet.lang.parsing.JetParserDefinition;
056    import org.jetbrains.jet.lang.parsing.JetScriptDefinitionProvider;
057    import org.jetbrains.jet.lang.psi.JetFile;
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        @NotNull
141        private static JavaCoreApplicationEnvironment createApplicationEnvironment(@NotNull Disposable parentDisposable) {
142            JavaCoreApplicationEnvironment applicationEnvironment = new JavaCoreApplicationEnvironment(parentDisposable);
143    
144            registerApplicationServicesForCLI(applicationEnvironment);
145            registerApplicationServices(applicationEnvironment);
146    
147            return applicationEnvironment;
148        }
149    
150        private static void registerApplicationServicesForCLI(@NotNull JavaCoreApplicationEnvironment applicationEnvironment) {
151            // ability to get text from annotations xml files
152            applicationEnvironment.registerFileType(PlainTextFileType.INSTANCE, "xml");
153            applicationEnvironment.registerParserDefinition(new JavaParserDefinition());
154        }
155    
156        // made public for Upsource
157        public static void registerApplicationServices(@NotNull JavaCoreApplicationEnvironment applicationEnvironment) {
158            applicationEnvironment.registerFileType(JetFileType.INSTANCE, "kt");
159            applicationEnvironment.registerFileType(JetFileType.INSTANCE, "ktm");
160            applicationEnvironment.registerFileType(JetFileType.INSTANCE, JetParserDefinition.STD_SCRIPT_SUFFIX); // should be renamed to kts
161            applicationEnvironment.registerParserDefinition(new JetParserDefinition());
162    
163            applicationEnvironment.getApplication().registerService(OperationModeProvider.class, new CompilerModeProvider());
164            applicationEnvironment.getApplication().registerService(KotlinBinaryClassCache.class, new KotlinBinaryClassCache());
165        }
166    
167        private final JavaCoreProjectEnvironment projectEnvironment;
168        private final List<JetFile> sourceFiles = new ArrayList<JetFile>();
169        private final ClassPath classPath = new ClassPath();
170    
171        private final CoreExternalAnnotationsManager annotationsManager;
172    
173        private final CompilerConfiguration configuration;
174    
175        private JetCoreEnvironment(
176                @NotNull Disposable parentDisposable,
177                @NotNull JavaCoreApplicationEnvironment applicationEnvironment,
178                @NotNull CompilerConfiguration configuration
179        ) {
180            this.configuration = configuration.copy();
181            this.configuration.setReadOnly(true);
182    
183            projectEnvironment = new JavaCoreProjectEnvironment(parentDisposable, applicationEnvironment);
184    
185            MockProject project = projectEnvironment.getProject();
186            annotationsManager = new CoreExternalAnnotationsManager(project.getComponent(PsiManager.class));
187            project.registerService(ExternalAnnotationsManager.class, annotationsManager);
188            project.registerService(DeclarationProviderFactoryService.class, new CliDeclarationProviderFactoryService(sourceFiles));
189    
190            registerProjectServicesForCLI(projectEnvironment);
191            registerProjectServices(projectEnvironment);
192    
193            // This extension points should be registered in JavaCoreApplicationEnvironment
194            CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), ClsCustomNavigationPolicy.EP_NAME,
195                                                              ClsCustomNavigationPolicy.class);
196            CoreApplicationEnvironment.registerExtensionPoint(Extensions.getRootArea(), ClassFileDecompilers.EP_NAME,
197                                                              ClassFileDecompilers.Decompiler.class);
198    
199            for (File path : configuration.getList(JVMConfigurationKeys.CLASSPATH_KEY)) {
200                addToClasspath(path);
201            }
202            for (File path : configuration.getList(JVMConfigurationKeys.ANNOTATIONS_PATH_KEY)) {
203                addExternalAnnotationsRoot(path);
204            }
205            sourceFiles.addAll(
206                    CompileEnvironmentUtil
207                            .getJetFiles(getProject(), configuration.getList(CommonConfigurationKeys.SOURCE_ROOTS_KEY),
208                                         new Function1<String, Unit>() {
209                                             @Override
210                                             public Unit invoke(String s) {
211                                                 report(ERROR, s);
212                                                 return Unit.INSTANCE$;
213                                             }
214                                         }));
215            JetScriptDefinitionProvider.getInstance(project).addScriptDefinitions(
216                    configuration.getList(CommonConfigurationKeys.SCRIPT_DEFINITIONS_KEY));
217    
218            project.registerService(VirtualFileFinder.class, new CliVirtualFileFinder(classPath));
219        }
220    
221        // made public for Upsource
222        public static void registerProjectServices(@NotNull JavaCoreProjectEnvironment projectEnvironment) {
223            MockProject project = projectEnvironment.getProject();
224            project.registerService(JetScriptDefinitionProvider.class, new JetScriptDefinitionProvider());
225    
226            CliLightClassGenerationSupport cliLightClassGenerationSupport = new CliLightClassGenerationSupport();
227            project.registerService(LightClassGenerationSupport.class, cliLightClassGenerationSupport);
228            project.registerService(CliLightClassGenerationSupport.class, cliLightClassGenerationSupport);
229            project.registerService(KotlinLightClassForPackage.FileStubCache.class, new KotlinLightClassForPackage.FileStubCache(project));
230    
231            Extensions.getArea(project)
232                    .getExtensionPoint(PsiElementFinder.EP_NAME)
233                    .registerExtension(new JavaElementFinder(project, cliLightClassGenerationSupport));
234        }
235    
236        private static void registerProjectServicesForCLI(@NotNull JavaCoreProjectEnvironment projectEnvironment) {
237            MockProject project = projectEnvironment.getProject();
238            project.registerService(CoreJavaFileManager.class, (CoreJavaFileManager) ServiceManager.getService(project, JavaFileManager.class));
239        }
240    
241        @NotNull
242        public CompilerConfiguration getConfiguration() {
243            return configuration;
244        }
245    
246        @NotNull
247        private CoreApplicationEnvironment getMyApplicationEnvironment() {
248            return projectEnvironment.getEnvironment();
249        }
250    
251        @NotNull
252        public MockApplication getApplication() {
253            return getMyApplicationEnvironment().getApplication();
254        }
255    
256        @NotNull
257        public Project getProject() {
258            return projectEnvironment.getProject();
259        }
260    
261        private void addExternalAnnotationsRoot(File path) {
262            if (!path.exists()) {
263                report(WARNING, "Annotations path entry points to a non-existent location: " + path);
264                return;
265            }
266            annotationsManager.addExternalAnnotationsRoot(PathUtil.jarFileOrDirectoryToVirtualFile(path));
267        }
268    
269        private void addToClasspath(File path) {
270            if (path.isFile()) {
271                VirtualFile jarFile = getMyApplicationEnvironment().getJarFileSystem().findFileByPath(path + "!/");
272                if (jarFile == null) {
273                    report(WARNING, "Classpath entry points to a file that is not a JAR archive: " + path);
274                    return;
275                }
276                projectEnvironment.addJarToClassPath(path);
277                classPath.add(jarFile);
278            }
279            else {
280                VirtualFile root = getMyApplicationEnvironment().getLocalFileSystem().findFileByPath(path.getAbsolutePath());
281                if (root == null) {
282                    report(WARNING, "Classpath entry points to a non-existent location: " + path);
283                    return;
284                }
285                projectEnvironment.addSourcesToClasspath(root);
286                classPath.add(root);
287            }
288        }
289    
290        @NotNull
291        public List<JetFile> getSourceFiles() {
292            return sourceFiles;
293        }
294    
295        private void report(@NotNull CompilerMessageSeverity severity, @NotNull String message) {
296            MessageCollector messageCollector = configuration.get(CLIConfigurationKeys.MESSAGE_COLLECTOR_KEY);
297            if (messageCollector != null) {
298                messageCollector.report(severity, message, CompilerMessageLocation.NO_LOCATION);
299            }
300            else {
301                throw new CompileEnvironmentException(message);
302            }
303        }
304    }