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