Maven cannot find Lombok's ExtensionMethod during compilation - maven

I'm using Eclipse and I have mulitmodule Maven project in which I use Lombok(1.16.4) Java=jdk1.7.0_71.
In Eclipse all my code compiles and JUnit testes pass. However in Maven (v. 3.2.3 and 3.3.3) code does not compile.
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] DetermineMarketDirection.java:[86,42] cannot find symbol
symbol: method sumValuesDouble(java.lang.String)
location: variable listToWorkOn of type java.util.List<T>
[INFO] 1 error
This sumValuesDouble is my Lombok's extension method for type List<> which is defined in module A. Error above occures in module C which depends on module A.
This is part of ExtensionList.java:
public static <T> Double sumValuesDouble(List<T> list, final String methodName) {
return sumValuesOf(list, methodName, Double.class);
}
private static <T, N extends Number> N sumValuesOf(List<T> list, final String methodName, Class<N> type) {
Function<T, N> transform = new Function<T, N>() {
#SuppressWarnings("unchecked")
#Override
#SneakyThrows
public N apply(T from) {
Method method = from.getClass().getMethod(methodName);
return (N) method.invoke(from);
}
};
List<N> projectedList = Lists.transform(list, transform);
return sumAll(projectedList);
}
It's very strange because I have tests in Module A for this extension method and they compile OK and pass.
Part of test from Module A:
#ExtensionMethod({ ExtensionList.class })
public class ExtensionListSumAllTest {
#Test
public void testDouble() {
List<ValueDouble> listDouble = new ArrayList<>();
listDouble.add(new ValueDouble(1, 1d));
listDouble.add(new ValueDouble(2, 2d));
listDouble.add(new ValueDouble(3, 3d));
Double actual = listDouble.sumValuesDouble("getValue");
final Double expected = 6d;
assertEquals(expected, actual);
}
#AllArgsConstructor
#Getter
public class ValueDouble {
private final Integer id;
private final Double value;
}
}
What can I do to make Maven "see" this method?
BTW: I use a lot of extension methods in module c and Maven has problem with only that one. Also in this project/eclipse I use Lombok for a long time and I didn't had such problem before.
UPDATE:
I played a little bit more and I found out, that when I add 'dummy' code before execution of Extension method maven compile code wihtout problems.
So, this does not compile
#Slf4j
#ExtensionMethod({ ExtensionList.class })
public class SmaCalculator {
static <C extends Candle> double calculateSMA(List<C> candles, int sma) {
List<C> listToWorkOn = prepareListForSmaCalculation(candles, sma);
Double sum = listToWorkOn.sumValuesOf("getClosePrice");
Double smaValue = sum / sma;
return smaValue;
}
...other methods...
}
But this compiles SUCCESSFULLY by Maven, and dupa object does not even referes to listToWorkOn object:
static <C extends Candle> double calculateSMA(List<C> candles, int sma) {
List<C> listToWorkOn = prepareListForSmaCalculation(candles, sma);
List<C> dupa = new ArrayList<C>();
dupa.add(listToWorkOn.first());
dupa.sumValuesOf("getClosePrice");
Double sum = listToWorkOn.sumValuesOf("getClosePrice");
Double smaValue = sum / sma;
return smaValue;
}

I had the same problem, after switching back to 1.16.2 and it seems to work.

Related

Java8 to Java7 - Migrate Comparators

I'm having troubles understanding how to "migrate" a simple Comparator in Java7.
The actual version I'm using in Java8 is like:
private static final Comparator<Entry> ENTRY_COMPARATOR = Comparator.comparing(new Function<Entry, EntryType>() {
#Override
public EntryType apply(Entry t) {
return t.type;
}
})
.thenComparing(Comparator.comparingLong(new ToLongFunction<Entry>() {
#Override
public long applyAsLong(Entry value) {
return value.count;
}
}).reversed());
But in build phase I get this error:
static interface method invocations are not supported in -source 7
How can I migrate the same comparator to Java7? I'm googling and searching for solution but the only thing I can think of, is to implement my own class as a Comparator interface implementation.
But If I go down that road, how can I apply both "comparing", "then comparing" and "reverse" in the same "compare" method?
Thanks in advance
Even your java-8 version can be made a lot shorter and easier to read with:
Comparator.comparing(Entry::getType)
.thenComparingLong(Entry::getCount)
.reversed();
With guava (java-7 compatible), this looks a bit more verbose:
#Override
public int compare(Entry left, Entry right) {
return ComparisonChain.start()
.compare(left.getType(), right.getCount(), Ordering.natural().reversed())
.compare(left.getCount(), right.getCount(), Ordering.natural().reversed())
.result();
}
You can write the logic in a single compare method:
public int compare (Entry one,Entry two) {
int result = two.getType().compareTo(one.getType());
if (result == 0) {
result = Long.compare(two.getCount(),one.getCount());
}
return result;
}
Note that the reversed order in achieved by swapping the order of the compared Entry instances.
You can construct a Comparator<Entry> the java 7 way, afterward, you can chain the default methods as you can do in java 8, but without using lambda expressions or method references as a parameter :
private static final Comparator<Entry> ENTRY_COMPARATOR = new Comparator<Entry>() {
#Override
public int compare(Entry left, Entry right) {
return left.type.compareTo(right.type);
}
}
.thenComparingLong(new ToLongFunction<Entry>() {
#Override
public long applyAsLong(Entry entry) {
return entry.value;
}
})
.reversed();
The code above is compiled with -source 1.7.

