javac2: refactored postprocessing step from javac2 task and added support for extdirs...
[fedora-idea.git] / javac2 / src / com / intellij / ant / Javac2.java
blobb16ea99ca6d17a447bbb166b355b6d7ebcb118c8
1 /*
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;
29 import java.io.*;
30 import java.net.MalformedURLException;
31 import java.net.URL;
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;
41 public Javac2() {
44 /**
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() {
52 return true;
55 /**
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);
67 /**
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);
77 /**
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);
87 /**
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);
97 /**
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");
144 super.setFork(f);
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
169 * class files.
171 protected void compile() {
172 // compile java
173 if (areJavaClassesCompiled()) {
174 super.compile();
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);
186 else {
187 log("Skipped @NotNull instrumentation because target JDK is not 1.5 or 1.6", Project.MSG_INFO);
192 * Instrument forms
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);
202 return;
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;
212 try {
213 rootContainer = Utils.getRootContainer(formFile.toURI().toURL(), new CompiledClassPropertiesProvider(loader));
215 catch (AlienFormFileException e) {
216 // ignore non-IDEA forms
217 continue;
219 catch (Exception e) {
220 fireError("Cannot process form file " + formFile.getAbsolutePath() + ". Reason: " + e);
221 continue;
224 final String classToBind = rootContainer.getClassToBind();
225 if (classToBind == null) {
226 continue;
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);
233 continue;
236 final File alreadyProcessedForm = (File)class2form.get(classToBind);
237 if (alreadyProcessedForm != null) {
238 fireError(formFile.getAbsolutePath() +
239 ": " +
240 "The form is bound to the class " +
241 classToBind +
242 ".\n" +
243 "Another form " +
244 alreadyProcessedForm.getAbsolutePath() +
245 " is also bound to this class.");
246 continue;
248 class2form.put(classToBind, formFile);
250 try {
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;
285 * Check JDK version
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];
299 break;
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()) {
327 cp.addJavaRuntime();
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);
341 try {
342 return createClassLoader(classPath);
344 catch (MalformedURLException e) {
345 fireError(e.getMessage());
346 return null;
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) {
358 cp.append(p);
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);
378 try {
379 final FileInputStream inputStream = new FileInputStream(file);
380 try {
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);
388 try {
389 fileOutputStream.write(writer.toByteArray());
390 instrumented++;
392 finally {
393 fileOutputStream.close();
397 finally {
398 inputStream.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);
413 return instrumented;
416 private void fireError(final String message) {
417 if (failOnError) {
418 throw new BuildException(message, getLocation());
420 else {
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) {
470 myLoader = 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);
487 return 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);
494 return 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('/', '.');
503 return className;