2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com
.intellij
.uiDesigner
.make
;
18 import com
.intellij
.compiler
.PsiClassWriter
;
19 import com
.intellij
.openapi
.application
.ApplicationManager
;
20 import com
.intellij
.openapi
.compiler
.*;
21 import com
.intellij
.openapi
.diagnostic
.Logger
;
22 import com
.intellij
.openapi
.editor
.Document
;
23 import com
.intellij
.openapi
.fileEditor
.FileDocumentManager
;
24 import com
.intellij
.openapi
.fileTypes
.StdFileTypes
;
25 import com
.intellij
.openapi
.module
.Module
;
26 import com
.intellij
.openapi
.module
.ModuleUtil
;
27 import com
.intellij
.openapi
.project
.Project
;
28 import com
.intellij
.openapi
.roots
.ProjectClasspathTraversing
;
29 import com
.intellij
.openapi
.roots
.ProjectRootsTraversing
;
30 import com
.intellij
.openapi
.util
.Computable
;
31 import com
.intellij
.openapi
.vfs
.VfsUtil
;
32 import com
.intellij
.openapi
.vfs
.VirtualFile
;
33 import com
.intellij
.psi
.JavaPsiFacade
;
34 import com
.intellij
.psi
.PsiClass
;
35 import com
.intellij
.psi
.PsiFile
;
36 import com
.intellij
.psi
.search
.GlobalSearchScope
;
37 import com
.intellij
.uiDesigner
.FormEditingUtil
;
38 import com
.intellij
.uiDesigner
.GuiDesignerConfiguration
;
39 import com
.intellij
.uiDesigner
.UIDesignerBundle
;
40 import com
.intellij
.uiDesigner
.compiler
.AlienFormFileException
;
41 import com
.intellij
.uiDesigner
.compiler
.AsmCodeGenerator
;
42 import com
.intellij
.uiDesigner
.compiler
.FormErrorInfo
;
43 import com
.intellij
.uiDesigner
.compiler
.Utils
;
44 import com
.intellij
.uiDesigner
.lw
.CompiledClassPropertiesProvider
;
45 import com
.intellij
.uiDesigner
.lw
.LwRootContainer
;
46 import org
.jetbrains
.annotations
.NotNull
;
47 import org
.jetbrains
.annotations
.Nullable
;
49 import java
.io
.DataInput
;
51 import java
.io
.IOException
;
53 import java
.net
.URLClassLoader
;
54 import java
.util
.ArrayList
;
55 import java
.util
.HashMap
;
56 import java
.util
.StringTokenizer
;
58 public final class Form2ByteCodeCompiler
implements ClassInstrumentingCompiler
{
59 private static final Logger LOG
= Logger
.getInstance("#com.intellij.uiDesigner.make.Form2ByteCodeCompiler");
62 public String
getDescription() {
63 return UIDesignerBundle
.message("component.gui.designer.form.to.bytecode.compiler");
66 public boolean validateConfiguration(CompileScope scope
) {
71 public static URLClassLoader
createClassLoader(@NotNull final String classPath
){
72 final ArrayList
<URL
> urls
= new ArrayList
<URL
>();
73 for (StringTokenizer tokenizer
= new StringTokenizer(classPath
, File
.pathSeparator
); tokenizer
.hasMoreTokens();) {
74 final String s
= tokenizer
.nextToken();
76 urls
.add(new File(s
).toURI().toURL());
78 catch (Exception exc
) {
79 throw new RuntimeException(exc
);
82 return new URLClassLoader(urls
.toArray(new URL
[urls
.size()]), null);
86 public ProcessingItem
[] getProcessingItems(final CompileContext context
) {
87 final Project project
= context
.getProject();
88 if (!GuiDesignerConfiguration
.getInstance(project
).INSTRUMENT_CLASSES
) {
89 return ProcessingItem
.EMPTY_ARRAY
;
92 final ArrayList
<ProcessingItem
> items
= new ArrayList
<ProcessingItem
>();
94 ApplicationManager
.getApplication().runReadAction(new Runnable() {
96 final CompileScope scope
= context
.getCompileScope();
97 final CompileScope projectScope
= context
.getProjectCompileScope();
99 final VirtualFile
[] formFiles
= projectScope
.getFiles(StdFileTypes
.GUI_DESIGNER_FORM
, true);
100 if (formFiles
.length
==0) return;
101 final CompilerManager compilerManager
= CompilerManager
.getInstance(project
);
102 final BindingsCache bindingsCache
= new BindingsCache(project
);
104 final HashMap
<Module
, ArrayList
<VirtualFile
>> module2formFiles
= sortByModules(project
, formFiles
);
107 for (final Module module
: module2formFiles
.keySet()) {
108 final HashMap
<String
, VirtualFile
> class2form
= new HashMap
<String
, VirtualFile
>();
110 final ArrayList
<VirtualFile
> list
= module2formFiles
.get(module
);
111 for (final VirtualFile formFile
: list
) {
112 if (compilerManager
.isExcludedFromCompilation(formFile
)) {
116 final String classToBind
;
118 classToBind
= bindingsCache
.getBoundClassName(formFile
);
120 catch (AlienFormFileException e
) {
121 // ignore non-IDEA forms
124 catch (Exception e
) {
125 addMessage(context
, UIDesignerBundle
.message("error.cannot.process.form.file", e
), formFile
, CompilerMessageCategory
.ERROR
);
129 if (classToBind
== null) {
133 final VirtualFile classFile
= findFile(context
, classToBind
, module
);
134 if (classFile
== null) {
135 if (scope
.belongs(formFile
.getUrl())) {
136 addMessage(context
, UIDesignerBundle
.message("error.class.to.bind.does.not.exist", classToBind
), formFile
,
137 CompilerMessageCategory
.ERROR
);
142 final VirtualFile alreadyProcessedForm
= class2form
.get(classToBind
);
143 if (alreadyProcessedForm
!= null) {
144 if (belongsToCompileScope(context
, formFile
, classToBind
)) {
147 UIDesignerBundle
.message("error.duplicate.bind",
148 classToBind
, alreadyProcessedForm
.getPresentableUrl()),
149 formFile
, CompilerMessageCategory
.ERROR
);
153 class2form
.put(classToBind
, formFile
);
155 final ProcessingItem item
= new MyInstrumentationItem(classFile
, formFile
, classToBind
);
161 bindingsCache
.close();
166 return items
.toArray(new ProcessingItem
[items
.size()]);
169 private static boolean belongsToCompileScope(final CompileContext context
, final VirtualFile formFile
, final String classToBind
) {
170 final CompileScope compileScope
= context
.getCompileScope();
171 if (compileScope
.belongs(formFile
.getUrl())) {
174 final VirtualFile sourceFile
= findSourceFile(context
, formFile
, classToBind
);
175 return sourceFile
!= null && compileScope
.belongs(sourceFile
.getUrl());
178 private static HashMap
<Module
, ArrayList
<VirtualFile
>> sortByModules(final Project project
, final VirtualFile
[] formFiles
) {
179 final HashMap
<Module
, ArrayList
<VirtualFile
>> module2formFiles
= new HashMap
<Module
,ArrayList
<VirtualFile
>>();
180 for (final VirtualFile formFile
: formFiles
) {
181 final Module module
= ModuleUtil
.findModuleForFile(formFile
, project
);
182 if (module
!= null) {
183 ArrayList
<VirtualFile
> list
= module2formFiles
.get(module
);
185 list
= new ArrayList
<VirtualFile
>();
186 module2formFiles
.put(module
, list
);
191 // todo[anton] handle somehow
194 return module2formFiles
;
197 private static HashMap
<Module
, ArrayList
<MyInstrumentationItem
>> sortByModules(final Project project
, final ProcessingItem
[] items
) {
198 final HashMap
<Module
, ArrayList
<MyInstrumentationItem
>> module2formFiles
= new HashMap
<Module
,ArrayList
<MyInstrumentationItem
>>();
199 for (ProcessingItem item1
: items
) {
200 final MyInstrumentationItem item
= (MyInstrumentationItem
)item1
;
201 final VirtualFile formFile
= item
.getFormFile();
203 final Module module
= ModuleUtil
.findModuleForFile(formFile
, project
);
204 if (module
!= null) {
205 ArrayList
<MyInstrumentationItem
> list
= module2formFiles
.get(module
);
207 list
= new ArrayList
<MyInstrumentationItem
>();
208 module2formFiles
.put(module
, list
);
213 // todo[anton] handle somehow
216 return module2formFiles
;
220 private static VirtualFile
findFile(final CompileContext context
, final String className
, final Module module
) {
221 /*for most cases (top-level classes) this will work*/
222 VirtualFile file
= findFileByRelativePath(context
, module
, className
.replace('.', '/') + ".class");
224 // getClassFileName() is much longer than simply conversion from dots into slashes, but works for inner classes
225 file
= findFileByRelativePath(context
, module
, getClassFileName(className
.replace('$', '.'), module
) + ".class");
230 private static VirtualFile
findFileByRelativePath(final CompileContext context
, final Module module
, final String relativepath
) {
231 final VirtualFile output
= context
.getModuleOutputDirectory(module
);
232 VirtualFile file
= output
!= null? output
.findFileByRelativePath(relativepath
) : null;
234 final VirtualFile testsOutput
= context
.getModuleOutputDirectoryForTests(module
);
235 if (testsOutput
!= null && !testsOutput
.equals(output
)) {
236 file
= testsOutput
.findFileByRelativePath(relativepath
);
242 private static String
getClassFileName(final String _className
, final Module module
) {
243 final PsiClass aClass
= JavaPsiFacade
.getInstance(module
.getProject()).findClass(_className
, GlobalSearchScope
.moduleScope(module
));
244 if (aClass
== null) {
245 return _className
.replace('.', '/');
248 PsiClass outerClass
= aClass
;
249 while (outerClass
.getParent() instanceof PsiClass
) {
250 outerClass
= (PsiClass
)outerClass
.getParent();
253 final String outerQualifiedName
= outerClass
.getQualifiedName();
255 assert outerQualifiedName
!= null;
256 return outerQualifiedName
.replace('.','/') + _className
.substring(outerQualifiedName
.length()).replace('.','$');
259 public ProcessingItem
[] process(final CompileContext context
, final ProcessingItem
[] items
) {
260 final ArrayList
<ProcessingItem
> compiledItems
= new ArrayList
<ProcessingItem
>();
262 context
.getProgressIndicator().pushState();
263 context
.getProgressIndicator().setText(UIDesignerBundle
.message("progress.compiling.ui.forms"));
265 final Project project
= context
.getProject();
266 final HashMap
<Module
, ArrayList
<MyInstrumentationItem
>> module2itemsList
= sortByModules(project
, items
);
268 int formsProcessed
= 0;
270 for (final Module module
: module2itemsList
.keySet()) {
271 final String classPath
=
272 ProjectRootsTraversing
.collectRoots(module
, ProjectClasspathTraversing
.FULL_CLASSPATH_RECURSIVE
).getPathsString();
273 final ClassLoader loader
= createClassLoader(classPath
);
275 if (GuiDesignerConfiguration
.getInstance(project
).COPY_FORMS_RUNTIME_TO_OUTPUT
) {
276 final String moduleOutputPath
= CompilerPaths
.getModuleOutputPath(module
, false);
278 if (moduleOutputPath
!= null) {
279 CopyResourcesUtil
.copyFormsRuntime(moduleOutputPath
, false);
281 final String testsOutputPath
= CompilerPaths
.getModuleOutputPath(module
, true);
282 if (testsOutputPath
!= null && !testsOutputPath
.equals(moduleOutputPath
)) {
283 CopyResourcesUtil
.copyFormsRuntime(testsOutputPath
, false);
286 catch (IOException e
) {
289 UIDesignerBundle
.message("error.cannot.copy.gui.designer.form.runtime", module
.getName(), e
.toString()),
290 null, CompilerMessageCategory
.ERROR
);
294 final ArrayList
<MyInstrumentationItem
> list
= module2itemsList
.get(module
);
296 for (final MyInstrumentationItem item
: list
) {
297 //context.getProgressIndicator().setFraction((double)++formsProcessed / (double)items.length);
299 final VirtualFile formFile
= item
.getFormFile();
300 context
.getProgressIndicator().setText2(formFile
.getPresentableUrl());
302 final Document doc
= ApplicationManager
.getApplication().runReadAction(new Computable
<Document
>() {
303 public Document
compute() {
304 if (!belongsToCompileScope(context
, formFile
, item
.getClassToBindFQname())) {
307 return FileDocumentManager
.getInstance().getDocument(formFile
);
311 continue; // does not belong to current scope
314 final LwRootContainer rootContainer
;
316 rootContainer
= Utils
.getRootContainer(doc
.getText(), new CompiledClassPropertiesProvider(loader
));
318 catch (Exception e
) {
319 addMessage(context
, UIDesignerBundle
.message("error.cannot.process.form.file", e
), formFile
, CompilerMessageCategory
.ERROR
);
323 final File classFile
= VfsUtil
.virtualToIoFile(item
.getFile());
324 LOG
.assertTrue(classFile
.exists(), classFile
.getPath());
326 final AsmCodeGenerator codeGenerator
= new AsmCodeGenerator(rootContainer
, loader
,
327 new PsiNestedFormLoader(module
), false,
328 new PsiClassWriter(module
));
329 ApplicationManager
.getApplication().runReadAction(new Runnable() {
331 codeGenerator
.patchFile(classFile
);
334 final FormErrorInfo
[] errors
= codeGenerator
.getErrors();
335 final FormErrorInfo
[] warnings
= codeGenerator
.getWarnings();
336 for (FormErrorInfo warning
: warnings
) {
337 addMessage(context
, warning
, formFile
, CompilerMessageCategory
.WARNING
);
339 for (FormErrorInfo error
: errors
) {
340 addMessage(context
, error
, formFile
, CompilerMessageCategory
.ERROR
);
342 if (errors
.length
== 0) {
343 compiledItems
.add(item
);
347 context
.getProgressIndicator().popState();
349 return compiledItems
.toArray(new ProcessingItem
[compiledItems
.size()]);
352 private static void addMessage(final CompileContext context
,
354 final VirtualFile formFile
,
355 final CompilerMessageCategory severity
) {
356 addMessage(context
, new FormErrorInfo(null, s
), formFile
, severity
);
359 private static void addMessage(final CompileContext context
,
360 final FormErrorInfo e
,
361 final VirtualFile formFile
,
362 final CompilerMessageCategory severity
) {
363 if (formFile
!= null) {
364 FormElementNavigatable navigatable
= new FormElementNavigatable(context
.getProject(), formFile
, e
.getComponentId());
365 context
.addMessage(severity
,
366 formFile
.getPresentableUrl() + ": " + e
.getErrorMessage(),
367 formFile
.getUrl(), -1, -1, navigatable
);
370 context
.addMessage(severity
, e
.getErrorMessage(), null, -1, -1);
374 public ValidityState
createValidityState(final DataInput in
) throws IOException
{
375 return TimestampValidityState
.load(in
);
378 public static VirtualFile
findSourceFile(final CompileContext context
, final VirtualFile formFile
, final String className
) {
379 final Module module
= context
.getModuleByFile(formFile
);
380 if (module
== null) {
383 final PsiClass aClass
= FormEditingUtil
.findClassToBind(module
, className
);
384 if (aClass
== null) {
388 final PsiFile containingFile
= aClass
.getContainingFile();
389 if (containingFile
== null){
393 return containingFile
.getVirtualFile();
396 private static final class MyInstrumentationItem
implements ProcessingItem
{
397 private final VirtualFile myClassFile
;
398 private final VirtualFile myFormFile
;
399 private final String myClassToBindFQname
;
400 private final TimestampValidityState myState
;
402 private MyInstrumentationItem(final VirtualFile classFile
, final VirtualFile formFile
, final String classToBindFQname
) {
403 myClassFile
= classFile
;
404 myFormFile
= formFile
;
405 myClassToBindFQname
= classToBindFQname
;
406 myState
= new TimestampValidityState(formFile
.getTimeStamp());
410 public VirtualFile
getFile() {
414 public VirtualFile
getFormFile() {
418 public String
getClassToBindFQname() {
419 return myClassToBindFQname
;
422 public ValidityState
getValidityState() {