Skip to content

Commit

Permalink
Merge pull request #6 from Toparvion/#5-composite-droplets
Browse files Browse the repository at this point in the history
#5 Implemented support of composite droplets
  • Loading branch information
Toparvion authored Aug 20, 2016
2 parents 85c249a + bad9f98 commit a4a13a7
Show file tree
Hide file tree
Showing 10 changed files with 63 additions and 191 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -176,7 +176,7 @@ java -javaagent:$JMINT=$DROPLETS com.example.coolapp.Main
Started with such arguments JVM will launch jMint and let it modify byte code of classes being loaded.
:warning: *Note that being unable to load an agent JVM will not start at all.*
: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.*
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).
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).
# Limitations
Unfortunately, source code of droplets' methods (the modifying code) can not be as rich and diverse as usual one.
Expand Down Expand Up @@ -206,9 +206,9 @@ Here's some sample messages emitted by jMint when `slf4j-simple` binding is pres
...
[main] INFO tech.toparvion.jmint.JMintAgent - Droplets loading took: 1167 ms
... (later, at runtime) ...
[main] INFO tech.toparvion.jmint.TargetsTransformer - Method 'sampleapp.standalone.painter.Painter.buildContent()' has been modified at AFTER cutpoint.
[main] INFO tech.toparvion.jmint.DropletsInjector - Method 'sampleapp.standalone.painter.Painter.buildContent()' has been modified at AFTER.
...
[main] INFO tech.toparvion.jmint.TargetsTransformer - Method 'sampleapp.standalone.painter.Painter#main' is skipped due to IGNORE cutpoint.
[main] INFO tech.toparvion.jmint.DropletsInjector - Method 'sampleapp.standalone.painter.Painter#main' is skipped due to IGNORE.
```
# Under the hood
Expand Down
11 changes: 4 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
group 'toparvion'
version '1.2'
version '1.3'

apply plugin: 'java'

Expand All @@ -21,7 +21,6 @@ dependencies {
compile group: 'org.antlr', name: 'antlr4-runtime', version: '4.5.3'
testCompile group: 'junit', name: 'junit', version: '4.11'
testRuntime group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.7'

}

jar {
Expand All @@ -40,14 +39,12 @@ jar {
}

task runPainterWithDebug(type: JavaExec, dependsOn: jar) {
classpath = sourceSets.test.runtimeClasspath /*+
project.files('src/test/java/sampleapp/standalone/painter/slf4j-simple-1.7.7.jar')*/
classpath = sourceSets.test.runtimeClasspath
/* + project.files('src/test/java/sampleapp/standalone/painter/slf4j-simple-1.7.7.jar')*/
main 'sampleapp.standalone.painter.Painter'
jvmArgs = ['-Dfile.encoding=UTF8',
'-javaagent:build/libs/jmint-'+version+'.jar=' +
'src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java;' +
'src/test/java/sampleapp/standalone/painter/PainterDroplet.java;' +
'src/test/resources/JFrameDroplet.java'
'src/test/java/sampleapp/standalone/painter/composite-droplet.zip'
].toList()
debug = true // Gradle default debug port is 5005
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@
/**
* Created by Toparvion on 29.04.2016 12:50
*/
class TargetsTransformer implements ClassFileTransformer {
private static final Logger log = LoggerFactory.getLogger(TargetsTransformer.class);
class DropletsInjector implements ClassFileTransformer {
private static final Logger log = LoggerFactory.getLogger(DropletsInjector.class);

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

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

} catch (Exception e) {
log.error(format("Failed to modify target method '%s#%s'. Skipped.",
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/tech/toparvion/jmint/JMintAgent.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ public static void premain(String agentArgs, Instrumentation inst) {
if (targetsMap.isEmpty()) {
log.warn("No droplets to apply left after arguments processing. No byte code will be modified.");
} else {
inst.addTransformer(new TargetsTransformer(targetsMap));
inst.addTransformer(new DropletsInjector(targetsMap));
}
}

Expand Down
63 changes: 50 additions & 13 deletions src/main/java/tech/toparvion/jmint/lang/DropletLoader.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package tech.toparvion.jmint.lang;

import org.antlr.v4.runtime.ANTLRFileStream;
import org.antlr.v4.runtime.BufferedTokenStream;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.*;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.ParseTreeWalker;
import org.slf4j.Logger;
Expand All @@ -12,7 +9,9 @@
import tech.toparvion.jmint.lang.gen.DroppingJavaParser;
import tech.toparvion.jmint.model.TargetsMap;

import java.io.IOException;
import java.io.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

/**
* @author Toparvion
Expand Down Expand Up @@ -43,23 +42,61 @@ public static TargetsMap loadDroplets(String args) {
}

private static TargetsMap loadSingleDroplet(String dropletPath) throws IOException, RecognitionException {
DropletAssembler assembler = parseAndGetAssembler(dropletPath);
return assembler.getTargetsMap();
if (dropletPath.toLowerCase().endsWith(".jar") || dropletPath.toLowerCase().endsWith(".zip")) {
// the argument points to composite droplet so we should first extract its content from the archive
TargetsMap compositeTargetMap;
ZipInputStream zis = null;
try {
zis = new ZipInputStream(new FileInputStream(dropletPath));
compositeTargetMap = new TargetsMap();
ZipEntry nextEntry;
while ((nextEntry = zis.getNextEntry()) != null) {
if (!nextEntry.getName().toLowerCase().endsWith(".java")) continue; // including directories
log.debug("Processing entry: {}", nextEntry.getName());
compositeTargetMap.putAll(parseDroplet(new NotClosingReader(zis)));
zis.closeEntry();
}
} finally {
if (zis != null) zis.close();
}
return compositeTargetMap;
}

// in case the argument is an ordinary file let's immediately pass it to ANTLR
return parseDroplet(new FileReader(dropletPath));
}

private static DropletAssembler parseAndGetAssembler(String dropletPath) throws IOException {
ANTLRFileStream fileStream = new ANTLRFileStream(dropletPath);
DroppingJavaLexer lexer = new DroppingJavaLexer(fileStream);
BufferedTokenStream tokenStream = new CommonTokenStream(lexer);
DroppingJavaParser parser = new DroppingJavaParser(tokenStream);
private static TargetsMap parseDroplet(Reader dropletReader) throws IOException {
CharStream charStream = new ANTLRInputStream(dropletReader);
DroppingJavaLexer lexer = new DroppingJavaLexer(charStream);
BufferedTokenStream tokenStream = new CommonTokenStream(lexer);
DroppingJavaParser parser = new DroppingJavaParser(tokenStream);
parser.removeErrorListeners();
parser.addErrorListener(new UnderlineErrorListener());
ParseTree tree = parser.compilationUnit();

DropletAssembler assembler = new DropletAssembler(tokenStream);
ParseTreeWalker walker = new ParseTreeWalker();
walker.walk(assembler, tree);
return assembler;
return assembler.getTargetsMap();
}

/**
* A dummy implementation of {@link InputStreamReader} with stubbed {@link #close()} method. This is a way to prevent
* ANTLRInputStream from preliminary closing {@code ZipInputStream} during loading composite droplets.
*/
private static class NotClosingReader extends InputStreamReader {

NotClosingReader(InputStream in) {
super(in);
}

@Override
public void close() throws IOException {
/* Here we're deliberately NOPing close operation as it must (and actually will) be done
on ZipEntry but not ZipInputStream. */
}
}


}
53 changes: 0 additions & 53 deletions src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java

This file was deleted.

2 changes: 1 addition & 1 deletion src/test/java/sampleapp/standalone/painter/Painter.java
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,7 @@ public ActivateListener(JLabel pointCnt, JProgressBar prgs, String name, int poi
this.points = points;
}
public void actionPerformed(ActionEvent event) {
//<editor-fold desc="This section was added just for testing of AFTER cutpoint">
//<editor-fold desc="This section was added just for testing AFTER cutpoint">
if (tabbedPane.getTabCount() > 4) {
throw new IllegalStateException("Too many tabs opened.");
}
Expand Down
95 changes: 0 additions & 95 deletions src/test/java/sampleapp/standalone/painter/PainterDroplet.java

This file was deleted.

Binary file not shown.
14 changes: 0 additions & 14 deletions src/test/resources/JFrameDroplet.java

This file was deleted.

0 comments on commit a4a13a7

Please sign in to comment.