Skip to content

Commit a4a13a7

Browse files
authored
Merge pull request #6 from Toparvion/#5-composite-droplets
#5 Implemented support of composite droplets
2 parents 85c249a + bad9f98 commit a4a13a7

File tree

10 files changed

+63
-191
lines changed

10 files changed

+63
-191
lines changed

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ java -javaagent:$JMINT=$DROPLETS com.example.coolapp.Main
176176
Started with such arguments JVM will launch jMint and let it modify byte code of classes being loaded.
177177
:warning: *Note that being unable to load an agent JVM will not start at all.*
178178
:information_source: *`javaagent` is not singleton option for JVM. You may add as many agents as you want declaring them as separate `javaagent` arguments on the JVM launch command.*
179-
To ensure that your target methods have been modified correctly look for messages from class `tech.toparvion.jmint.TargetsTransformer` in the log (see _Logging_ section).
179+
To ensure that your target methods have been modified correctly look for messages from class `tech.toparvion.jmint.DropletsInjector` in the log (see _Logging_ section).
180180
181181
# Limitations
182182
Unfortunately, source code of droplets' methods (the modifying code) can not be as rich and diverse as usual one.
@@ -206,9 +206,9 @@ Here's some sample messages emitted by jMint when `slf4j-simple` binding is pres
206206
...
207207
[main] INFO tech.toparvion.jmint.JMintAgent - Droplets loading took: 1167 ms
208208
... (later, at runtime) ...
209-
[main] INFO tech.toparvion.jmint.TargetsTransformer - Method 'sampleapp.standalone.painter.Painter.buildContent()' has been modified at AFTER cutpoint.
209+
[main] INFO tech.toparvion.jmint.DropletsInjector - Method 'sampleapp.standalone.painter.Painter.buildContent()' has been modified at AFTER.
210210
...
211-
[main] INFO tech.toparvion.jmint.TargetsTransformer - Method 'sampleapp.standalone.painter.Painter#main' is skipped due to IGNORE cutpoint.
211+
[main] INFO tech.toparvion.jmint.DropletsInjector - Method 'sampleapp.standalone.painter.Painter#main' is skipped due to IGNORE.
212212
```
213213
214214
# Under the hood

build.gradle

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
group 'toparvion'
2-
version '1.2'
2+
version '1.3'
33

44
apply plugin: 'java'
55

@@ -21,7 +21,6 @@ dependencies {
2121
compile group: 'org.antlr', name: 'antlr4-runtime', version: '4.5.3'
2222
testCompile group: 'junit', name: 'junit', version: '4.11'
2323
testRuntime group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.7'
24-
2524
}
2625

2726
jar {
@@ -40,14 +39,12 @@ jar {
4039
}
4140

4241
task runPainterWithDebug(type: JavaExec, dependsOn: jar) {
43-
classpath = sourceSets.test.runtimeClasspath /*+
44-
project.files('src/test/java/sampleapp/standalone/painter/slf4j-simple-1.7.7.jar')*/
42+
classpath = sourceSets.test.runtimeClasspath
43+
/* + project.files('src/test/java/sampleapp/standalone/painter/slf4j-simple-1.7.7.jar')*/
4544
main 'sampleapp.standalone.painter.Painter'
4645
jvmArgs = ['-Dfile.encoding=UTF8',
4746
'-javaagent:build/libs/jmint-'+version+'.jar=' +
48-
'src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java;' +
49-
'src/test/java/sampleapp/standalone/painter/PainterDroplet.java;' +
50-
'src/test/resources/JFrameDroplet.java'
47+
'src/test/java/sampleapp/standalone/painter/composite-droplet.zip'
5148
].toList()
5249
debug = true // Gradle default debug port is 5005
5350
}

src/main/java/tech/toparvion/jmint/TargetsTransformer.java renamed to src/main/java/tech/toparvion/jmint/DropletsInjector.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@
2020
/**
2121
* Created by Toparvion on 29.04.2016 12:50
2222
*/
23-
class TargetsTransformer implements ClassFileTransformer {
24-
private static final Logger log = LoggerFactory.getLogger(TargetsTransformer.class);
23+
class DropletsInjector implements ClassFileTransformer {
24+
private static final Logger log = LoggerFactory.getLogger(DropletsInjector.class);
2525

2626
/**
2727
* The package that is implicitly imported into every Javassist ClassPool instance and therefore should be considered
@@ -34,7 +34,7 @@ class TargetsTransformer implements ClassFileTransformer {
3434
private final Set<ClassLoader> knownLoaders = new HashSet<ClassLoader>();
3535
private final Set<String> knownPackages = new HashSet<String>();
3636

37-
TargetsTransformer(TargetsMap targetsMap) {
37+
DropletsInjector(TargetsMap targetsMap) {
3838
this.targetsMap = targetsMap;
3939
this.pool = ClassPool.getDefault();
4040
// setup Javassist to dump all modified classes into directory specified via JVM option (if any)
@@ -106,7 +106,7 @@ public byte[] transform(ClassLoader loader,
106106
Cutpoint cutpoint = targetMethod.getCutpoint();
107107
MethodModifier modifier = cutpoint.getType().getModifier();
108108
modifier.apply(targetMethod.getText(), ctMethod, cutpoint.getAuxParams());
109-
log.info("Method '{}' has been modified at {} cutpoint.", ctMethod.getLongName(), cutpoint);
109+
log.info("Method '{}' has been modified at {}.", ctMethod.getLongName(), cutpoint);
110110

111111
} catch (Exception e) {
112112
log.error(format("Failed to modify target method '%s#%s'. Skipped.",

src/main/java/tech/toparvion/jmint/JMintAgent.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ public static void premain(String agentArgs, Instrumentation inst) {
2828
if (targetsMap.isEmpty()) {
2929
log.warn("No droplets to apply left after arguments processing. No byte code will be modified.");
3030
} else {
31-
inst.addTransformer(new TargetsTransformer(targetsMap));
31+
inst.addTransformer(new DropletsInjector(targetsMap));
3232
}
3333
}
3434

src/main/java/tech/toparvion/jmint/lang/DropletLoader.java

Lines changed: 50 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
package tech.toparvion.jmint.lang;
22

3-
import org.antlr.v4.runtime.ANTLRFileStream;
4-
import org.antlr.v4.runtime.BufferedTokenStream;
5-
import org.antlr.v4.runtime.CommonTokenStream;
6-
import org.antlr.v4.runtime.RecognitionException;
3+
import org.antlr.v4.runtime.*;
74
import org.antlr.v4.runtime.tree.ParseTree;
85
import org.antlr.v4.runtime.tree.ParseTreeWalker;
96
import org.slf4j.Logger;
@@ -12,7 +9,9 @@
129
import tech.toparvion.jmint.lang.gen.DroppingJavaParser;
1310
import tech.toparvion.jmint.model.TargetsMap;
1411

15-
import java.io.IOException;
12+
import java.io.*;
13+
import java.util.zip.ZipEntry;
14+
import java.util.zip.ZipInputStream;
1615

1716
/**
1817
* @author Toparvion
@@ -43,23 +42,61 @@ public static TargetsMap loadDroplets(String args) {
4342
}
4443

4544
private static TargetsMap loadSingleDroplet(String dropletPath) throws IOException, RecognitionException {
46-
DropletAssembler assembler = parseAndGetAssembler(dropletPath);
47-
return assembler.getTargetsMap();
45+
if (dropletPath.toLowerCase().endsWith(".jar") || dropletPath.toLowerCase().endsWith(".zip")) {
46+
// the argument points to composite droplet so we should first extract its content from the archive
47+
TargetsMap compositeTargetMap;
48+
ZipInputStream zis = null;
49+
try {
50+
zis = new ZipInputStream(new FileInputStream(dropletPath));
51+
compositeTargetMap = new TargetsMap();
52+
ZipEntry nextEntry;
53+
while ((nextEntry = zis.getNextEntry()) != null) {
54+
if (!nextEntry.getName().toLowerCase().endsWith(".java")) continue; // including directories
55+
log.debug("Processing entry: {}", nextEntry.getName());
56+
compositeTargetMap.putAll(parseDroplet(new NotClosingReader(zis)));
57+
zis.closeEntry();
58+
}
59+
} finally {
60+
if (zis != null) zis.close();
61+
}
62+
return compositeTargetMap;
63+
}
64+
65+
// in case the argument is an ordinary file let's immediately pass it to ANTLR
66+
return parseDroplet(new FileReader(dropletPath));
4867
}
4968

50-
private static DropletAssembler parseAndGetAssembler(String dropletPath) throws IOException {
51-
ANTLRFileStream fileStream = new ANTLRFileStream(dropletPath);
52-
DroppingJavaLexer lexer = new DroppingJavaLexer(fileStream);
53-
BufferedTokenStream tokenStream = new CommonTokenStream(lexer);
54-
DroppingJavaParser parser = new DroppingJavaParser(tokenStream);
69+
private static TargetsMap parseDroplet(Reader dropletReader) throws IOException {
70+
CharStream charStream = new ANTLRInputStream(dropletReader);
71+
DroppingJavaLexer lexer = new DroppingJavaLexer(charStream);
72+
BufferedTokenStream tokenStream = new CommonTokenStream(lexer);
73+
DroppingJavaParser parser = new DroppingJavaParser(tokenStream);
5574
parser.removeErrorListeners();
5675
parser.addErrorListener(new UnderlineErrorListener());
5776
ParseTree tree = parser.compilationUnit();
5877

5978
DropletAssembler assembler = new DropletAssembler(tokenStream);
6079
ParseTreeWalker walker = new ParseTreeWalker();
6180
walker.walk(assembler, tree);
62-
return assembler;
81+
return assembler.getTargetsMap();
6382
}
6483

84+
/**
85+
* A dummy implementation of {@link InputStreamReader} with stubbed {@link #close()} method. This is a way to prevent
86+
* ANTLRInputStream from preliminary closing {@code ZipInputStream} during loading composite droplets.
87+
*/
88+
private static class NotClosingReader extends InputStreamReader {
89+
90+
NotClosingReader(InputStream in) {
91+
super(in);
92+
}
93+
94+
@Override
95+
public void close() throws IOException {
96+
/* Here we're deliberately NOPing close operation as it must (and actually will) be done
97+
on ZipEntry but not ZipInputStream. */
98+
}
99+
}
100+
101+
65102
}

src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java

Lines changed: 0 additions & 53 deletions
This file was deleted.

src/test/java/sampleapp/standalone/painter/Painter.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -357,7 +357,7 @@ public ActivateListener(JLabel pointCnt, JProgressBar prgs, String name, int poi
357357
this.points = points;
358358
}
359359
public void actionPerformed(ActionEvent event) {
360-
//<editor-fold desc="This section was added just for testing of AFTER cutpoint">
360+
//<editor-fold desc="This section was added just for testing AFTER cutpoint">
361361
if (tabbedPane.getTabCount() > 4) {
362362
throw new IllegalStateException("Too many tabs opened.");
363363
}

src/test/java/sampleapp/standalone/painter/PainterDroplet.java

Lines changed: 0 additions & 95 deletions
This file was deleted.
2.66 KB
Binary file not shown.

src/test/resources/JFrameDroplet.java

Lines changed: 0 additions & 14 deletions
This file was deleted.

0 commit comments

Comments
 (0)