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 }