//NonCompliant comment usage - SonarQube Custom Rule

I am trying to write a few SONARQUBE custom rules for my project.
After reading up the below document -
https://docs.sonarqube.org/display/PLUG/Writing+Custom+Java+Rules+101
and
https://github.com/SonarSource/sonar-custom-rules-examples,
I created a custom rule like these classes below -
The Rule file:
#Rule(key = "MyAssertionRule")
public class FirstSonarCustomRule extends BaseTreeVisitor implements JavaFileScanner {
private static final String DEFAULT_VALUE = "Inject";
private JavaFileScannerContext context;
/**
* Name of the annotation to avoid. Value can be set by users in Quality
* profiles. The key
*/
#RuleProperty(defaultValue = DEFAULT_VALUE, description = "Name of the annotation to avoid, without the prefix #, for instance 'Override'")
protected String name;
#Override
public void scanFile(JavaFileScannerContext context) {
this.context = context;
System.out.println(PrinterVisitor.print(context.getTree()));
scan(context.getTree());
}
#Override
public void visitMethod(MethodTree tree) {
List<StatementTree> statements = tree.block().body();
for (StatementTree statement : statements) {
System.out.println("KIND IS " + statement.kind());
if (statement.is(Kind.EXPRESSION_STATEMENT)) {
if (statement.firstToken().text().equals("Assert")) {
System.out.println("ERROR");
}
}
}
}
}
The Test class:
public class FirstSonarCustomRuleTest {
#Test
public void verify() {
FirstSonarCustomRule f = new FirstSonarCustomRule();
f.name = "ASSERTION";
JavaCheckVerifier.verify("src/test/files/FirstSonarCustom.java", f);
}
}
And finally - the Test file:
class FirstSonarCustom {
int aField;
public void methodToUseTestNgAssertions() {
Assert.assertTrue(true);
}
}
The above Test file would later be my Project's source code.
As per the SONAR documentation - the // Noncompliant is a mandatory comment in my Test file. Thus my first question is should I add this comment everywhere in my Source code too?
If yes - is there any way I can avoid adding this comment, because I do not want to add that code refactoring exercise all over.
Can someone suggest me what I need to do here?
I am using SONARQUBE 6.3.
This comment is only used by the test framework (JavaCheckVerifier class) to test the implementation of your rule. It is not mandatory in any way and for sure you don't need it in your real code.

Using map of maps as Maven plugin parameters

