Jian Li
Committed by Gerrit Code Review

Revise ChartModel and add ChartRequestHandler, ChartUtils classes

Change-Id: I9c72122368c8270df9a1055845281e999d488243
......@@ -16,16 +16,20 @@
package org.onosproject.ui.chart;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* A simple model of chart data.
* A simple model of time series chart data.
*
* <p>
* Note that this is not a full MVC type model; the expected usage pattern
......@@ -35,58 +39,65 @@ import static com.google.common.base.Preconditions.checkNotNull;
*/
public class ChartModel {
// key is series name, value is series index
private final Map<String, Integer> seriesMap;
private final DataPoint[] dataPoints;
private final Set<String> seriesSet;
private final String[] seriesArray;
private final List<Long> labels = Lists.newArrayList();
private final List<DataPoint> dataPoints = Lists.newArrayList();
/**
* Constructs a chart model with initialized series set.
*
* @param size datapoints size
* @param series a set of series
*/
public ChartModel(int size, String... series) {
public ChartModel(String... series) {
checkNotNull(series, "series cannot be null");
checkArgument(series.length > 0, "must be at least one series");
seriesMap = Maps.newConcurrentMap();
for (int index = 0; index < series.length; index++) {
seriesMap.put(series[index], index);
seriesSet = Sets.newHashSet(series);
if (seriesSet.size() != series.length) {
throw new IllegalArgumentException("duplicate series detected");
}
checkArgument(size > 0, "must have at least one data point");
dataPoints = new DataPoint[size];
this.seriesArray = Arrays.copyOf(series, series.length);
}
private void checkDataPoint(DataPoint dataPoint) {
checkArgument(dataPoint.getSize() == seriesCount(),
checkArgument(dataPoint.size() == seriesCount(),
"data size should be equal to number of series");
}
/**
* Checks the validity of the given series.
*
* @param series series name
*/
private void checkSeries(String series) {
checkNotNull(series, "must provide a series name");
if (!seriesSet.contains(series)) {
throw new IllegalArgumentException("unknown series: " + series);
}
}
/**
* Returns the number of series in this chart model.
*
* @return number of series
*/
public int seriesCount() {
return seriesMap.size();
return seriesSet.size();
}
/**
* Shifts all of the data points to the left,
* and adds a new data point to the tail of the array.
* Adds a data point to the chart model.
*
* @param label label name
* @param values a set of data values
* @return the data point, for chaining
*/
public void addDataPoint(String label, Double[] values) {
DataPoint dp = new DataPoint(label, values);
checkDataPoint(dp);
for (int index = 1; index < dataPoints.length; index++) {
dataPoints[index - 1] = dataPoints[index];
}
dataPoints[dataPoints.length - 1] = dp;
public DataPoint addDataPoint(Long label) {
DataPoint dp = new DataPoint();
labels.add(label);
dataPoints.add(dp);
return dp;
}
/**
......@@ -95,16 +106,34 @@ public class ChartModel {
* @return an array of series
*/
public String[] getSeries() {
return seriesMap.keySet().toArray(new String[seriesMap.size()]);
return seriesArray;
}
/**
* Returns all of data points.
* Returns all of data points in order.
*
* @return an array of data points
*/
public DataPoint[] getDataPoints() {
return Arrays.copyOf(dataPoints, dataPoints.length);
return dataPoints.toArray(new DataPoint[dataPoints.size()]);
}
/**
* Returns all of labels in order.
*
* @return an array of labels
*/
public Object[] getLabels() {
return labels.toArray(new Long[labels.size()]);
}
/**
* Returns the number of data points in this chart model.
*
* @return number of data points
*/
public int dataPointCount() {
return dataPoints.size();
}
/**
......@@ -113,7 +142,7 @@ public class ChartModel {
* @return data point
*/
public DataPoint getLastDataPoint() {
return dataPoints[dataPoints.length - 1];
return dataPoints.get(dataPoints.size() - 1);
}
/**
......@@ -121,47 +150,52 @@ public class ChartModel {
*/
public class DataPoint {
// values for all series
private final Double[] values;
private final String label;
private final Map<String, Double> data = Maps.newHashMap();
/**
* Constructs a data point.
* Sets the data value for the given series of this data point.
*
* @param label label name
* @param values a set of data values for all series
* @param series series name
* @param value value to set
* @return self, for chaining
*/
public DataPoint(String label, Double[] values) {
this.label = label;
this.values = values;
public DataPoint data(String series, Double value) {
checkSeries(series);
data.put(series, value);
return this;
}
/**
* Returns the label name of this data point.
* Returns the data value with the given series for this data point.
*
* @return label name
* @return data value
*/
public String getLabel() {
return label;
public Double get(String series) {
return data.get(series);
}
/**
* Returns the size of data point.
* This should be identical to the size of series.
* Return the data value with the same order of series.
*
* @return size of data point
* @return an array of ordered data values
*/
public int getSize() {
return values.length;
public Double[] getAll() {
Double[] value = new Double[getSeries().length];
int idx = 0;
for (String s : getSeries()) {
value[idx] = get(s);
idx++;
}
return value;
}
/**
* Returns the value of the data point of the given series.
* Returns the size of data point.
*
* @param series series name
* @return data value of a specific series
* @return the size of data point
*/
public Double getValue(String series) {
return values[seriesMap.get(series)];
public int size() {
return data.size();
}
}
}
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed 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.onosproject.ui.chart;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onosproject.ui.RequestHandler;
/**
* Message handler specifically for the chart views.
*/
public abstract class ChartRequestHandler extends RequestHandler {
private final String respType;
private final String nodeName;
/**
* Constructs a chart model handler for a specific graph view. When chart
* requests come in, the handler will generate the appropriate chart data
* points and send back the response to the client.
*
* @param reqType type of the request event
* @param respType type of the response event
* @param nodeName name of JSON node holding data point
*/
public ChartRequestHandler(String reqType, String respType, String nodeName) {
super(reqType);
this.respType = respType;
this.nodeName = nodeName;
}
@Override
public void process(long sid, ObjectNode payload) {
ChartModel cm = createChartModel();
populateChart(cm, payload);
ObjectNode rootNode = MAPPER.createObjectNode();
rootNode.set(nodeName, ChartUtils.generateDataPointArrayNode(cm));
sendMessage(respType, 0, rootNode);
}
/**
* Creates the chart model using {@link #getSeries()}
* to initialize it, ready to be populated.
* <p>
* This default implementation returns a chart model for all series.
* </p>
*
* @return an empty chart model
*/
protected ChartModel createChartModel() {
return new ChartModel(getSeries());
}
/**
* Subclasses should return the array of series with which to initialize
* their chart model.
*
* @return the series name
*/
protected abstract String[] getSeries();
/**
* Subclasses should populate the chart model by adding
* {@link ChartModel.DataPoint datapoints}.
* <pre>
* cm.addDataPoint()
* .data(SERIES_ONE, ...)
* .data(SERIES_TWO, ...)
* ... ;
* </pre>
* The request payload is provided in case there are request filtering
* parameters.
*
* @param cm the chart model
* @param payload request payload
*/
protected abstract void populateChart(ChartModel cm, ObjectNode payload);
}
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed 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.onosproject.ui.chart;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Provides static utility methods for dealing with charts.
*/
public final class ChartUtils {
private static final ObjectMapper MAPPER = new ObjectMapper();
// non-instantiable
private ChartUtils() {
}
/**
* Generates a JSON array node from the data points of the given chart model.
*
* @param cm the chart model
* @return the array node representation of data points
*/
public static ArrayNode generateDataPointArrayNode(ChartModel cm) {
ArrayNode array = MAPPER.createArrayNode();
for (ChartModel.DataPoint dp : cm.getDataPoints()) {
array.add(toJsonNode(dp, cm));
}
return array;
}
/**
* Generate a JSON node from the data point and given chart model.
*
* @param dp the data point
* @param cm the chart model
* @return the node representation of a data point with series
*/
public static JsonNode toJsonNode(ChartModel.DataPoint dp, ChartModel cm) {
ObjectNode result = MAPPER.createObjectNode();
String[] series = cm.getSeries();
Double[] values = dp.getAll();
int n = series.length;
for (int i = 0; i < n; i++) {
result.put(series[i], values[i]);
}
return result;
}
}
......@@ -17,10 +17,7 @@ package org.onosproject.ui.chart;
import org.junit.Test;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
/**
* Unit tests for {@link ChartModel}.
......@@ -35,57 +32,97 @@ public class ChartModelTest {
private static final Double[] VALUES2 = {3D, 4D, 5D};
private static final Double[] VALUES3 = {6D, 7D, 8D};
private static final String[] SERIES = {FOO, BAR, ZOO};
private ChartModel cm;
@Test(expected = NullPointerException.class)
public void guardAgainstNullSeries() {
cm = new ChartModel(1, null);
}
@Test(expected = IllegalArgumentException.class)
public void guardAgainstWrongDpNumber() {
cm = new ChartModel(0, FOO);
cm = new ChartModel(null);
}
@Test
public void testSeriesCount() {
cm = new ChartModel(1, FOO, BAR, ZOO);
cm = new ChartModel(FOO, BAR, ZOO);
assertEquals("Wrong series count", 3, cm.seriesCount());
}
@Test
public void emptyLabel() {
cm = new ChartModel(FOO, BAR, ZOO);
cm.addDataPoint(System.currentTimeMillis());
assertEquals("bad data point count", 1, cm.dataPointCount());
}
@Test(expected = IllegalArgumentException.class)
public void dataPointBandSeries() {
cm = new ChartModel(FOO, BAR);
cm.addDataPoint(System.currentTimeMillis())
.data(ZOO, VALUES3[0]);
}
@Test
public void testAddDataPoint() {
cm = new ChartModel(2, FOO, BAR, ZOO);
cm = new ChartModel(FOO, BAR, ZOO);
cm.addDataPoint("1", VALUES1);
cm.addDataPoint("2", VALUES2);
long time = System.currentTimeMillis();
assertEquals("Wrong result", "1", cm.getDataPoints()[0].getLabel());
assertEquals("Wrong result", "2", cm.getDataPoints()[1].getLabel());
cm.addDataPoint(time)
.data(FOO, VALUES1[0])
.data(BAR, VALUES2[0])
.data(ZOO, VALUES3[0]);
cm.addDataPoint("3", VALUES3);
cm.addDataPoint(time + 1)
.data(FOO, VALUES1[1])
.data(BAR, VALUES2[1])
.data(ZOO, VALUES3[1]);
assertEquals("Wrong result", "2", cm.getDataPoints()[0].getLabel());
assertEquals("Wrong result", "3", cm.getDataPoints()[1].getLabel());
cm.addDataPoint(time + 2)
.data(FOO, VALUES1[2])
.data(BAR, VALUES2[2])
.data(ZOO, VALUES3[2]);
assertEquals("Wrong result", 3, cm.getDataPoints()[0].size());
assertEquals("Wrong result", 3, cm.getDataPoints()[1].size());
assertEquals("Wrong result", 3, cm.getDataPoints()[2].size());
assertEquals("Wrong result", 3, cm.getDataPoints().length);
}
@Test
public void testGetData() {
cm = new ChartModel(2, FOO, BAR, ZOO);
public void testGetDataPoint() {
cm = new ChartModel(FOO, BAR);
long time = System.currentTimeMillis();
cm.addDataPoint("1", VALUES1);
assertThat(cm.getLastDataPoint().getValue(ZOO), is(2D));
cm.addDataPoint(time)
.data(FOO, VALUES1[0])
.data(BAR, VALUES2[0]);
cm.addDataPoint("2", VALUES2);
assertThat(cm.getLastDataPoint().getValue(BAR), is(4D));
cm.addDataPoint(time + 1)
.data(FOO, VALUES1[1])
.data(BAR, VALUES2[1]);
assertEquals("Wrong result", (Double) 0D, cm.getDataPoints()[0].get(FOO));
assertEquals("Wrong result", (Double) 1D, cm.getDataPoints()[1].get(FOO));
assertEquals("Wrong result", (Double) 3D, cm.getDataPoints()[0].get(BAR));
assertEquals("Wrong result", (Double) 4D, cm.getDataPoints()[1].get(BAR));
}
@Test
public void testGetSeries() {
cm = new ChartModel(1, FOO, BAR, ZOO);
public void testGetLastDataPoint() {
cm = new ChartModel(FOO, BAR);
long time = System.currentTimeMillis();
cm.addDataPoint(time)
.data(FOO, VALUES1[0])
.data(BAR, VALUES2[0]);
cm.addDataPoint(time + 1)
.data(FOO, VALUES1[1])
.data(BAR, VALUES2[1]);
assertArrayEquals("series", SERIES, cm.getSeries());
assertEquals("Wrong result", VALUES1[1], cm.getLastDataPoint().get(FOO));
assertEquals("Wrong result", VALUES2[1], cm.getLastDataPoint().get(BAR));
}
}
......
/*
* Copyright 2016-present Open Networking Laboratory
*
* Licensed 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.onosproject.ui.chart;
import com.fasterxml.jackson.databind.node.ArrayNode;
import org.junit.Assert;
import org.junit.Test;
/**
* Unit tests for {@link ChartUtils}.
*/
public class ChartUtilsTest {
private static final String FOO = "foo";
private static final String BAR = "bar";
private static final String ARRAY_AS_STRING =
"[{\"foo\":1.0,\"bar\":2.0},{\"foo\":3.0,\"bar\":4.0}]";
@Test
public void basic() {
ChartModel cm = new ChartModel(FOO, BAR);
cm.addDataPoint(1L).data(FOO, 1D).data(BAR, 2D);
cm.addDataPoint(2L).data(FOO, 3D).data(BAR, 4D);
ArrayNode array = ChartUtils.generateDataPointArrayNode(cm);
Assert.assertEquals("wrong results", ARRAY_AS_STRING, array.toString());
}
}