2
* Copyright (c) 2005-2010 Laf-Widget Kirill Grouchnikov. All Rights Reserved.
4
* Redistribution and use in source and binary forms, with or without
5
* modification, are permitted provided that the following conditions are met:
7
* o Redistributions of source code must retain the above copyright notice,
8
* this list of conditions and the following disclaimer.
10
* o Redistributions in binary form must reproduce the above copyright notice,
11
* this list of conditions and the following disclaimer in the documentation
12
* and/or other materials provided with the distribution.
14
* o Neither the name of Laf-Widget Kirill Grouchnikov nor the names of
15
* its contributors may be used to endorse or promote products derived
16
* from this software without specific prior written permission.
18
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
20
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
22
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
25
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
26
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
27
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
28
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
package org.pushingpixels.lafwidget.ant;
33
import java.lang.reflect.Method;
35
import java.net.URLClassLoader;
38
import org.objectweb.asm.*;
41
* Augments the UI classes with ghosting painting. Is based on sample adapter
42
* from ASM distribution.
44
* @author Kirill Grouchnikov
46
public class ContainerGhostingAugmenter {
48
* Verbosity indication.
50
private boolean isVerbose;
53
* Adapter for augmenting a single class.
55
* @author Kirill Grouchnikov.
57
protected class AugmentClassAdapter extends ClassAdapter implements Opcodes {
59
* Contains all method names.
61
private Set<String> existingMethods;
64
* Contains all field names.
66
private Set<String> existingFields;
71
private Method methodToAugment;
74
* <code>true</code> if the code needs to be injected after the call to
75
* the original implementation.
77
private boolean toInjectAfterOriginal;
80
* Prefix for delegate methods that will be added.
82
private String prefix;
85
* Creates a new augmentor.
88
* Class visitor to recreate the non-augmented methods.
89
* @param existingMethods
90
* Contains all method names.
91
* @param existingFields
92
* Contains all field names.
93
* @param methodToAugment
95
* @param toInjectAfterOriginal
96
* <code>true</code> if the code needs to be injected after
97
* the call to the original implementation.
99
public AugmentClassAdapter(final ClassVisitor cv,
100
Set<String> existingMethods, Set<String> existingFields,
101
Method methodToAugment, boolean toInjectAfterOriginal) {
103
this.existingMethods = existingMethods;
104
this.existingFields = existingFields;
105
this.methodToAugment = methodToAugment;
106
this.toInjectAfterOriginal = toInjectAfterOriginal;
112
* @see org.objectweb.asm.ClassAdapter#visit(int, int, java.lang.String,
113
* java.lang.String, java.lang.String, java.lang.String[])
116
public void visit(final int version, final int access,
117
final String name, final String signature,
118
final String superName, final String[] interfaces) {
119
this.prefix = "__" + name.replaceAll("/", "__") + "__container__";
121
.visit(version, access, name, signature, superName,
124
// Check if need to add the containerGhostingMarker field (boolean)
125
if (!this.existingFields.contains("containerGhostingMarker")) {
126
FieldVisitor fv = this.visitField(Opcodes.ACC_PROTECTED,
127
"containerGhostingMarker", "Z", null, null);
131
// We have three separate cases for the method that we
134
// 1. The current .class has both function and the __ version -
135
// already has been augmented. Can be ignored.
137
// 2. The current .class has function but doesn't have the __
138
// version. Than, the original function has already been renamed to
139
// __ (in the visitMethod). We need to create a new version for this
140
// function that performs pre-logic, calls __ and performs the
143
// 3. The current .class doesn't have neither the function nor
144
// the __ version. In this case we need to create the __ version
145
// that calls super (with the original name) and the function that
146
// performs pre-logic, calls __ and performs the post-logic.
148
String methodName = methodToAugment.getName();
149
boolean hasOriginal = this.existingMethods.contains(methodName);
150
boolean hasDelegate = this.existingMethods.contains(this.prefix
153
String methodSignature = Utils.getMethodDesc(methodToAugment);
154
int paramCount = methodToAugment.getParameterTypes().length;
155
if (ContainerGhostingAugmenter.this.isVerbose)
156
System.out.println("... Augmenting " + methodName + " "
157
+ methodSignature + " : original - " + hasOriginal
158
+ ", delegate - " + hasDelegate + ", " + paramCount
162
if (toInjectAfterOriginal) {
163
this.augmentUpdateMethodAfter(!hasOriginal, name,
164
superName, methodSignature);
166
this.augmentUpdateMethodBefore(!hasOriginal, name,
167
superName, methodSignature);
173
* Augments the <code>update</code> method that is assumed to always
174
* have two parameters, injecting the ghosting code <b>before</b> the
175
* original implementation.
177
* @param toSynthOriginal
178
* Indication whether we need to create an empty (only call
179
* to super()) implementation.
182
* @param superClassName
183
* Super class name (relevant for generating empty
186
* Function signature (using JNI style declaration). Example
187
* for <code>void installUI(JButton button)</code>:
188
* <code>(Ljavax/swing/JButton;)V</code>.
190
public void augmentUpdateMethodBefore(boolean toSynthOriginal,
191
String className, String superClassName, String methodDesc) {
192
// Some ASM woodoo. The code below was generated by using
193
// ASMifierClassVisitor.
194
if (toSynthOriginal) {
195
MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
196
this.prefix + "update",
197
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
200
mv.visitVarInsn(ALOAD, 0);
201
mv.visitVarInsn(ALOAD, 1);
202
mv.visitVarInsn(ALOAD, 2);
203
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
205
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
206
mv.visitInsn(RETURN);
211
MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED, "update",
212
methodDesc, null, null);
214
mv.visitVarInsn(ALOAD, 2);
215
mv.visitVarInsn(ALOAD, 1);
219
"org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils",
221
"(Ljava/awt/Component;Ljava/awt/Graphics;)V");
222
mv.visitVarInsn(ALOAD, 0);
223
mv.visitVarInsn(ALOAD, 1);
224
mv.visitVarInsn(ALOAD, 2);
225
mv.visitMethodInsn(INVOKEVIRTUAL, className,
226
this.prefix + "update",
227
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
228
mv.visitInsn(RETURN);
234
* Augments the <code>update</code> method that is assumed to always
235
* have two parameters, injecting the ghosting code <b>after</b> the
236
* original implementation.
238
* @param toSynthOriginal
239
* Indication whether we need to create an empty (only call
240
* to super()) implementation.
243
* @param superClassName
244
* Super class name (relevant for generating empty
247
* Function signature (using JNI style declaration). Example
248
* for <code>void installUI(JButton button)</code>:
249
* <code>(Ljavax/swing/JButton;)V</code>.
251
public void augmentUpdateMethodAfter(boolean toSynthOriginal,
252
String className, String superClassName, String methodDesc) {
253
// Some ASM woodoo. The code below was generated by using
254
// ASMifierClassVisitor.
255
if (toSynthOriginal) {
256
MethodVisitor mv = this.cv.visitMethod(Opcodes.ACC_PUBLIC,
257
this.prefix + "update",
258
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V", null,
261
mv.visitVarInsn(ALOAD, 0);
262
mv.visitVarInsn(ALOAD, 1);
263
mv.visitVarInsn(ALOAD, 2);
264
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, superClassName,
266
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
267
mv.visitInsn(RETURN);
272
MethodVisitor mv = this.cv.visitMethod(ACC_PROTECTED, "update",
273
methodDesc, null, null);
275
mv.visitVarInsn(ALOAD, 0);
276
mv.visitVarInsn(ALOAD, 1);
277
mv.visitVarInsn(ALOAD, 2);
278
mv.visitMethodInsn(INVOKEVIRTUAL, className,
279
this.prefix + "update",
280
"(Ljava/awt/Graphics;Ljavax/swing/JComponent;)V");
281
mv.visitVarInsn(ALOAD, 2);
282
mv.visitVarInsn(ALOAD, 1);
286
"org/pushingpixels/lafwidget/animation/effects/GhostPaintingUtils",
288
"(Ljava/awt/Component;Ljava/awt/Graphics;)V");
289
mv.visitInsn(RETURN);
297
* @see org.objectweb.asm.ClassAdapter#visitMethod(int,
298
* java.lang.String, java.lang.String, java.lang.String,
299
* java.lang.String[])
302
public MethodVisitor visitMethod(final int access, final String name,
303
final String desc, final String signature,
304
final String[] exceptions) {
305
if (methodToAugment.getName().equals(name)) {
306
// possible candidate for weaving. Check if has __ already
307
if (!this.existingMethods.contains(this.prefix + name)) {
308
// effectively renames the existing method prepending __
310
if (ContainerGhostingAugmenter.this.isVerbose)
311
System.out.println("... renaming '" + name + "(" + desc
312
+ ")' to '" + (this.prefix + name) + "'");
313
return this.cv.visitMethod(access, this.prefix + name,
314
desc, signature, exceptions);
317
// preserve the existing method as is
318
return this.cv.visitMethod(access, name, desc, signature,
324
* Augments a single class with image ghosting UI behaviour.
327
* Root directory for the library that contains the class.
329
* Fully-qualified class name.
330
* @param toInjectAfterOriginal
331
* <code>true</code> if the code needs to be injected after the
332
* call to the original implementation.
333
* @throws AugmentException
334
* If the augmentation process failed.
336
protected synchronized void augmentClass(String dir, final String name,
337
final boolean toInjectAfterOriginal) {
339
System.out.println("Working on " + name + ".update() ["
340
+ (toInjectAfterOriginal ? "after]" : "before]"));
341
// gets an input stream to read the bytecode of the class
342
String resource = dir + File.separator + name.replace('.', '/')
345
Method methodToAugment = null;
347
ClassLoader cl = new URLClassLoader(new URL[] { new File(dir)
348
.toURL() }, ContainerGhostingAugmenter.class
350
Class<?> clazz = cl.loadClass(name);
351
// Start iterating over all methods and see what do we
353
while (clazz != null) {
354
if (methodToAugment != null)
356
Method[] methods = clazz.getDeclaredMethods();
357
for (int i = 0; i < methods.length; i++) {
358
Method method = methods[i];
359
if (!"update".equals(method.getName()))
361
methodToAugment = method;
364
clazz = clazz.getSuperclass();
366
} catch (Exception e) {
367
throw new AugmentException(name, e);
370
Set<String> existingMethods = null;
371
Set<String> existingFields = null;
372
InputStream is = null;
374
is = new FileInputStream(resource);
375
ClassReader cr = new ClassReader(is);
376
InfoClassVisitor infoAdapter = new InfoClassVisitor();
377
cr.accept(infoAdapter, false);
378
existingMethods = infoAdapter.getMethods();
379
existingFields = infoAdapter.getFields();
380
} catch (Exception e) {
381
throw new AugmentException(name, e);
385
} catch (IOException ioe) {
389
// See if the 'containerGhostingMarker' field is already defined. In
391
// case the class is *not* augmented
392
if (existingFields.contains("containerGhostingMarker")) {
395
.println("Not augmenting resource, field 'containerGhostingMarker' is present");
399
// Augment the class (overriding the existing file).
402
is = new FileInputStream(resource);
403
ClassReader cr = new ClassReader(is);
404
ClassWriter cw = new ClassWriter(false);
405
ClassVisitor cv = new AugmentClassAdapter(cw, existingMethods,
406
existingFields, methodToAugment, toInjectAfterOriginal);
407
cr.accept(cv, false);
408
b = cw.toByteArray();
409
} catch (Exception e) {
410
throw new AugmentException(name, e);
414
} catch (IOException ioe) {
418
FileOutputStream fos = null;
420
fos = new FileOutputStream(resource);
423
System.out.println("Updated resource " + resource);
424
} catch (Exception e) {
429
} catch (IOException ioe) {
436
* Processes a single file or a directory, augmenting all relevant classes.
439
* The leading prefix to strip from the file names. Is used to
440
* create fully-qualified class name.
442
* File resource (can point to a single file or to a directory).
444
* List of class-method pairs to augment.
445
* @throws AugmentException
446
* If the augmentation process failed.
448
public void process(String toStrip, File file,
449
List<ContainerGhostingType> ids) throws AugmentException {
450
if (file.isDirectory()) {
451
File[] children = file.listFiles();
452
for (int i = 0; i < children.length; i++) {
453
this.process(toStrip, children[i], ids);
456
String currClassName = file.getAbsolutePath().substring(
457
toStrip.length() + 1);
458
currClassName = currClassName.replace(File.separatorChar, '.');
459
currClassName = currClassName.substring(0,
460
currClassName.length() - 6);
461
for (ContainerGhostingType igt : ids) {
462
// System.out.println(currClassName + ":" + igt.getClassName());
463
if (currClassName.equals(igt.getClassName())) {
464
this.augmentClass(toStrip, igt.getClassName(), igt
465
.isToInjectAfterOriginal());
472
* Sets the verbosity.
475
* New value for augmentation process verbosity.
477
public void setVerbose(boolean isVerbose) {
478
this.isVerbose = isVerbose;
485
* @throws AugmentException
487
public static void main(final String args[]) throws AugmentException {
488
if (args.length == 0) {
490
.println("Usage : java ... IconGhostingDelegateAugmenter [-verbose] [-pattern class_pattern] file_resource");
492
.println("\tIf -verbose option is specified, the augmenter prints out its actions.");
494
.println("\tIf -class option if specified, its value is used as class name to augment.");
496
.println("\tIf -before option if specified, the code is injected before the original code.");
498
.println("\tThe last parameter can point to either a file or a directory. "
499
+ "The directory should be the root directory for classes.");
503
ContainerGhostingAugmenter uiDelegateAugmenter = new ContainerGhostingAugmenter();
506
String className = null;
507
boolean isAfter = true;
509
String currArg = args[argNum];
510
if ("-verbose".equals(currArg)) {
511
uiDelegateAugmenter.setVerbose(true);
515
if ("-class".equals(currArg)) {
517
className = args[argNum];
521
if ("-before".equals(currArg)) {
529
File starter = new File(args[argNum]);
530
ContainerGhostingType igt = new ContainerGhostingType();
531
igt.setClassName(className);
532
igt.setToInjectAfterOriginal(isAfter);
533
uiDelegateAugmenter.process(starter.getAbsolutePath(), starter, Arrays