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