diff --git a/README.md b/README.md index 35d13e3..0184878 100644 --- a/README.md +++ b/README.md @@ -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. @@ -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 diff --git a/build.gradle b/build.gradle index 698d4cb..d41a824 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ group 'toparvion' -version '1.2' +version '1.3' apply plugin: 'java' @@ -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 { @@ -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 } diff --git a/src/main/java/tech/toparvion/jmint/TargetsTransformer.java b/src/main/java/tech/toparvion/jmint/DropletsInjector.java similarity index 96% rename from src/main/java/tech/toparvion/jmint/TargetsTransformer.java rename to src/main/java/tech/toparvion/jmint/DropletsInjector.java index 4028add..f036aa3 100644 --- a/src/main/java/tech/toparvion/jmint/TargetsTransformer.java +++ b/src/main/java/tech/toparvion/jmint/DropletsInjector.java @@ -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 @@ -34,7 +34,7 @@ class TargetsTransformer implements ClassFileTransformer { private final Set knownLoaders = new HashSet(); private final Set knownPackages = new HashSet(); - 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) @@ -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.", diff --git a/src/main/java/tech/toparvion/jmint/JMintAgent.java b/src/main/java/tech/toparvion/jmint/JMintAgent.java index 8df6c29..ca85c4f 100644 --- a/src/main/java/tech/toparvion/jmint/JMintAgent.java +++ b/src/main/java/tech/toparvion/jmint/JMintAgent.java @@ -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)); } } diff --git a/src/main/java/tech/toparvion/jmint/lang/DropletLoader.java b/src/main/java/tech/toparvion/jmint/lang/DropletLoader.java index c1986a1..a0e99a3 100644 --- a/src/main/java/tech/toparvion/jmint/lang/DropletLoader.java +++ b/src/main/java/tech/toparvion/jmint/lang/DropletLoader.java @@ -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; @@ -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 @@ -43,15 +42,35 @@ 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(); @@ -59,7 +78,25 @@ private static DropletAssembler parseAndGetAssembler(String dropletPath) throws 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. */ + } + } + + } diff --git a/src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java b/src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java deleted file mode 100644 index aee30be..0000000 --- a/src/test/java/sampleapp/standalone/painter/DrawPaneDroplet.java +++ /dev/null @@ -1,53 +0,0 @@ -/** - * - */ -package sampleapp.standalone.painter; - -import javax.swing.*; -import java.awt.*; -import java.util.UUID; - -/** - * @author Toparvion - */ -public class DrawPaneDroplet extends JComponent /*implements Runnable*/ { - - public int dkmx, dkmy; - private int n; - private double t; - int xs[]; - int ys[]; - private float red; - private float green; - private float blue; - - /** - * - * @param indicator - * @param points - * @param parent - * @cutpoint BEFORE - */ - public DrawPaneDroplet(JProgressBar indicator, int points, Painter parent) { - System.out.println("UUID: " + UUID.randomUUID().toString()); - System.out.println("Parent component size is " + parent.getSize().toString()); - } - - /** - * - * @param g - * @cutpoint INSTEAD - */ - @Override - public void paintComponent(Graphics g) { - Graphics2D g2d = (Graphics2D) g; -// calculate(); - g2d.setBackground(Color.BLACK); - g2d.clearRect(0, 0, this.getWidth(), this.getHeight()); // удаление предыдущей фигуры - red = ((float) Math.sin(t * dkmx) + 1) / 2; - green = (float) (Math.sin(t + n) + 1) / 2; - blue = (float) (Math.cos(t + n) + 1) / 2; - g2d.setColor(new Color(red, green, blue)); // назначение цвета - g2d.drawPolygon(xs, ys, n); // построение фигуры - } -} diff --git a/src/test/java/sampleapp/standalone/painter/Painter.java b/src/test/java/sampleapp/standalone/painter/Painter.java index 915d8db..665a05a 100644 --- a/src/test/java/sampleapp/standalone/painter/Painter.java +++ b/src/test/java/sampleapp/standalone/painter/Painter.java @@ -357,7 +357,7 @@ public ActivateListener(JLabel pointCnt, JProgressBar prgs, String name, int poi this.points = points; } public void actionPerformed(ActionEvent event) { - // + // if (tabbedPane.getTabCount() > 4) { throw new IllegalStateException("Too many tabs opened."); } diff --git a/src/test/java/sampleapp/standalone/painter/PainterDroplet.java b/src/test/java/sampleapp/standalone/painter/PainterDroplet.java deleted file mode 100644 index 27c7bab..0000000 --- a/src/test/java/sampleapp/standalone/painter/PainterDroplet.java +++ /dev/null @@ -1,95 +0,0 @@ -/** - * Главный модуль программы; отвечает за инициализацию (в любом режиме) - * и построение/заполнение области программы. - */ -package sampleapp.standalone.painter; - -import javax.swing.*; -import java.awt.event.ActionEvent; -import java.awt.event.ActionListener; - -public class PainterDroplet extends JPanel { - - private JTextField statusField; - private JTabbedPane tabbedPane; - final private String[] names = {"Пинта", "Нинья", "Мария", "Санта", "Доминика"}; - final private int points[] = {80, 98, 130, 137, 200}; - - - /** - * @param args - * @cutpoint BEFORE - */ - public static void main(String[] args) { - System.out.println("Length of arguments list: " + args.length); - } - - /** - * @cutpoint AFTER - */ - private void buildContent() { - statusField.setText(statusField.getText() + " [Droplet is active ;) ]"); - } - - - /* private JPanel createBottomPane() - { - JPanel thePane = new JPanel(); - thePane.setBorder(BorderFactory.createTitledBorder("Управление фигурами")); - GridBagLayout gbl = new GridBagLayout(); - thePane.setLayout(gbl); - GridBagConstraints gbc = new GridBagConstraints(); - - gbc.weightx = 1; - gbc.weighty = 0; - gbc.anchor = GridBagConstraints.WEST; - // заголовок "таблицы" - JLabel next = new JLabel(" Состояние"); - thePane.add(next, gbc); - next = new JLabel("Сложность"); - thePane.add(next, gbc); - next = new JLabel("Время расчета"); - gbc.gridwidth = GridBagConstraints.REMAINDER; - thePane.add(next,gbc); - // тело "таблицы" - int i; - JCheckBox turner; - JLabel pointCnt; - JProgressBar prgs; - for(i=0; i