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