Is it possible to use a map of maps as a Maven plugin parameter?, e.g.
#Parameter
private Map<String, Map<String, String>> converters;
and then to use it like
<converters>
<json>
<indent>true</indent>
<strict>true</strict>
</json>
<yaml>
<stripComments>false</stripComments>
</yaml>
<converters>
If I use it like this, converters only contain the keys json and yaml with null as values.
I know it is possible to have complex objects as values, but is it also somehow possible to use maps for variable element values like in this example?
This is apparently a limitation of the sisu.plexus project internally used by the Mojo API. If you peek inside the MapConverter source, you'll find out that it first tries to fetch the value of the map by trying to interpret the configuration as a String (invoking fromExpression), and when this fails, looks up the expected type of the value. However this method doesn't check for parameterized types, which is our case here (since the type of the map value is Map<String, String>). I filed the bug 498757 on the Bugzilla of this project to track this.
Using a custom wrapper object
One workaround would be to not use a Map<String, String> as value but use a custom object:
#Parameter
private Map<String, Converter> converters;
with a class Converter, located in the same package as the Mojo, being:
public class Converter {
#Parameter
private Map<String, String> properties;
#Override
public String toString() { return properties.toString(); } // to test
}
You can then configure your Mojo with:
<converters>
<json>
<properties>
<indent>true</indent>
<strict>true</strict>
</properties>
</json>
<yaml>
<properties>
<stripComments>false</stripComments>
</properties>
</yaml>
</converters>
This configuration will correctly inject the values in the inner-maps. It also keeps the variable aspect: the object is only introduced as a wrapper around the inner-map. I tested this with a simple test mojo having
public void execute() throws MojoExecutionException, MojoFailureException {
getLog().info(converters.toString());
}
and the output was the expected {json={indent=true, strict=true}, yaml={stripComments=false}}.
Using a custom configurator
I also found a way to keep a Map<String, Map<String, String>> by using a custom ComponentConfigurator.
So we want to fix MapConverter by inhering it, the trouble is how to register this new FixedMapConverter. By default, Maven uses a BasicComponentConfigurator to configure the Mojo and it relies on a DefaultConverterLookup to look-up for converters to use for a specific class. In this case, we want to provide a custom converted for Map that will return our fixed version. Therefore, we need to extend this basic configurator and register our new converter.
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.codehaus.plexus.component.configurator.BasicComponentConfigurator;
import org.codehaus.plexus.component.configurator.ComponentConfigurationException;
import org.codehaus.plexus.component.configurator.ConfigurationListener;
import org.codehaus.plexus.component.configurator.expression.ExpressionEvaluator;
import org.codehaus.plexus.configuration.PlexusConfiguration;
public class CustomBasicComponentConfigurator extends BasicComponentConfigurator {
#Override
public void configureComponent(final Object component, final PlexusConfiguration configuration,
final ExpressionEvaluator evaluator, final ClassRealm realm, final ConfigurationListener listener)
throws ComponentConfigurationException {
converterLookup.registerConverter(new FixedMapConverter());
super.configureComponent(component, configuration, evaluator, realm, listener);
}
}
Then we need to tell Maven to use this new configurator instead of the basic one. This is a 2-step process:
Inside your Maven plugin, create a file src/main/resources/META-INF/plexus/components.xml registering the new component:
<?xml version="1.0" encoding="UTF-8"?>
<component-set>
<components>
<component>
<role>org.codehaus.plexus.component.configurator.ComponentConfigurator</role>
<role-hint>custom-basic</role-hint>
<implementation>package.to.CustomBasicComponentConfigurator</implementation>
</component>
</components>
</component-set>
Note a few things: we declare a new component having the hint "custom-basic", this will serve as an id to refer to it and the <implementation> refers to the fully qualified class name of our configurator.
Tell our Mojo to use this configurator with the configurator attribute of the #Mojo annotation:
#Mojo(name = "test", configurator = "custom-basic")
The configurator passed here corresponds to the role-hint specified in the components.xml above.
With such a set-up, you can finally declare
#Parameter
private Map<String, Map<String, String>> converters;
and everything will be injected properly: Maven will use our custom configurator, that will register our fixed version of the map converter and will correctly convert the inner-maps.
Full code of FixedMapConverter (which pretty much copy-pastes MapConverter because we can't override the faulty method):
public class FixedMapConverter extends MapConverter {
public Object fromConfiguration(final ConverterLookup lookup, final PlexusConfiguration configuration,
final Class<?> type, final Type[] typeArguments, final Class<?> enclosingType, final ClassLoader loader,
final ExpressionEvaluator evaluator, final ConfigurationListener listener)
throws ComponentConfigurationException {
final Object value = fromExpression(configuration, evaluator, type);
if (null != value) {
return value;
}
try {
final Map<Object, Object> map = instantiateMap(configuration, type, loader);
final Class<?> elementType = findElementType(typeArguments);
if (Object.class == elementType || String.class == elementType) {
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
final PlexusConfiguration element = configuration.getChild(i);
map.put(element.getName(), fromExpression(element, evaluator));
}
return map;
}
// handle maps with complex element types...
final ConfigurationConverter converter = lookup.lookupConverterForType(elementType);
for (int i = 0, size = configuration.getChildCount(); i < size; i++) {
Object elementValue;
final PlexusConfiguration element = configuration.getChild(i);
try {
elementValue = converter.fromConfiguration(lookup, element, elementType, enclosingType, //
loader, evaluator, listener);
}
// TEMP: remove when http://jira.codehaus.org/browse/MSHADE-168
// is fixed
catch (final ComponentConfigurationException e) {
elementValue = fromExpression(element, evaluator);
Logs.warn("Map in " + enclosingType + " declares value type as: {} but saw: {} at runtime",
elementType, null != elementValue ? elementValue.getClass() : null);
}
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
map.put(element.getName(), elementValue);
}
return map;
} catch (final ComponentConfigurationException e) {
if (null == e.getFailedConfiguration()) {
e.setFailedConfiguration(configuration);
}
throw e;
}
}
#SuppressWarnings("unchecked")
private Map<Object, Object> instantiateMap(final PlexusConfiguration configuration, final Class<?> type,
final ClassLoader loader) throws ComponentConfigurationException {
final Class<?> implType = getClassForImplementationHint(type, configuration, loader);
if (null == implType || Modifier.isAbstract(implType.getModifiers())) {
return new TreeMap<Object, Object>();
}
final Object impl = instantiateObject(implType);
failIfNotTypeCompatible(impl, type, configuration);
return (Map<Object, Object>) impl;
}
private static Class<?> findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 1 )
{
if ( typeArguments[1] instanceof Class<?> )
{
return (Class<?>) typeArguments[1];
}
// begin fix here
if ( typeArguments[1] instanceof ParameterizedType )
{
return (Class<?>) ((ParameterizedType) typeArguments[1]).getRawType();
}
// end fix here
}
return Object.class;
}
}
One solution is quite simple and works for 1-level nesting. A more sophisticated approach can be found in the alternative answer which possibly also allows for deeper nesting of Maps.
Instead of using an interface as type parameter, simply use a concrete class like TreeMap
#Parameter
private Map<String, TreeMap> converters.
The reason is this check in MapConverter which fails for an interface but suceeds for a concrete class:
private static Class<?> findElementType( final Type[] typeArguments )
{
if ( null != typeArguments && typeArguments.length > 1
&& typeArguments[1] instanceof Class<?> )
{
return (Class<?>) typeArguments[1];
}
return Object.class;
}
As a side-note, an as it is also related to this answer for Maven > 3.3.x it also works to install a custom converter by subclassing BasicComponentConfigurator and using it as a Plexus component. BasicComponentConfigurator has the DefaultConverterLookup as a protected member variable and is hence easily accessible for registering custom converters.

