You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jmeter.apache.org by pm...@apache.org on 2017/01/14 13:18:06 UTC
svn commit: r1778767 - in /jmeter/trunk:
src/components/org/apache/jmeter/visualizers/
src/core/org/apache/jmeter/gui/util/ src/jorphan/org/apache/jorphan/gui/
test/src/org/apache/jorphan/gui/ xdocs/
Author: pmouawad
Date: Sat Jan 14 13:18:06 2017
New Revision: 1778767
URL: http://svn.apache.org/viewvc?rev=1778767&view=rev
Log:
Bug 52962 - Allow sorting by columns for View Results in Table, Summary Report, Aggregate Report and Aggregate Graph
Based on a contribution by Logan Mauzaize (logan.mauzaize at gmail.com) and Maxime Chassagneux
This closes github pr #245
Bugzilla Id: 52962
Added:
jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java (with props)
jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java (with props)
jmeter/trunk/test/src/org/apache/jorphan/gui/
jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java (with props)
jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java (with props)
jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java (with props)
Modified:
jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java
jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatVisualizer.java
jmeter/trunk/src/components/org/apache/jmeter/visualizers/SummaryReport.java
jmeter/trunk/src/components/org/apache/jmeter/visualizers/TableVisualizer.java
jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java
jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java
jmeter/trunk/xdocs/changes.xml
Modified: jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatGraphVisualizer.java Sat Jan 14 13:18:06 2017
@@ -69,7 +69,7 @@ import org.apache.jmeter.gui.action.Acti
import org.apache.jmeter.gui.action.SaveGraphics;
import org.apache.jmeter.gui.util.FileDialoger;
import org.apache.jmeter.gui.util.FilePanel;
-import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer;
+import org.apache.jmeter.gui.util.HeaderAsPropertyRendererWrapper;
import org.apache.jmeter.gui.util.VerticalPanel;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
@@ -80,6 +80,7 @@ import org.apache.jorphan.gui.GuiUtils;
import org.apache.jorphan.gui.JLabeledTextField;
import org.apache.jorphan.gui.NumberRenderer;
import org.apache.jorphan.gui.ObjectTableModel;
+import org.apache.jorphan.gui.ObjectTableSorter;
import org.apache.jorphan.gui.RateRenderer;
import org.apache.jorphan.gui.RendererUtils;
import org.apache.jorphan.logging.LoggingManager;
@@ -94,7 +95,7 @@ import org.apache.log.Logger;
*
*/
public class StatGraphVisualizer extends AbstractVisualizer implements Clearable, ActionListener {
- private static final long serialVersionUID = 240L;
+ private static final long serialVersionUID = 241L;
private static final String PCT1_LABEL = JMeterUtils.getPropDefault("aggregate_rpt_pct1", "90");
private static final String PCT2_LABEL = JMeterUtils.getPropDefault("aggregate_rpt_pct2", "95");
@@ -319,8 +320,8 @@ public class StatGraphVisualizer extends
new Functor("getSentKBPerSecond") }, //$NON-NLS-1$
new Functor[] { null, null, null, null, null, null, null, null, null, null, null, null, null },
new Class[] { String.class, Long.class, Long.class, Long.class, Long.class,
- Long.class, Long.class, Long.class, Long.class, String.class,
- String.class, String.class, String.class});
+ Long.class, Long.class, Long.class, Long.class, Double.class,
+ Double.class, Double.class, Double.class});
}
// Column formats
@@ -467,9 +468,10 @@ public class StatGraphVisualizer extends
mainPanel.add(makeTitlePanel());
myJTable = new JTable(model);
+ myJTable.setRowSorter(new ObjectTableSorter(model).fixLastRow());
JMeterUtils.applyHiDPI(myJTable);
// Fix centering of titles
- myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer(getColumnsMsgParameters()));
+ HeaderAsPropertyRendererWrapper.setupDefaultRenderer(myJTable, getColumnsMsgParameters());
myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70));
RendererUtils.applyRenderers(myJTable, getRenderers());
myScrollPane = new JScrollPane(myJTable);
@@ -503,6 +505,7 @@ public class StatGraphVisualizer extends
});
spane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
+ spane.setOneTouchExpandable(true);
spane.setLeftComponent(myScrollPane);
spane.setRightComponent(tabbedGraph);
spane.setResizeWeight(.2);
Modified: jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatVisualizer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatVisualizer.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatVisualizer.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/StatVisualizer.java Sat Jan 14 13:18:06 2017
@@ -40,7 +40,7 @@ import javax.swing.border.Border;
import javax.swing.border.EmptyBorder;
import org.apache.jmeter.gui.util.FileDialoger;
-import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer;
+import org.apache.jmeter.gui.util.HeaderAsPropertyRendererWrapper;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.save.CSVSaveService;
@@ -48,6 +48,7 @@ import org.apache.jmeter.testelement.Tes
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.gui.ObjectTableModel;
+import org.apache.jorphan.gui.ObjectTableSorter;
import org.apache.jorphan.gui.RendererUtils;
/**
@@ -59,7 +60,7 @@ import org.apache.jorphan.gui.RendererUt
*/
public class StatVisualizer extends AbstractVisualizer implements Clearable, ActionListener {
- private static final long serialVersionUID = 240L;
+ private static final long serialVersionUID = 241L;
private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$
@@ -172,8 +173,9 @@ public class StatVisualizer extends Abst
mainPanel.add(makeTitlePanel());
myJTable = new JTable(model);
+ myJTable.setRowSorter(new ObjectTableSorter(model).fixLastRow());
JMeterUtils.applyHiDPI(myJTable);
- myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer(StatGraphVisualizer.getColumnsMsgParameters()));
+ HeaderAsPropertyRendererWrapper.setupDefaultRenderer(myJTable, StatGraphVisualizer.getColumnsMsgParameters());
myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70));
RendererUtils.applyRenderers(myJTable, StatGraphVisualizer.getRenderers());
myScrollPane = new JScrollPane(myJTable);
@@ -219,4 +221,3 @@ public class StatVisualizer extends Abst
}
}
}
-
Modified: jmeter/trunk/src/components/org/apache/jmeter/visualizers/SummaryReport.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/SummaryReport.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/SummaryReport.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/SummaryReport.java Sat Jan 14 13:18:06 2017
@@ -43,7 +43,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.table.TableCellRenderer;
import org.apache.jmeter.gui.util.FileDialoger;
-import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer;
+import org.apache.jmeter.gui.util.HeaderAsPropertyRendererWrapper;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
import org.apache.jmeter.save.CSVSaveService;
@@ -53,6 +53,7 @@ import org.apache.jmeter.util.JMeterUtil
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.gui.NumberRenderer;
import org.apache.jorphan.gui.ObjectTableModel;
+import org.apache.jorphan.gui.ObjectTableSorter;
import org.apache.jorphan.gui.RateRenderer;
import org.apache.jorphan.gui.RendererUtils;
import org.apache.jorphan.reflect.Functor;
@@ -63,7 +64,7 @@ import org.apache.jorphan.reflect.Functo
*/
public class SummaryReport extends AbstractVisualizer implements Clearable, ActionListener {
- private static final long serialVersionUID = 240L;
+ private static final long serialVersionUID = 241L;
private static final String USE_GROUP_NAME = "useGroupName"; //$NON-NLS-1$
@@ -158,8 +159,8 @@ public class SummaryReport extends Abstr
new Functor("getAvgPageBytes"), //$NON-NLS-1$
},
new Functor[] { null, null, null, null, null, null, null, null , null, null, null },
- new Class[] { String.class, Long.class, Long.class, Long.class, Long.class,
- String.class, String.class, String.class, String.class, String.class, String.class });
+ new Class[] { String.class, Integer.class, Long.class, Long.class, Long.class,
+ Double.class, Double.class, Double.class, Double.class, Double.class, Double.class });
clearData();
init();
}
@@ -239,8 +240,9 @@ public class SummaryReport extends Abstr
mainPanel.add(makeTitlePanel());
myJTable = new JTable(model);
+ myJTable.setRowSorter(new ObjectTableSorter(model).fixLastRow());
JMeterUtils.applyHiDPI(myJTable);
- myJTable.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer());
+ HeaderAsPropertyRendererWrapper.setupDefaultRenderer(myJTable);
myJTable.setPreferredScrollableViewportSize(new Dimension(500, 70));
RendererUtils.applyRenderers(myJTable, RENDERERS);
myScrollPane = new JScrollPane(myJTable);
Modified: jmeter/trunk/src/components/org/apache/jmeter/visualizers/TableVisualizer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/components/org/apache/jmeter/visualizers/TableVisualizer.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/components/org/apache/jmeter/visualizers/TableVisualizer.java (original)
+++ jmeter/trunk/src/components/org/apache/jmeter/visualizers/TableVisualizer.java Sat Jan 14 13:18:06 2017
@@ -23,6 +23,7 @@ import java.awt.Color;
import java.awt.FlowLayout;
import java.text.Format;
import java.text.SimpleDateFormat;
+import java.util.Comparator;
import javax.swing.BorderFactory;
import javax.swing.ImageIcon;
@@ -37,7 +38,7 @@ import javax.swing.border.EmptyBorder;
import javax.swing.table.TableCellRenderer;
import org.apache.jmeter.JMeter;
-import org.apache.jmeter.gui.util.HeaderAsPropertyRenderer;
+import org.apache.jmeter.gui.util.HeaderAsPropertyRendererWrapper;
import org.apache.jmeter.gui.util.HorizontalPanel;
import org.apache.jmeter.samplers.Clearable;
import org.apache.jmeter.samplers.SampleResult;
@@ -45,6 +46,7 @@ import org.apache.jmeter.util.Calculator
import org.apache.jmeter.util.JMeterUtils;
import org.apache.jmeter.visualizers.gui.AbstractVisualizer;
import org.apache.jorphan.gui.ObjectTableModel;
+import org.apache.jorphan.gui.ObjectTableSorter;
import org.apache.jorphan.gui.RendererUtils;
import org.apache.jorphan.gui.RightAlignRenderer;
import org.apache.jorphan.gui.layout.VerticalLayout;
@@ -58,7 +60,7 @@ import org.apache.jorphan.reflect.Functo
*/
public class TableVisualizer extends AbstractVisualizer implements Clearable {
- private static final long serialVersionUID = 240L;
+ private static final long serialVersionUID = 241L;
private static final String ICON_SIZE = JMeterUtils.getPropDefault(JMeter.TREE_ICON_SIZE, JMeter.DEFAULT_TREE_ICON_SIZE);
@@ -185,10 +187,10 @@ public class TableVisualizer extends Abs
calc.addSample(res);
int count = calc.getCount();
TableSample newS = new TableSample(
- count,
- res.getSampleCount(),
- res.getStartTime(),
- res.getThreadName(),
+ count,
+ res.getSampleCount(),
+ res.getStartTime(),
+ res.getThreadName(),
res.getSampleLabel(),
res.getTime(),
res.isSuccessful(),
@@ -238,8 +240,22 @@ public class TableVisualizer extends Abs
// Set up the table itself
table = new JTable(model);
+ table.setRowSorter(new ObjectTableSorter(model).setValueComparator(5,
+ Comparator.nullsFirst(
+ (ImageIcon o1, ImageIcon o2) -> {
+ if (o1 == o2) {
+ return 0;
+ }
+ if (o1 == imageSuccess) {
+ return -1;
+ }
+ if (o1 == imageFailure) {
+ return 1;
+ }
+ throw new IllegalArgumentException("Only success and failure images can be compared");
+ })));
JMeterUtils.applyHiDPI(table);
- table.getTableHeader().setDefaultRenderer(new HeaderAsPropertyRenderer());
+ HeaderAsPropertyRendererWrapper.setupDefaultRenderer(table);
RendererUtils.applyRenderers(table, RENDERERS);
tableScrollPanel = new JScrollPane(table);
Modified: jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java (original)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRenderer.java Sat Jan 14 13:18:06 2017
@@ -78,6 +78,19 @@ public class HeaderAsPropertyRenderer ex
* @return the text
*/
protected String getText(Object value, int row, int column) {
+ return getText(value, row, column, columnsMsgParameters);
+ }
+
+ /**
+ * Get the text for the value as the translation of the resource name.
+ *
+ * @param value value for which to get the translation
+ * @param column index which column message parameters should be used
+ * @param row not used
+ * @param columnsMsgParameters
+ * @return the text
+ */
+ static String getText(Object value, int row, int column, Object[][] columnsMsgParameters) {
if (value == null){
return "";
}
Added: jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java?rev=1778767&view=auto
==============================================================================
--- jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java (added)
+++ jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java Sat Jan 14 13:18:06 2017
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package org.apache.jmeter.gui.util;
+
+import java.awt.Component;
+import java.io.Serializable;
+
+import javax.swing.JTable;
+import javax.swing.SwingConstants;
+import javax.swing.UIManager;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.JTableHeader;
+import javax.swing.table.TableCellRenderer;
+
+/**
+ * Wraps {@link TableCellRenderer} to renders items in a JTable by using resource names
+ * and control some formatting (centering, fonts and border)
+ */
+public class HeaderAsPropertyRendererWrapper implements TableCellRenderer, Serializable {
+
+ private static final long serialVersionUID = 240L;
+ private Object[][] columnsMsgParameters;
+
+ private TableCellRenderer delegate;
+
+ /**
+ * @param columnsMsgParameters Optional parameters of i18n keys
+ */
+ public HeaderAsPropertyRendererWrapper(TableCellRenderer renderer, Object[][] columnsMsgParameters) {
+ this.delegate = renderer;
+ this.columnsMsgParameters = columnsMsgParameters;
+ }
+
+ @Override
+ public Component getTableCellRendererComponent(JTable table, Object value,
+ boolean isSelected, boolean hasFocus, int row, int column) {
+ if(delegate instanceof DefaultTableCellRenderer) {
+ DefaultTableCellRenderer tr = (DefaultTableCellRenderer) delegate;
+ if (table != null) {
+ JTableHeader header = table.getTableHeader();
+ if (header != null){
+ tr.setForeground(header.getForeground());
+ tr.setBackground(header.getBackground());
+ tr.setFont(header.getFont());
+ }
+ }
+ tr.setBorder(UIManager.getBorder("TableHeader.cellBorder"));
+ tr.setHorizontalAlignment(SwingConstants.CENTER);
+ }
+ return delegate.getTableCellRendererComponent(table,
+ HeaderAsPropertyRenderer.getText(value, row, column, columnsMsgParameters),
+ isSelected, hasFocus, row, column);
+ }
+
+ /**
+ *
+ * @param table {@link JTable}
+ */
+ public static void setupDefaultRenderer(JTable table) {
+ setupDefaultRenderer(table, null);
+ }
+
+ /**
+ * @param table {@link JTable}
+ * @param columnsMsgParameters Double dimension array of column message parameters
+ */
+ public static void setupDefaultRenderer(JTable table, Object[][] columnsMsgParameters) {
+ TableCellRenderer defaultRenderer = table.getTableHeader().getDefaultRenderer();
+ HeaderAsPropertyRendererWrapper newRenderer = new HeaderAsPropertyRendererWrapper(defaultRenderer, columnsMsgParameters);
+ table.getTableHeader().setDefaultRenderer(newRenderer);
+ }
+
+}
Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/src/core/org/apache/jmeter/gui/util/HeaderAsPropertyRendererWrapper.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java (original)
+++ jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableModel.java Sat Jan 14 13:18:06 2017
@@ -23,7 +23,6 @@ import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
-import javax.swing.event.TableModelEvent;
import javax.swing.table.DefaultTableModel;
import org.apache.jorphan.logging.LoggingManager;
@@ -134,9 +133,8 @@ public class ObjectTableModel extends De
}
public void clearData() {
- int size = getRowCount();
objects.clear();
- super.fireTableRowsDeleted(0, size);
+ super.fireTableDataChanged();
}
public void addRow(Object value) {
@@ -149,12 +147,12 @@ public class ObjectTableModel extends De
}
}
objects.add(value);
- super.fireTableRowsInserted(objects.size() - 1, objects.size());
+ super.fireTableRowsInserted(objects.size() - 1, objects.size() - 1);
}
public void insertRow(Object value, int index) {
objects.add(index, value);
- super.fireTableRowsInserted(index, index + 1);
+ super.fireTableRowsInserted(index, index);
}
/** {@inheritDoc} */
@@ -202,12 +200,11 @@ public class ObjectTableModel extends De
/** {@inheritDoc} */
@Override
public void moveRow(int start, int end, int to) {
- List<Object> subList = new ArrayList<>(objects.subList(start, end));
- for (int x = end - 1; x >= start; x--) {
- objects.remove(x);
- }
- objects.addAll(to, subList);
- super.fireTableChanged(new TableModelEvent(this));
+ List<Object> subList = objects.subList(start, end);
+ List<Object> backup = new ArrayList<>(subList);
+ subList.clear();
+ objects.addAll(to, backup);
+ super.fireTableDataChanged();
}
/** {@inheritDoc} */
@@ -292,9 +289,19 @@ public class ObjectTableModel extends De
return status;
}
+ /**
+ * @return Object (List of Object)
+ */
public Object getObjectList() { // used by TableEditor
return objects;
}
+
+ /**
+ * @return List of Object
+ */
+ public List<Object> getObjectListAsList() {
+ return objects;
+ }
public void setRows(Iterable<?> rows) { // used by TableEditor
clearData();
Added: jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java?rev=1778767&view=auto
==============================================================================
--- jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java (added)
+++ jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java Sat Jan 14 13:18:06 2017
@@ -0,0 +1,356 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.gui;
+
+import static java.lang.String.format;
+
+import java.text.Collator;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Function;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import javax.swing.RowSorter;
+import javax.swing.SortOrder;
+
+/**
+ * Implementation of a {@link RowSorter} for {@link ObjectTableModel}
+ * @since 3.2
+ *
+ */
+public class ObjectTableSorter extends RowSorter<ObjectTableModel> {
+
+ /**
+ * View row with model mapping. All data relates to model.
+ */
+ public class Row {
+ private int index;
+
+ protected Row(int index) {
+ this.index = index;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ public Object getValue() {
+ return getModel().getObjectListAsList().get(getIndex());
+ }
+
+ public Object getValueAt(int column) {
+ return getModel().getValueAt(getIndex(), column);
+ }
+ }
+
+ protected class PreserveLastRowComparator implements Comparator<Row> {
+ @Override
+ public int compare(Row o1, Row o2) {
+ int lastIndex = model.getRowCount() - 1;
+ if (o1.getIndex() >= lastIndex || o2.getIndex() >= lastIndex) {
+ return o1.getIndex() - o2.getIndex();
+ }
+ return 0;
+ }
+ }
+
+ private ObjectTableModel model;
+ private SortKey sortkey;
+
+ private Comparator<Row> comparator = null;
+ private ArrayList<Row> viewToModel = new ArrayList<>();
+ private int[] modelToView = new int[0];
+
+ private Comparator<Row> primaryComparator = null;
+ private Comparator<?>[] valueComparators;
+ private Comparator<Row> fallbackComparator;
+
+ public ObjectTableSorter(ObjectTableModel model) {
+ this.model = model;
+
+ this.valueComparators = new Comparator<?>[this.model.getColumnCount()];
+ IntStream.range(0, this.valueComparators.length).forEach(i -> this.setValueComparator(i, null));
+
+ setFallbackComparator(null);
+ }
+
+ /**
+ * Comparator used prior to sorted columns.
+ */
+ public Comparator<Row> getPrimaryComparator() {
+ return primaryComparator;
+ }
+
+ /**
+ * Comparator used on sorted columns.
+ */
+ public Comparator<?> getValueComparator(int column) {
+ return valueComparators[column];
+ }
+
+ /**
+ * Comparator if all sorted columns matches. Defaults to model index comparison.
+ */
+ public Comparator<Row> getFallbackComparator() {
+ return fallbackComparator;
+ }
+
+ /**
+ * Comparator used prior to sorted columns.
+ * @return <code>this</code>
+ */
+ public ObjectTableSorter setPrimaryComparator(Comparator<Row> primaryComparator) {
+ invalidate();
+ this.primaryComparator = primaryComparator;
+ return this;
+ }
+
+ /**
+ * Sets {@link #getPrimaryComparator() primary comparator} to one that don't sort last row.
+ * @return <code>this</code>
+ */
+ public ObjectTableSorter fixLastRow() {
+ return setPrimaryComparator(new PreserveLastRowComparator());
+ }
+
+ /**
+ * Assign comparator to given column, if <code>null</code> a {@link #getDefaultComparator(int) default one} is used instead.
+ * @param column Model column index.
+ * @param comparator Column value comparator.
+ * @return <code>this</code>
+ */
+ public ObjectTableSorter setValueComparator(int column, Comparator<?> comparator) {
+ invalidate();
+ if (comparator == null) {
+ comparator = getDefaultComparator(column);
+ }
+ valueComparators[column] = comparator;
+ return this;
+ }
+
+ /**
+ * Builds a default comparator based on model column class. {@link Collator#getInstance()} for {@link String},
+ * {@link Comparator#naturalOrder() natural order} for {@link Comparable}, no sort support for others.
+ * @param column Model column index.
+ */
+ protected Comparator<?> getDefaultComparator(int column) {
+ Class<?> columnClass = model.getColumnClass(column);
+ if (columnClass == null) {
+ return null;
+ }
+ if (columnClass == String.class) {
+ return Comparator.nullsFirst(Collator.getInstance());
+ }
+ if (Comparable.class.isAssignableFrom(columnClass)) {
+ return Comparator.nullsFirst(Comparator.naturalOrder());
+ }
+ return null;
+ }
+
+ /**
+ * Sets a fallback comparator (defaults to model index comparison) if none {@link #getPrimaryComparator() primary}, neither {@link #getValueComparator(int) column value comparators} can make differences between two rows.
+ * @return <code>this</code>
+ */
+ public ObjectTableSorter setFallbackComparator(Comparator<Row> comparator) {
+ invalidate();
+ if (comparator == null) {
+ comparator = Comparator.comparingInt(Row::getIndex);
+ }
+ fallbackComparator = comparator;
+ return this;
+ }
+
+ @Override
+ public ObjectTableModel getModel() {
+ return model;
+ }
+
+ @Override
+ public void toggleSortOrder(int column) {
+ SortKey newSortKey;
+ if (isSortable(column)) {
+ SortOrder newOrder = sortkey == null || sortkey.getColumn() != column
+ || sortkey.getSortOrder() != SortOrder.ASCENDING ? SortOrder.ASCENDING : SortOrder.DESCENDING;
+ newSortKey = new SortKey(column, newOrder);
+ } else {
+ newSortKey = null;
+ }
+ setSortKey(newSortKey);
+ }
+
+ @Override
+ public int convertRowIndexToModel(int index) {
+ if (!isSorted()) {
+ return index;
+ }
+ validate();
+ return viewToModel.get(index).getIndex();
+ }
+
+ @Override
+ public int convertRowIndexToView(int index) {
+ if (!isSorted()) {
+ return index;
+ }
+ validate();
+ return modelToView[index];
+ }
+
+ @Override
+ public void setSortKeys(List<? extends SortKey> keys) {
+ switch (keys.size()) {
+ case 0:
+ setSortKey(null);
+ break;
+ case 1:
+ setSortKey(keys.get(0));
+ break;
+ default:
+ throw new IllegalArgumentException("Only one column can be sorted");
+ }
+ }
+
+ public void setSortKey(SortKey sortkey) {
+ if (Objects.equals(this.sortkey, sortkey)) {
+ return;
+ }
+
+ invalidate();
+ if (sortkey != null) {
+ int column = sortkey.getColumn();
+ Comparator<?> comparator = valueComparators[column];
+ if (comparator == null) {
+ throw new IllegalArgumentException(format("Can't sort column %s, it is mapped to type %s and this one have no natural order. So an explicit one must be specified", column, model.getColumnClass(column)));
+ }
+ }
+ this.sortkey = sortkey;
+ this.comparator = null;
+ }
+
+ @Override
+ public List<? extends SortKey> getSortKeys() {
+ return isSorted() ? Collections.singletonList(sortkey) : Collections.emptyList();
+ }
+
+ @Override
+ public int getViewRowCount() {
+ return getModelRowCount();
+ }
+
+ @Override
+ public int getModelRowCount() {
+ return model.getRowCount();
+ }
+
+ @Override
+ public void modelStructureChanged() {
+ setSortKey(null);
+ }
+
+ @Override
+ public void allRowsChanged() {
+ invalidate();
+ }
+
+ @Override
+ public void rowsInserted(int firstRow, int endRow) {
+ rowsChanged(firstRow, endRow, false, true);
+ }
+
+ @Override
+ public void rowsDeleted(int firstRow, int endRow) {
+ rowsChanged(firstRow, endRow, true, false);
+ }
+
+ @Override
+ public void rowsUpdated(int firstRow, int endRow) {
+ rowsChanged(firstRow, endRow, true, true);
+ }
+
+ protected void rowsChanged(int firstRow, int endRow, boolean deleted, boolean inserted) {
+ invalidate();
+ }
+
+ @Override
+ public void rowsUpdated(int firstRow, int endRow, int column) {
+ if (isSorted(column)) {
+ rowsUpdated(firstRow, endRow);
+ }
+ }
+
+ protected boolean isSortable(int column) {
+ return getValueComparator(column) != null;
+ }
+
+ protected boolean isSorted(int column) {
+ return isSorted() && sortkey.getColumn() == column && sortkey.getSortOrder() != SortOrder.UNSORTED;
+ }
+
+ protected boolean isSorted() {
+ return sortkey != null;
+ }
+
+ protected void invalidate() {
+ viewToModel.clear();
+ modelToView = new int[0];
+ }
+
+ protected void validate() {
+ if (isSorted() && viewToModel.isEmpty()) {
+ sort();
+ }
+ }
+
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ protected Comparator<Row> getComparatorFromSortKey(SortKey sortkey) {
+ Comparator comparator = getValueComparator(sortkey.getColumn());
+ if (sortkey.getSortOrder() == SortOrder.DESCENDING) {
+ comparator = comparator.reversed();
+ }
+ Function<Row,Object> getValueAt = (Row row) -> row.getValueAt(sortkey.getColumn());
+ return Comparator.comparing(getValueAt, comparator);
+ }
+
+ protected void sort() {
+ if (comparator == null) {
+ comparator = Stream.concat(
+ Stream.concat(
+ getPrimaryComparator() != null ? Stream.of(getPrimaryComparator()) : Stream.<Comparator<Row>>empty(),
+ getSortKeys().stream().filter(sk -> sk != null && sk.getSortOrder() != SortOrder.UNSORTED).map(this::getComparatorFromSortKey)
+ ),
+ Stream.of(getFallbackComparator())
+ ).reduce(comparator, (result, current) -> result != null ? result.thenComparing(current) : current);
+ }
+
+ viewToModel.clear();
+ viewToModel.ensureCapacity(model.getRowCount());
+ IntStream.range(0, model.getRowCount()).mapToObj(i -> new Row(i)).forEach(viewToModel::add);
+ Collections.sort(viewToModel, comparator);
+
+ updateModelToView();
+ }
+
+ protected void updateModelToView() {
+ modelToView = new int[viewToModel.size()];
+ IntStream.range(0, viewToModel.size()).forEach(viewIndex -> modelToView[viewToModel.get(viewIndex).getIndex()] = viewIndex);
+ }
+}
Propchange: jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/src/jorphan/org/apache/jorphan/gui/ObjectTableSorter.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java?rev=1778767&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java (added)
+++ jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java Sat Jan 14 13:18:06 2017
@@ -0,0 +1,251 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.gui;
+
+import static java.lang.String.format;
+import static java.util.stream.IntStream.range;
+import static org.junit.Assert.assertEquals;
+
+import java.util.Arrays;
+import java.util.stream.IntStream;
+
+import javax.swing.event.TableModelEvent;
+
+import org.apache.jorphan.reflect.Functor;
+import org.junit.Before;
+import org.junit.Test;
+
+public class ObjectTableModelTest {
+
+ public static class Dummy {
+ String a;
+ String b;
+ String c;
+
+ Dummy(String a, String b, String c) {
+ this.a = a;
+ this.b = b;
+ this.c = c;
+ }
+
+ public String getA() {
+ return a;
+ }
+
+ public String getB() {
+ return b;
+ }
+
+ public String getC() {
+ return c;
+ }
+ }
+
+ ObjectTableModel model;
+ TableModelEventBacker events;
+
+ @Before
+ public void init() {
+ String[] headers = { "a", "b", "c" };
+ Functor[] readFunctors = Arrays.stream(headers).map(name -> "get" + name.toUpperCase()).map(Functor::new).toArray(n -> new Functor[n]);
+ Functor[] writeFunctors = new Functor[headers.length];
+ Class<?>[] editorClasses = new Class<?>[headers.length];
+ Arrays.fill(editorClasses, String.class);
+ model = new ObjectTableModel(headers, readFunctors, writeFunctors, editorClasses);
+ events = new TableModelEventBacker();
+ }
+
+ @Test
+ public void checkAddRow() {
+ model.addTableModelListener(events);
+
+ assertModel();
+
+ model.addRow(new Dummy("1", "1", "1"));
+ assertModel("1");
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.INSERT)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(0)
+ );
+
+ model.addRow(new Dummy("2", "1", "1"));
+ assertModel("1", "2");
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.INSERT)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(1)
+ .lastRow(1)
+ );
+ }
+
+ @Test
+ public void checkClear() {
+ // Arrange
+ for (int i = 0; i < 5; i++) {
+ model.addRow(new Dummy("" + i, "" + i%2, "" + i%3));
+ }
+ assertModelRanges(range(0,5));
+
+ // Act
+ model.addTableModelListener(events);
+ model.clearData();
+
+ // Assert
+ assertModelRanges();
+
+
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.UPDATE)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(Integer.MAX_VALUE)
+ );
+ }
+
+ @Test
+ public void checkInsertRow() {
+ assertModel();
+ model.addRow(new Dummy("3", "1", "1"));
+ assertModel("3");
+ model.addTableModelListener(events);
+
+ model.insertRow(new Dummy("1", "1", "1"), 0);
+ assertModel("1", "3");
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.INSERT)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(0)
+ );
+
+ model.insertRow(new Dummy("2", "1", "1"), 1);
+ assertModel("1", "2", "3");
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.INSERT)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(1)
+ .lastRow(1)
+ );
+
+
+ }
+
+ @Test
+ public void checkMoveRow_from_5_11_to_0() {
+ // Arrange
+ for (int i = 0; i < 20; i++) {
+ model.addRow(new Dummy("" + i, "" + i%2, "" + i%3));
+ }
+ assertModelRanges(range(0, 20));
+
+ // Act
+ model.addTableModelListener(events);
+ model.moveRow(5, 11, 0);
+
+ // Assert
+ assertModelRanges(range(5, 11), range(0, 5), range(11, 20));
+
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.UPDATE)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(Integer.MAX_VALUE)
+ );
+ }
+
+ @Test
+ public void checkMoveRow_from_0_6_to_0() {
+ // Arrange
+ for (int i = 0; i < 20; i++) {
+ model.addRow(new Dummy("" + i, "" + i%2, "" + i%3));
+ }
+ assertModelRanges(range(0, 20));
+
+ // Act
+ model.addTableModelListener(events);
+ model.moveRow(0, 6, 0);
+
+ // Assert
+ assertModelRanges(range(0, 20));
+
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.UPDATE)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(Integer.MAX_VALUE)
+ );
+ }
+
+ @Test
+ public void checkMoveRow_from_0_6_to_10() {
+ // Arrange
+ for (int i = 0; i < 20; i++) {
+ model.addRow(new Dummy("" + i, "" + i%2, "" + i%3));
+ }
+ assertModelRanges(range(0, 20));
+
+ // Act
+ model.addTableModelListener(events);
+ model.moveRow(0, 6, 10);
+
+ // Assert
+ assertModelRanges(range(6, 16), range(0, 6), range(16, 20));
+
+ events.assertEvents(
+ events.assertEvent()
+ .source(model)
+ .type(TableModelEvent.UPDATE)
+ .column(TableModelEvent.ALL_COLUMNS)
+ .firstRow(0)
+ .lastRow(Integer.MAX_VALUE)
+ );
+ }
+
+ private void assertModelRanges(IntStream... ranges) {
+ IntStream ints = IntStream.empty();
+ for (IntStream range : ranges) {
+ ints = IntStream.concat(ints, range);
+ }
+ assertModel(ints.mapToObj(i -> "" + i).toArray(n -> new String[n]));
+ }
+
+ private void assertModel(String... as) {
+ assertEquals("model row count", as.length, model.getRowCount());
+
+ for (int row = 0; row < as.length; row++) {
+ assertEquals(format("model[%d,0]", row), as[row], model.getValueAt(row, 0));
+ }
+ }
+
+}
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableModelTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java?rev=1778767&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java (added)
+++ jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java Sat Jan 14 13:18:06 2017
@@ -0,0 +1,304 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.gui;
+
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+
+import java.util.AbstractMap;
+import java.util.AbstractMap.SimpleImmutableEntry;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map.Entry;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import javax.swing.RowSorter.SortKey;
+import javax.swing.SortOrder;
+
+import org.apache.jorphan.reflect.Functor;
+import org.hamcrest.CoreMatchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ErrorCollector;
+import org.junit.rules.ExpectedException;
+
+public class ObjectTableSorterTest {
+ ObjectTableModel model;
+ ObjectTableSorter sorter;
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public ErrorCollector errorCollector = new ErrorCollector();
+
+ @Before
+ public void createModelAndSorter() {
+ String[] headers = { "key", "value", "object" };
+ Functor[] readFunctors = { new Functor("getKey"), new Functor("getValue"), new Functor("getValue") };
+ Functor[] writeFunctors = { null, null, null };
+ Class<?>[] editorClasses = { String.class, Integer.class, Object.class };
+ model = new ObjectTableModel(headers, readFunctors, writeFunctors, editorClasses);
+ sorter = new ObjectTableSorter(model);
+ List<Entry<String,Integer>> data = asList(b2(), a3(), d4(), c1());
+ data.forEach(model::addRow);
+ }
+
+ @Test
+ public void noSorting() {
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(b2(), a3(), d4(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void sortKeyAscending() {
+ sorter.setSortKey(new SortKey(0, SortOrder.ASCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(a3(), b2(), c1(), d4());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void sortKeyDescending() {
+ sorter.setSortKey(new SortKey(0, SortOrder.DESCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(d4(), c1(), b2(), a3());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void sortValueAscending() {
+ sorter.setSortKey(new SortKey(1, SortOrder.ASCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(c1(), b2(), a3(), d4());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void sortValueDescending() {
+ sorter.setSortKey(new SortKey(1, SortOrder.DESCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(d4(), a3(), b2(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+
+ @Test
+ public void fixLastRowWithAscendingKey() {
+ sorter.fixLastRow().setSortKey(new SortKey(0, SortOrder.ASCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(a3(), b2(), d4(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void fixLastRowWithDescendingKey() {
+ sorter.fixLastRow().setSortKey(new SortKey(0, SortOrder.DESCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(d4(), b2(), a3(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void fixLastRowWithAscendingValue() {
+ sorter.fixLastRow().setSortKey(new SortKey(1, SortOrder.ASCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(b2(), a3(), d4(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void fixLastRowWithDescendingValue() {
+ sorter.fixLastRow().setSortKey(new SortKey(1, SortOrder.DESCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(d4(), a3(), b2(), c1());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void customKeyOrder() {
+ HashMap<String, Integer> customKeyOrder = asList("a", "c", "b", "d").stream().reduce(new HashMap<String,Integer>(), (map,key) -> { map.put(key, map.size()); return map; }, (a,b) -> a);
+ Comparator<String> customKeyComparator = (a,b) -> customKeyOrder.get(a).compareTo(customKeyOrder.get(b));
+ sorter.setValueComparator(0, customKeyComparator).setSortKey(new SortKey(0, SortOrder.ASCENDING));
+ List<SimpleImmutableEntry<String, Integer>> expected = asList(a3(), c1(), b2(), d4());
+ assertRowOrderAndIndexes(expected);
+ }
+
+ @Test
+ public void getDefaultComparatorForNullClass() {
+ ObjectTableModel model = new ObjectTableModel(new String[] { "null" }, new Functor[] { null }, new Functor[] { null }, new Class<?>[] { null });
+ ObjectTableSorter sorter = new ObjectTableSorter(model);
+
+ assertThat(sorter.getValueComparator(0), is(nullValue()));
+ }
+
+ @Test
+ public void getDefaultComparatorForStringClass() {
+ ObjectTableModel model = new ObjectTableModel(new String[] { "string" }, new Functor[] { null }, new Functor[] { null }, new Class<?>[] { String.class });
+ ObjectTableSorter sorter = new ObjectTableSorter(model);
+
+ assertThat(sorter.getValueComparator(0), is(CoreMatchers.notNullValue()));
+ }
+
+ @Test
+ public void getDefaultComparatorForIntegerClass() {
+ ObjectTableModel model = new ObjectTableModel(new String[] { "integer" }, new Functor[] { null }, new Functor[] { null }, new Class<?>[] { Integer.class });
+ ObjectTableSorter sorter = new ObjectTableSorter(model);
+
+ assertThat(sorter.getValueComparator(0), is(CoreMatchers.notNullValue()));
+ }
+
+ @Test
+ public void getDefaultComparatorForObjectClass() {
+ ObjectTableModel model = new ObjectTableModel(new String[] { "integer" }, new Functor[] { null }, new Functor[] { null }, new Class<?>[] { Object.class });
+ ObjectTableSorter sorter = new ObjectTableSorter(model);
+
+ assertThat(sorter.getValueComparator(0), is(nullValue()));
+ }
+
+ @Test
+ public void toggleSortOrder_none() {
+ assertSame(emptyList(), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_0() {
+ sorter.toggleSortOrder(0);
+ assertEquals(singletonList(new SortKey(0, SortOrder.ASCENDING)), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_0_1() {
+ sorter.toggleSortOrder(0);
+ sorter.toggleSortOrder(1);
+ assertEquals(singletonList(new SortKey(1, SortOrder.ASCENDING)), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_0_0() {
+ sorter.toggleSortOrder(0);
+ sorter.toggleSortOrder(0);
+ assertEquals(singletonList(new SortKey(0, SortOrder.DESCENDING)), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_0_0_0() {
+ sorter.toggleSortOrder(0);
+ sorter.toggleSortOrder(0);
+ sorter.toggleSortOrder(0);
+ assertEquals(singletonList(new SortKey(0, SortOrder.ASCENDING)), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_2() {
+ sorter.toggleSortOrder(2);
+ assertSame(emptyList(), sorter.getSortKeys());
+ }
+
+ @Test
+ public void toggleSortOrder_0_2() {
+ sorter.toggleSortOrder(0);
+ sorter.toggleSortOrder(2);
+ assertSame(emptyList(), sorter.getSortKeys());
+ }
+
+ @Test
+ public void setSortKeys_none() {
+ sorter.setSortKeys(new ArrayList<>());
+ assertSame(Collections.emptyList(), sorter.getSortKeys());
+ }
+
+ @Test
+ public void setSortKeys_withSortedThenUnsorted() {
+ sorter.setSortKeys(singletonList(new SortKey(0, SortOrder.ASCENDING)));
+ sorter.setSortKeys(new ArrayList<>());
+ assertSame(Collections.emptyList(), sorter.getSortKeys());
+ }
+
+ @Test
+ public void setSortKeys_single() {
+ List<SortKey> keys = singletonList(new SortKey(0, SortOrder.ASCENDING));
+ sorter.setSortKeys(keys);
+ assertThat(sorter.getSortKeys(), allOf( is(not(sameInstance(keys))), is(equalTo(keys)) ));
+ }
+
+ @Test
+ public void setSortKeys_many() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ sorter.setSortKeys(asList(new SortKey(0, SortOrder.ASCENDING), new SortKey(1, SortOrder.ASCENDING)));
+ }
+
+ @Test
+ public void setSortKeys_invalidColumn() {
+ expectedException.expect(IllegalArgumentException.class);
+
+ sorter.setSortKeys(Collections.singletonList(new SortKey(2, SortOrder.ASCENDING)));
+ }
+
+
+ @SuppressWarnings("unchecked")
+ protected List<Entry<String,Integer>> actual() {
+ return IntStream
+ .range(0, sorter.getViewRowCount())
+ .map(sorter::convertRowIndexToModel)
+ .mapToObj(modelIndex -> (Entry<String,Integer>) sorter.getModel().getObjectListAsList().get(modelIndex))
+ .collect(Collectors.toList())
+ ;
+ }
+
+ protected SimpleImmutableEntry<String, Integer> d4() {
+ return new AbstractMap.SimpleImmutableEntry<>("d", 4);
+ }
+
+ protected SimpleImmutableEntry<String, Integer> c1() {
+ return new AbstractMap.SimpleImmutableEntry<>("c", 1);
+ }
+
+ protected SimpleImmutableEntry<String, Integer> b2() {
+ return new AbstractMap.SimpleImmutableEntry<>("b", 2);
+ }
+
+ protected SimpleImmutableEntry<String, Integer> a3() {
+ return new AbstractMap.SimpleImmutableEntry<>("a", 3);
+ }
+
+ protected void assertRowOrderAndIndexes(List<SimpleImmutableEntry<String, Integer>> expected) {
+ assertEquals(expected, actual());
+ assertRowIndexes();
+ }
+
+ protected void assertRowIndexes() {
+ IntStream
+ .range(0, sorter.getViewRowCount())
+ .forEach(viewIndex -> {
+ int modelIndex = sorter.convertRowIndexToModel(viewIndex);
+ errorCollector.checkThat(format("view(%d) model(%d)", viewIndex, modelIndex),
+ sorter.convertRowIndexToView(modelIndex),
+ CoreMatchers.equalTo(viewIndex));
+ });
+
+ }
+}
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/ObjectTableSorterTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added: jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java
URL: http://svn.apache.org/viewvc/jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java?rev=1778767&view=auto
==============================================================================
--- jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java (added)
+++ jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java Sat Jan 14 13:18:06 2017
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.jorphan.gui;
+
+import static java.lang.String.format;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.function.ObjIntConsumer;
+import java.util.function.ToIntFunction;
+
+import javax.swing.event.TableModelEvent;
+import javax.swing.event.TableModelListener;
+
+/**
+ * Listener implementation that stores {@link TableModelEvent} and can make assertions against them.
+ */
+public class TableModelEventBacker implements TableModelListener {
+
+ /**
+ * Makes assertions for a single {@link TableModelEvent}.
+ */
+ public class EventAssertion {
+ private List<ObjIntConsumer<TableModelEvent>> assertions = new ArrayList<>();
+
+ /**
+ * Adds an assertion first args is table model event, second one is event index.
+ * @return <code>this</code>
+ */
+ public EventAssertion add(ObjIntConsumer<TableModelEvent> assertion) {
+ assertions.add(assertion);
+ return this;
+ }
+
+ /**
+ * Adds assertion based on a {@link ToIntFunction to-int} transformation (examples: <code>TableModelEvent::getType</code>).
+ * @param name Label for assertion reason
+ * @param expected Expected value.
+ * @param f {@link ToIntFunction to-int} transformation (examples: <code>TableModelEvent::getType</code>).
+ * @return <code>this</code>
+ */
+ public EventAssertion addInt(String name, int expected, ToIntFunction<TableModelEvent> f) {
+ return add((e,i) -> assertEquals(format("%s[%d]", name, i), expected, f.applyAsInt(e)));
+ }
+
+ /**
+ * Adds {@link TableModelEvent#getSource()} assertion.
+ * @return <code>this</code>
+ */
+ public EventAssertion source(Object expected) {
+ return add((e,i) -> assertSame(format("source[%d]",i), expected, e.getSource()));
+ }
+
+ /**
+ * Adds {@link TableModelEvent#getType()} assertion.
+ * @return <code>this</code>
+ */
+ public EventAssertion type(int expected) {
+ return addInt("type", expected, TableModelEvent::getType);
+ }
+
+ /**
+ * Adds {@link TableModelEvent#getColumn()} assertion.
+ * @return <code>this</code>
+ */
+ public EventAssertion column(int expected) {
+ return addInt("column", expected, TableModelEvent::getColumn);
+ }
+
+ /**
+ * Adds {@link TableModelEvent#getFirstRow()} assertion.
+ * @return <code>this</code>
+ */
+ public EventAssertion firstRow(int expected) {
+ return addInt("firstRow", expected, TableModelEvent::getFirstRow);
+ }
+
+ /**
+ * Adds {@link TableModelEvent#getLastRow()} assertion.
+ * @return <code>this</code>
+ */
+ public EventAssertion lastRow(int expected) {
+ return addInt("lastRow", expected, TableModelEvent::getLastRow);
+ }
+
+ /**
+ * Check assertion against provided value.
+ * @param event Event to check
+ * @param index Index.
+ */
+ protected void assertEvent(TableModelEvent event, int index) {
+ assertions.forEach(a -> a.accept(event, index));
+ }
+ }
+
+ private Deque<TableModelEvent> events = new LinkedList<>();
+
+ /**
+ * Stores event.
+ */
+ @Override
+ public void tableChanged(TableModelEvent e) {
+ events.add(e);
+ }
+
+ public Deque<TableModelEvent> getEvents() {
+ return events;
+ }
+
+ /**
+ * Creates a new event assertion.
+ * @see #assertEvents(EventAssertion...)
+ */
+ public EventAssertion assertEvent() {
+ return new EventAssertion();
+ }
+
+ /**
+ * Checks each event assertion against each backed event in order. Event storage is cleared after it.
+ */
+ public void assertEvents(EventAssertion... assertions) {
+ try {
+ assertEquals("event count", assertions.length, events.size());
+
+ int i = 0;
+ for (TableModelEvent event : events) {
+ assertions[i].assertEvent(event, i++);
+ }
+ } finally {
+ events.clear();
+ }
+ }
+
+
+}
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: jmeter/trunk/test/src/org/apache/jorphan/gui/TableModelEventBacker.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Modified: jmeter/trunk/xdocs/changes.xml
URL: http://svn.apache.org/viewvc/jmeter/trunk/xdocs/changes.xml?rev=1778767&r1=1778766&r2=1778767&view=diff
==============================================================================
--- jmeter/trunk/xdocs/changes.xml [utf-8] (original)
+++ jmeter/trunk/xdocs/changes.xml [utf-8] Sat Jan 14 13:18:06 2017
@@ -124,6 +124,7 @@ Fill in some detail.
<ul>
<li><bug>60144</bug>View Results Tree : Add a more up to date Browser Renderer to replace old Render</li>
<li><bug>60542</bug>View Results Tree : Allow Upper Panel to be collapsed. Contributed by Ubik Load Pack (support at ubikloadpack.com)</li>
+ <li><bug>52962</bug>Allow sorting by columns for View Results in Table, Summary Report, Aggregate Report and Aggregate Graph. Based on a contribution by Logan Mauzaize (logan.mauzaize at gmail.com) and Maxime Chassagneux (maxime.chassagneux@gmail.com).</li>
</ul>
<h3>Timers, Assertions, Config, Pre- & Post-Processors</h3>
@@ -147,7 +148,7 @@ Fill in some detail.
<h3>General</h3>
<ul>
<li><bug>54525</bug>Search Feature : Enhance it with ability to replace</li>
- <li><bug>60530</bug>Add API to create JMeter threads while test is running. Based on a contribution by Logan Mauzaize and Maxime Chassagneux</li>
+ <li><bug>60530</bug>Add API to create JMeter threads while test is running. Based on a contribution by Logan Mauzaize (logan.mauzaize at gmail.com) and Maxime Chassagneux (maxime.chassagneux@gmail.com).</li>
</ul>
<ch_section>Non-functional changes</ch_section>
@@ -216,8 +217,8 @@ Fill in some detail.
<li>(gavin at 16degrees.com.au)</li>
<li>Thomas Schapitz (ts-nospam12 at online.de)</li>
<li>Murdecai777 (https://github.com/Murdecai777)</li>
-<li>Logan Mauzaize (https://github.com/loganmzz)</li>
-<li>Maxime Chassagneux (https://github.com/max3163)</li>
+<li>Logan Mauzaize (logan.mauzaize at gmail.com)</li>
+<li>Maxime Chassagneux (maxime.chassagneux@gmail.com)</li>
<li>\u5ffb\u9686 (298015902 at qq.com)</li>
<li><a href="http://ubikloadpack.com">Ubik Load Pack</a></li>
</ul>