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