How do you hash a string in a PCL project?

We've tried using this library: http://msdn.microsoft.com/en-us/library/system.security.cryptography.hashalgorithm(v=vs.110).aspx
And this code:
public static byte[] GetHash(string inputString)
{
HashAlgorithm algorithm = SHA1.Create(); // SHA1.Create()
return algorithm.ComputeHash(Encoding.UTF8.GetBytes(inputString));
}
public static string GetHashString(string inputString)
{
StringBuilder sb = new StringBuilder();
foreach (byte b in GetHash(inputString))
sb.Append(b.ToString("X2"));
return sb.ToString();
}
But the library doesn't seem to be available.
In case certain API is not available in PCL, you would normally create an interface of and inject it in the constructor.
In your example, it would be something like this
PCL library project
public interface IHashService
{
byte[] ComputeHash(byte[] data)
}
Platform specific project
public class Sha1HashService : IHashService
{
public ComputeHash(byte[] data)
{
using(var algorithm = SHA1.Create())
{
var result = algorithm.ComputeHash(data);
return result;
}
}
}
It is good practice not to use static methods and use a dependency injection whenever possible.
Also you probably want your interface to be more generic (takes bytes as an argument) rather than a string, for the very same reason (dependency on the Encoding.UTF8.GetBytes).

how do I track metrics in jmeter for 'java requests' with sub results?

