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 }