2 * Copyright 2000-2008 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
.ant
;
18 import com
.intellij
.compiler
.notNullVerification
.NotNullVerifyingInstrumenter
;
19 import com
.intellij
.uiDesigner
.compiler
.*;
20 import com
.intellij
.uiDesigner
.lw
.CompiledClassPropertiesProvider
;
21 import com
.intellij
.uiDesigner
.lw
.LwRootContainer
;
22 import org
.apache
.tools
.ant
.BuildException
;
23 import org
.apache
.tools
.ant
.Project
;
24 import org
.apache
.tools
.ant
.taskdefs
.Javac
;
25 import org
.apache
.tools
.ant
.types
.Path
;
26 import org
.objectweb
.asm
.ClassReader
;
27 import org
.objectweb
.asm
.ClassWriter
;
30 import java
.net
.MalformedURLException
;
32 import java
.net
.URLClassLoader
;
33 import java
.util
.ArrayList
;
34 import java
.util
.HashMap
;
35 import java
.util
.Iterator
;
36 import java
.util
.StringTokenizer
;
38 public class Javac2
extends Javac
{
39 private ArrayList myFormFiles
;
45 * Check if Java classes should be actually compiled by the task. This method is overriden by
46 * {@link com.intellij.ant.InstrumentIdeaExtensions} task in order to suppress actual compilation
47 * of the java sources.
49 * @return true if the java classes are compiled, false if just instrumentation is performed.
51 protected boolean areJavaClassesCompiled() {
56 * This method is called when option that supported only for the case when java sources are compiled
57 * and it is not supported for the case when only instrumentation is performed.
59 * @param optionName the option name to warn about.
61 private void unsupportedOptionMessage(final String optionName
) {
62 if (!areJavaClassesCompiled()) {
63 log("The option " + optionName
+ " is not supported by instrumentIdeaExtenstions task", Project
.MSG_ERR
);
68 * The overriden setter method that warns about unsupported option.
70 * @param v the option value
72 public void setDebugLevel(String v
) {
73 unsupportedOptionMessage("debugLevel");
74 super.setDebugLevel(v
);
78 * The overriden setter method that warns about unsupported option.
80 * @param list the option value
82 public void setListfiles(boolean list
) {
83 unsupportedOptionMessage("listFiles");
84 super.setListfiles(list
);
88 * The overriden setter method that warns about unsupported option.
90 * @param memoryInitialSize the option value
92 public void setMemoryInitialSize(String memoryInitialSize
) {
93 unsupportedOptionMessage("memoryInitialSize");
94 super.setMemoryInitialSize(memoryInitialSize
);
98 * The overriden setter method that warns about unsupported option.
100 * @param memoryMaximumSize the option value
102 public void setMemoryMaximumSize(String memoryMaximumSize
) {
103 unsupportedOptionMessage("memoryMaximumSize");
104 super.setMemoryMaximumSize(memoryMaximumSize
);
108 * The overriden setter method that warns about unsupported option.
110 * @param encoding the option value
112 public void setEncoding(String encoding
) {
113 unsupportedOptionMessage("encoding");
114 super.setEncoding(encoding
);
118 * The overriden setter method that warns about unsupported option.
120 * @param optimize the option value
122 public void setOptimize(boolean optimize
) {
123 unsupportedOptionMessage("optimize");
124 super.setOptimize(optimize
);
128 * The overriden setter method that warns about unsupported option.
130 * @param depend the option value
132 public void setDepend(boolean depend
) {
133 unsupportedOptionMessage("depend");
134 super.setDepend(depend
);
138 * The overriden setter method that warns about unsupported option.
140 * @param f the option value
142 public void setFork(boolean f
) {
143 unsupportedOptionMessage("fork");
148 * The overriden setter method that warns about unsupported option.
150 * @param forkExec the option value
152 public void setExecutable(String forkExec
) {
153 unsupportedOptionMessage("executable");
154 super.setExecutable(forkExec
);
158 * The overriden setter method that warns about unsupported option.
160 * @param compiler the option value
162 public void setCompiler(String compiler
) {
163 unsupportedOptionMessage("compiler");
164 super.setCompiler(compiler
);
168 * The overriden compile method that does not actually compiles java sources but only instruments
171 protected void compile() {
173 if (areJavaClassesCompiled()) {
177 ClassLoader loader
= buildClasspathClassLoader();
178 if (loader
== null) return;
179 instrumentForms(loader
);
181 //NotNull instrumentation
182 if (isJdkVersion(5) || isJdkVersion(6)) {
183 final int instrumented
= instrumentNotNull(getDestdir(), loader
);
184 log("Added @NotNull assertions to " + instrumented
+ " files", Project
.MSG_INFO
);
187 log("Skipped @NotNull instrumentation because target JDK is not 1.5 or 1.6", Project
.MSG_INFO
);
194 * @param loader a classloader to use
196 private void instrumentForms(final ClassLoader loader
) {
197 // we instrument every file, because we cannot find which files should not be instrumented without dependency storage
198 final ArrayList formsToInstrument
= myFormFiles
;
200 if (formsToInstrument
.size() == 0) {
201 log("No forms to instrument found", Project
.MSG_VERBOSE
);
205 final HashMap class2form
= new HashMap();
207 for (int i
= 0; i
< formsToInstrument
.size(); i
++) {
208 final File formFile
= (File
)formsToInstrument
.get(i
);
210 log("compiling form " + formFile
.getAbsolutePath(), Project
.MSG_VERBOSE
);
211 final LwRootContainer rootContainer
;
213 rootContainer
= Utils
.getRootContainer(formFile
.toURI().toURL(), new CompiledClassPropertiesProvider(loader
));
215 catch (AlienFormFileException e
) {
216 // ignore non-IDEA forms
219 catch (Exception e
) {
220 fireError("Cannot process form file " + formFile
.getAbsolutePath() + ". Reason: " + e
);
224 final String classToBind
= rootContainer
.getClassToBind();
225 if (classToBind
== null) {
229 String name
= classToBind
.replace('.', '/');
230 File classFile
= getClassFile(name
);
231 if (classFile
== null) {
232 log(formFile
.getAbsolutePath() + ": Class to bind does not exist: " + classToBind
, Project
.MSG_WARN
);
236 final File alreadyProcessedForm
= (File
)class2form
.get(classToBind
);
237 if (alreadyProcessedForm
!= null) {
238 fireError(formFile
.getAbsolutePath() +
240 "The form is bound to the class " +
244 alreadyProcessedForm
.getAbsolutePath() +
245 " is also bound to this class.");
248 class2form
.put(classToBind
, formFile
);
251 final AsmCodeGenerator codeGenerator
= new AsmCodeGenerator(rootContainer
, loader
, new AntNestedFormLoader(loader
), false,
252 new AntClassWriter(getAsmClassWriterFlags(), loader
));
253 codeGenerator
.patchFile(classFile
);
254 final FormErrorInfo
[] warnings
= codeGenerator
.getWarnings();
256 for (int j
= 0; j
< warnings
.length
; j
++) {
257 log(formFile
.getAbsolutePath() + ": " + warnings
[j
].getErrorMessage(), Project
.MSG_WARN
);
259 final FormErrorInfo
[] errors
= codeGenerator
.getErrors();
260 if (errors
.length
> 0) {
261 StringBuffer message
= new StringBuffer();
262 for (int j
= 0; j
< errors
.length
; j
++) {
263 if (message
.length() > 0) {
264 message
.append("\n");
266 message
.append(formFile
.getAbsolutePath()).append(": ").append(errors
[j
].getErrorMessage());
268 fireError(message
.toString());
271 catch (Exception e
) {
272 fireError("Forms instrumentation failed for " + formFile
.getAbsolutePath() + ": " + e
.toString());
278 * @return the flags for class writer
280 private int getAsmClassWriterFlags() {
281 return isJdkVersion(6) ? ClassWriter
.COMPUTE_FRAMES
: ClassWriter
.COMPUTE_MAXS
;
287 * @param ver the version to check (for Java 5 it is {@value 5}. for Java 6 it is {@value 6})
288 * @return if the target JDK is of the specified version
290 private boolean isJdkVersion(int ver
) {
291 String versionString
= Integer
.toString(ver
);
292 String targetVersion
= getTarget();
293 if (targetVersion
== null) {
294 final String
[] strings
= getCurrentCompilerArgs();
295 for (int i
= 0; i
< strings
.length
; i
++) {
296 log("currentCompilerArgs: " + strings
[i
], Project
.MSG_VERBOSE
);
297 if (strings
[i
].equals("-target") && i
< strings
.length
- 1) {
298 targetVersion
= strings
[i
+ 1];
303 if (targetVersion
!= null) {
304 log("targetVersion: " + targetVersion
, Project
.MSG_VERBOSE
);
305 return targetVersion
.equals(versionString
) || targetVersion
.equals("1." + versionString
);
307 return getCompilerVersion().equals("javac1." + versionString
);
311 * Create class loader based on classpath, bootclasspath, and sourcepath.
313 * @return a URL classloader
315 private ClassLoader
buildClasspathClassLoader() {
316 final StringBuffer classPathBuffer
= new StringBuffer();
317 final Path cp
= new Path(getProject());
318 appendPath(cp
, getBootclasspath());
319 cp
.setLocation(getDestdir().getAbsoluteFile());
320 appendPath(cp
, getClasspath());
321 appendPath(cp
, getSourcepath());
322 appendPath(cp
, getSrcdir());
323 if (getIncludeantruntime()) {
324 cp
.addExisting(cp
.concatSystemClasspath("last"));
326 if (getIncludejavaruntime()) {
329 cp
.addExtdirs(getExtdirs());
331 final String
[] pathElements
= cp
.list();
332 for (int i
= 0; i
< pathElements
.length
; i
++) {
333 final String pathElement
= pathElements
[i
];
334 classPathBuffer
.append(File
.pathSeparator
);
335 classPathBuffer
.append(pathElement
);
338 final String classPath
= classPathBuffer
.toString();
339 log("classpath=" + classPath
, Project
.MSG_VERBOSE
);
342 return createClassLoader(classPath
);
344 catch (MalformedURLException e
) {
345 fireError(e
.getMessage());
351 * Append path to class path if the appened path is not empty and is not null
353 * @param cp the path to modify
354 * @param p the path to append
356 private void appendPath(Path cp
, final Path p
) {
357 if (p
!= null && p
.size() > 0) {
363 * Instrument classes with NotNull annotations
365 * @param dir the directory with classes to instrument (the directory is processed recursively)
366 * @param loader the classloader to use
367 * @return the amount of classes actually affected by instrumentation
369 private int instrumentNotNull(File dir
, final ClassLoader loader
) {
370 int instrumented
= 0;
371 final File
[] files
= dir
.listFiles();
372 for (int i
= 0; i
< files
.length
; i
++) {
373 File file
= files
[i
];
374 final String name
= file
.getName();
375 if (name
.endsWith(".class")) {
376 final String path
= file
.getPath();
377 log("Adding @NotNull assertions to " + path
, Project
.MSG_VERBOSE
);
379 final FileInputStream inputStream
= new FileInputStream(file
);
381 ClassReader reader
= new ClassReader(inputStream
);
382 ClassWriter writer
= new AntClassWriter(getAsmClassWriterFlags(), loader
);
384 final NotNullVerifyingInstrumenter instrumenter
= new NotNullVerifyingInstrumenter(writer
);
385 reader
.accept(instrumenter
, 0);
386 if (instrumenter
.isModification()) {
387 final FileOutputStream fileOutputStream
= new FileOutputStream(path
);
389 fileOutputStream
.write(writer
.toByteArray());
393 fileOutputStream
.close();
401 catch (IOException e
) {
402 log("Failed to instrument @NotNull assertion for " + path
+ ": " + e
.getMessage(), Project
.MSG_WARN
);
404 catch (Exception e
) {
405 fireError("@NotNull instrumentation failed for " + path
+ ": " + e
.toString());
408 else if (file
.isDirectory()) {
409 instrumented
+= instrumentNotNull(file
, loader
);
416 private void fireError(final String message
) {
418 throw new BuildException(message
, getLocation());
421 log(message
, Project
.MSG_ERR
);
425 private File
getClassFile(String className
) {
426 final String classOrInnerName
= getClassOrInnerName(className
);
427 if (classOrInnerName
== null) return null;
428 return new File(getDestdir().getAbsolutePath(), classOrInnerName
+ ".class");
431 private String
getClassOrInnerName(String className
) {
432 File classFile
= new File(getDestdir().getAbsolutePath(), className
+ ".class");
433 if (classFile
.exists()) return className
;
434 int position
= className
.lastIndexOf('/');
435 if (position
== -1) return null;
436 return getClassOrInnerName(className
.substring(0, position
) + '$' + className
.substring(position
+ 1));
439 private static URLClassLoader
createClassLoader(final String classPath
) throws MalformedURLException
{
440 final ArrayList urls
= new ArrayList();
441 for (StringTokenizer tokenizer
= new StringTokenizer(classPath
, File
.pathSeparator
); tokenizer
.hasMoreTokens();) {
442 final String s
= tokenizer
.nextToken();
443 urls
.add(new File(s
).toURI().toURL());
445 final URL
[] urlsArr
= (URL
[])urls
.toArray(new URL
[urls
.size()]);
446 return new URLClassLoader(urlsArr
, null);
449 protected void resetFileLists() {
450 super.resetFileLists();
451 myFormFiles
= new ArrayList();
454 protected void scanDir(final File srcDir
, final File destDir
, final String
[] files
) {
455 super.scanDir(srcDir
, destDir
, files
);
456 for (int i
= 0; i
< files
.length
; i
++) {
457 final String file
= files
[i
];
458 if (file
.endsWith(".form")) {
459 log("Found form file " + file
, Project
.MSG_VERBOSE
);
460 myFormFiles
.add(new File(srcDir
, file
));
465 private class AntNestedFormLoader
implements NestedFormLoader
{
466 private final ClassLoader myLoader
;
467 private final HashMap myFormCache
= new HashMap();
469 public AntNestedFormLoader(final ClassLoader loader
) {
473 public LwRootContainer
loadForm(String formFileName
) throws Exception
{
474 if (myFormCache
.containsKey(formFileName
)) {
475 return (LwRootContainer
)myFormCache
.get(formFileName
);
477 String formFileFullName
= formFileName
.toLowerCase();
478 log("Searching for form " + formFileFullName
, Project
.MSG_VERBOSE
);
479 for (Iterator iterator
= myFormFiles
.iterator(); iterator
.hasNext();) {
480 File file
= (File
)iterator
.next();
481 String name
= file
.getAbsolutePath().replace(File
.separatorChar
, '/').toLowerCase();
482 log("Comparing with " + name
, Project
.MSG_VERBOSE
);
483 if (name
.endsWith(formFileFullName
)) {
484 InputStream formInputStream
= new FileInputStream(file
);
485 final LwRootContainer container
= Utils
.getRootContainer(formInputStream
, null);
486 myFormCache
.put(formFileName
, container
);
490 InputStream resourceStream
= myLoader
.getResourceAsStream("/" + formFileName
+ ".form");
491 if (resourceStream
!= null) {
492 final LwRootContainer container
= Utils
.getRootContainer(resourceStream
, null);
493 myFormCache
.put(formFileName
, container
);
496 throw new Exception("Cannot find nested form file " + formFileName
);
499 public String
getClassToBindName(LwRootContainer container
) {
500 final String className
= container
.getClassToBind();
501 String result
= getClassOrInnerName(className
.replace('.', '/'));
502 if (result
!= null) return result
.replace('/', '.');