I am using jmeter with Java Request samplers. These call java classes I have written which returns a SampleResult object which contains the timing metrics for the use case. SampleResult is a tree and can have child SampleResult objects (SampleResult.addSubResult method). I cant seem to find a good way in jmeter to track the sub results so I can only easily get the results for the parent SampleResult.
Is there a listener in jmeter that allows me to see statistics / graphs for sub results (for instance see the average time across all sub results with the same name).
I have just succeeded in doing this, and wanted to share it. If you follow the instructions I provide here, it will work for you as well. I did this for the summary table listener. And, I did it on Windows. And, I used Eclipse
Steps:
Go to JMeter's web site and download the source code. You can find that here, for version 3.0.
http://jmeter.apache.org/download_jmeter.cgi
One there, I clicked the option to download the Zip file for the Source.
Then, on that same page, download the binary for version 3.0, if you have not already done so. Then, extract that zip file onto your hard drive.
Once you've extracted the zip file to your hard drive, grab the file "SummaryReport.java". It can be found here: "\apache-jmeter-3.0\src\components\org\apache\jmeter\visualizers\SummaryReport.java"
Create a new class in Eclipse, then Copy/Paste all of that code into your new class. Then, rename your class from what it is, "SummaryReport" to a different name. And everywhere in the code, replace "SummaryReport" with the new name of your class.
I am using Java 8. So, there is one line of code that won't compile for me. It's the line below.
private final Map tableRows = new ConcurrentHashMap<>();
You need to remove the <> on that line, as Java 1.8 doesn't support it. Then, it will compile
There was one more line that gave a compile error. It was the one below.
CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, FORMATS),writer,`
saveHeaders.isSelected() ? StatGraphVisualizer.getLabels(COLUMNS) : null);
Firstly, it wasn't finding the source for class StatGraphVisualizer. So, I imported it, as below.
import org.apache.jmeter.visualizers.StatGraphVisualizer;
Secondly, it wasn't finding the method "getLabels" in "StatGraphVisualizer.getLabels." So, here is what this line of code looked like after I fixed it. It is seen below.
CSVSaveService.saveCSVStats(StatGraphVisualizer.getAllTableData(model, FORMATS),writer);
That compiles. That method doesn't need the second argument.
Now, everything should compile.
Find this method below. This is where you will begin adding your customizations.
#Override
public void add(final SampleResult res) {
You need to create an array of all of your sub results, as I did, as seen below. The line in Bold is the new code. (All new code is seen in Bold).
public void add(final SampleResult res) {
final String sampleLabel = res.getSampleLabel(); // useGroupName.isSelected());
**final SampleResult[] theSubResults = res.getSubResults();**
Then, create a String for each label for your sub results objects, as seen below.
**final String writesampleLabel = theSubResults[0].getSampleLabel(); // (useGroupName.isSelected());
final String readsampleLabel = theSubResults[1].getSampleLabel(); // (useGroupName.isSelected());**
Next, go to the method below.
JMeterUtils.runSafe(false, new Runnable() {
#Override
public void run() {
The new code added is below, in Bold.
JMeterUtils.runSafe(false, new Runnable() {
#Override
public void run() {
Calculator row = null;
**Calculator row1 = null;
Calculator row2 = null;**
synchronized (lock) {
row = tableRows.get(sampleLabel);
**row1 = tableRows.get(writesampleLabel);
row2 = tableRows.get(readsampleLabel);**
if (row == null) {
row = new Calculator(sampleLabel);
tableRows.put(row.getLabel(), row);
model.insertRow(row, model.getRowCount() - 1);
}
**if (row1 == null) {
row1 = new Calculator(writesampleLabel);
tableRows.put(row1.getLabel(), row1);
model.insertRow(row1, model.getRowCount() - 1);
}
if (row2 == null) {
row2 = new Calculator(readsampleLabel);
tableRows.put(row2.getLabel(), row2);
model.insertRow(row2, model.getRowCount() - 1);
}**
} // close lock
/*
* Synch is needed because multiple threads can update the counts.
*/
synchronized(row) {
row.addSample(res);
}
**synchronized(row1) {
row1.addSample(theSubResults[0]);
}**
**synchronized(row2) {
row2.addSample(theSubResults[1]);
}**
That is all that needs to be customized.
In Eclipse, export your new class into a Jar file. Then place it inside of the lib/ext folder of your binary of Jmeter that you extracted, from Step 1 above.
Start up Jmeter, as you normally would.
In your Java sampler, add a new Listener. You will now see two "Summary Table" listeners. One of these will be the new one that you have just created. Once you have brought that new one into your Java Sampler, rename it to something unique. Then run your test and look at your new "Summary Table" listener. You will see summary results/stats for all of your sample results.
My next step is to perform these same steps for all of the other Listeners that I would like to customize.
I hope that this post helps.
Here is some of my plugin code which you can use as a starting point in writing your own plugin. I cant really post everything as there are really dozens of classes. Few things to know are:
my plugin like all visualizer plugins extends the jmeter class
AbstractVisualizer
you need the following jars in eclipse to complile:
jfxrt.jar,ApacheJMeter_core.jar
you need java 1.8 for javafx (the jar file comes in the sdk)
if you compile a plugin you need to put that in jmeter/lib/ext.
You also need to put the jars from bullet 2 in jmeter/lib
there is a method called "add(SampleResult)" in my class. This
will get called by the jmeter framework every time a java sample
completes and will pass the SampleResult as a parameter. Assuming you
have your own Java Sample classes that extend
AbstractJavaSamplerClient your class will have a method called
runTest which returns a sampleresult. That same return object will be
passed into your plugins add method.
my plugin puts all the sample results into a buffer and only
updates the screen every 5 results.
Here is the code:
import java.awt.BorderLayout;
import java.util.ArrayList;
import java.util.List;
import javafx.application.Platform;
import javafx.embed.swing.JFXPanel;
import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.testelement.TestStateListener;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
public class FxVisualizer extends AbstractVisualizer implements TestStateListener {
int currentId = 0;
/**
*
*/
private static final long serialVersionUID = 1L;
private static final int BUFFER_SIZE = 5;
#Override
public String getName()
{
return super.getName();//"George's sub result viewer.";
}
#Override
public String getStaticLabel()
{
return "Georges FX Visualizer";
}
#Override
public String getComment()
{
return "George wrote this plugin. There are many plugins like it but this one is mine.";
}
static Long initCount = new Long(0);
public FxVisualizer()
{
init();
}
private void init()
{
//LoggingUtil.debug("in FxVisualizer init()");
try
{
FxTestListener.setListener(this);
this.setLayout(new BorderLayout());
Border margin = new EmptyBorder(10, 10, 5, 10);
this.setBorder(margin);
//this.add(makeTitlePanel(), BorderLayout.NORTH);
final JFXPanel fxPanel = new JFXPanel();
add(fxPanel);
//fxPanel.setScene(getScene());
Platform.runLater(new Runnable() {
#Override
public void run() {
initFX(fxPanel);
}
});
}
catch(Exception e)
{
e.printStackTrace();
}
}
static FxVisualizerScene fxScene;
private static void initFX(JFXPanel fxPanel) {
// This method is invoked on the JavaFX thread
fxScene = new FxVisualizerScene();
fxPanel.setScene(fxScene.getScene());
}
final List <Event> bufferedEvents = new ArrayList<Event>();
#Override
public void add(SampleResult result)
{
final List <Event> events = ...;//here you need to take the result.getSubResults() parameter and get all the children events.
final List<Event> eventsToAdd = new ArrayList<Event>();
synchronized(bufferedEvents)
{
for (Event evt : events)
{
bufferedEvents.add(evt);
}
if (bufferedEvents.size() >= BUFFER_SIZE)
{
eventsToAdd.addAll(bufferedEvents);
bufferedEvents.clear();
}
}
if (eventsToAdd.size() > 0)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(eventsToAdd);
}
});
}
}
public void updatePanel(List <Event> events )
{
for (Event evt: events)
{
fxScene.addEvent(evt);
}
}
#Override
public void clearData()
{
synchronized(bufferedEvents)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
bufferedEvents.clear();
fxScene.clearData();
}
});
}
}
#Override
public String getLabelResource() {
return "Georges Java Sub FX Sample Listener";
}
Boolean isRunning = false;
#Override
public void testEnded()
{
final List<Event> eventsToAdd = new ArrayList<Event>();
synchronized(bufferedEvents)
{
eventsToAdd.addAll(bufferedEvents);
bufferedEvents.clear();
}
if (eventsToAdd.size() > 0)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(eventsToAdd);
fxScene.testStopped();
}
});
}
}
Long testCount = new Long(0);
#Override
public void testStarted() {
synchronized(bufferedEvents)
{
Platform.runLater(new Runnable() {
#Override
public void run() {
updatePanel(bufferedEvents);
bufferedEvents.clear();
fxScene.testStarted();
}
});
}
}
#Override
public void testEnded(String arg0)
{
//LoggingUtil.debug("testEnded 2:" + arg0);
testEnded();
}
int registeredCount = 0;
#Override
public void testStarted(String arg0) {
//LoggingUtil.debug("testStarted 2:" + arg0);
testStarted();
}
}
OK so I just decided to write my own jmeter plugin and it is dead simple. Ill share the code for posterity when it is complete. Just write a class that extends AbstractVisualizer, compile it into a jar, then throw it into the jmeter lib/ext directory. That plugin will show up in the listeners section of jmeter when you go to add visualizers.

Resources