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