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 }