/*
* Copyright 2014 Red Hat, Inc. and/or its affiliates.
*
* 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.dashbuilder.dataset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.dashbuilder.dataset.group.AggregateFunctionType;
import org.dashbuilder.dataset.group.ColumnGroup;
import org.dashbuilder.dataset.group.DataSetGroup;
import org.dashbuilder.dataset.group.GroupFunction;
import org.dashbuilder.dataset.impl.DataSetLookupBuilderImpl;
/**
* A set of constraints over the structure of a DataSetLookup instance.
*/
public class DataSetLookupConstraints extends DataSetConstraints<DataSetLookupConstraints> {
public static final int ERROR_GROUP_NUMBER = 200;
public static final int ERROR_GROUP_NOT_ALLOWED = 201;
public static final int ERROR_GROUP_REQUIRED = 203;
public static final int ERROR_DUPLICATED_COLUMN_ID = 204;
protected boolean uniqueColumnIds = false;
protected boolean groupAllowed = true;
protected boolean groupRequired = false;
protected int maxGroups = -1;
protected String groupsTitle = "Rows";
protected String columnsTitle = "Columns";
protected boolean groupColumn = false;
protected boolean functionRequired = false;
protected Map<Integer,String> columnTitleMap = new HashMap<Integer,String>();
public boolean isUniqueColumnIds() {
return uniqueColumnIds;
}
public DataSetLookupConstraints setUniqueColumnIds(boolean uniqueColumnIds) {
this.uniqueColumnIds = uniqueColumnIds;
return this;
}
public boolean isGroupAllowed() {
return groupAllowed;
}
public DataSetLookupConstraints setGroupAllowed(boolean groupAllowed) {
this.groupAllowed = groupAllowed;
return this;
}
public boolean isGroupRequired() {
return groupRequired;
}
public DataSetLookupConstraints setGroupRequired(boolean groupRequired) {
this.groupRequired = groupRequired;
return this;
}
public int getMaxGroups() {
return maxGroups;
}
public DataSetLookupConstraints setMaxGroups(int maxGroups) {
this.maxGroups = maxGroups;
return this;
}
public String getGroupsTitle() {
return groupsTitle;
}
public DataSetLookupConstraints setGroupsTitle(String groupsTitle) {
this.groupsTitle = groupsTitle;
return this;
}
public String getColumnsTitle() {
return columnsTitle;
}
public DataSetLookupConstraints setColumnsTitle(String columnsTitle) {
this.columnsTitle = columnsTitle;
return this;
}
public DataSetLookupConstraints setColumnTitle(Integer index, String title) {
columnTitleMap.put(index, title);
return this;
}
public String getColumnTitle(Integer index) {
return columnTitleMap.get(index);
}
public boolean isGroupColumn() {
return groupColumn;
}
public DataSetLookupConstraints setGroupColumn(boolean groupColumn) {
this.groupColumn = groupColumn;
return this;
}
public boolean isFunctionRequired() {
return functionRequired;
}
public DataSetLookupConstraints setFunctionRequired(boolean functionRequired) {
this.functionRequired = functionRequired;
return this;
}
public ValidationError check(DataSetLookup lookup) {
return check(lookup, null);
}
public ValidationError check(DataSetLookup lookup, DataSetMetadata metadata) {
List<DataSetGroup> grOps = lookup.getOperationList(DataSetGroup.class);
int lastGop = lookup.getLastGroupOpIndex(0);
if (!groupAllowed && lastGop != -1) {
DataSetGroup groupOp = lookup.getOperation(lastGop);
if (groupOp.getColumnGroup() != null) {
return createValidationError(ERROR_GROUP_NOT_ALLOWED);
}
}
if (groupRequired && lastGop == -1) {
return createValidationError(ERROR_GROUP_REQUIRED);
}
if (maxGroups != -1 && grOps.size() > maxGroups) {
return createValidationError(ERROR_GROUP_NUMBER);
}
if (lastGop != -1) {
DataSetGroup groupOp = lookup.getOperation(lastGop);
if (groupRequired && groupOp.getColumnGroup() == null) {
return createValidationError(ERROR_GROUP_REQUIRED);
}
List<GroupFunction> groupFunctions = groupOp.getGroupFunctions();
if (minColumns != -1 && groupFunctions.size() < minColumns) {
return createValidationError(ERROR_COLUMN_NUMBER);
}
if (maxColumns != -1 && groupFunctions.size() > maxColumns) {
return createValidationError(ERROR_COLUMN_NUMBER);
}
if (uniqueColumnIds) {
Set<String> columnIds = new HashSet<String>();
for (GroupFunction groupFunction : groupFunctions) {
String columnId = groupFunction.getColumnId();
if (columnId != null) {
if (columnIds.contains(columnId)) {
return createValidationError(ERROR_DUPLICATED_COLUMN_ID, columnId);
} else {
columnIds.add(columnId);
}
}
}
}
if (metadata != null) {
int currentColumns = -1;
boolean ok = false;
ValidationError error = null;
for (ColumnType[] types : columnTypeList) {
if (currentColumns < 0 || currentColumns < types.length) {
currentColumns = types.length;
}
error = checkTypes(metadata, groupOp, types);
if (!ok && error == null) {
ok = true;
}
}
if (!ok) {
return error;
}
// Check extra columns type
if (currentColumns > 0 && extraColumnsAllowed && extraColumnsType != null && groupFunctions.size() > currentColumns) {
for (int i = currentColumns; i < groupFunctions.size(); i++) {
GroupFunction gf = groupFunctions.get(i);
ColumnType columnType = metadata.getColumnType(gf.getSourceId());
if (!columnType.equals(extraColumnsType)) {
return createValidationError(ERROR_COLUMN_TYPE, i, extraColumnsType, columnType);
}
}
}
return null;
}
}
return null;
}
private ValidationError checkTypes(DataSetMetadata metadata, DataSetGroup groupOp, ColumnType[] types) {
ColumnGroup columnGroup = groupOp.getColumnGroup();
List<GroupFunction> groupFunctions = groupOp.getGroupFunctions();
for (int i = 0; i < groupFunctions.size(); i++) {
GroupFunction gf = groupFunctions.get(i);
ColumnType columnType = metadata.getColumnType(gf.getSourceId());
if (i < types.length && !columnType.equals(types[i])) {
boolean isGroupColumn = columnGroup != null && columnGroup.getSourceId().equals(gf.getSourceId());
boolean isGroupLabel = isGroupColumn && types[i].equals(ColumnType.LABEL);
boolean isFunctionColumn = gf.getFunction() != null && !columnType.equals(ColumnType.NUMBER);
if (!isGroupLabel && !isFunctionColumn) {
return createValidationError(ERROR_COLUMN_TYPE, i, types[i], columnType);
}
}
}
return null;
}
protected ValidationError createValidationError(int error, Object... params) {
switch (error) {
case ERROR_GROUP_NOT_ALLOWED:
return new ValidationError(error, "Group not allowed");
case ERROR_GROUP_REQUIRED:
String groupColumn = groupsTitle != null ? groupsTitle : "Group";
return new ValidationError(error, groupColumn + " column required");
case ERROR_GROUP_NUMBER:
return new ValidationError(error, "Max. groups allowed exceeded " + maxGroups);
case ERROR_DUPLICATED_COLUMN_ID:
String columnId = (String) params[0];
return new ValidationError(error, "Column id '" + columnId + "' is duplicated");
}
return super.createValidationError(error, params);
}
public DataSetLookup newDataSetLookup(DataSetMetadata metatada) {
DataSetLookupBuilder<DataSetLookupBuilderImpl> builder = DataSetFactory.newDataSetLookupBuilder();
builder.dataset(metatada.getUUID());
Set<Integer> exclude = new HashSet<Integer>();
int startIndex = 0;
// A group lookup requires to add a group-ready column
if (groupRequired) {
int groupIdx = getGroupColumn(metatada);
if (groupIdx == -1) {
throw new IllegalStateException("The data set does not contains group-able columns (label or date)");
}
// Add the group column
exclude.add(groupIdx);
builder.group(metatada.getColumnId(groupIdx));
builder.column(metatada.getColumnId(groupIdx));
startIndex = 1;
}
// If no target columns has been specified then take them all
ColumnType[] types = getColumnTypes();
if (types == null || types.length == 0) {
if (maxColumns > 0 && maxColumns < metatada.getNumberOfColumns()) {
types = new ColumnType[maxColumns];
}
else {
types = new ColumnType[metatada.getNumberOfColumns()];
}
for (int i = 0; i < types.length; i++) {
types[i] = metatada.getColumnType(i);
}
}
// Add the columns to the lookup
for (int i=startIndex; i<types.length; i++) {
ColumnType targetType = types[i];
// Do the best to get a new (not already added) column for the targetType.
int idx = getTargetColumn(metatada, targetType, exclude);
// Otherwise, get the first column available.
if (idx == -1) {
idx = getTargetColumn(metatada, exclude);
}
String columnId = metatada.getColumnId(idx);
ColumnType columnType = metatada.getColumnType(idx);
exclude.add(idx);
DataSetLookup currentLookup = builder.buildLookup();
String uniqueColumnId = buildUniqueColumnId(currentLookup, columnId);
String uniqueCountId = buildUniqueColumnId(currentLookup, "#items");
if (ColumnType.NUMBER.equals(targetType)) {
if (groupRequired || functionRequired) {
if (ColumnType.NUMBER.equals(columnType)) {
builder.column(columnId, AggregateFunctionType.SUM, uniqueColumnId);
} else {
builder.column(AggregateFunctionType.COUNT, uniqueCountId);
}
} else {
builder.column(columnId, uniqueColumnId);
}
} else {
if (functionRequired) {
builder.column(AggregateFunctionType.COUNT, uniqueCountId);
} else {
builder.column(columnId, uniqueColumnId);
}
}
}
return builder.buildLookup();
}
public String buildUniqueColumnId(DataSetLookup lookup, String targetId) {
return buildUniqueColumnId(lookup, new GroupFunction(targetId, targetId, null));
}
public String buildUniqueColumnId(DataSetLookup lookup, GroupFunction column) {
String targetId = column.getSourceId();
int lastGop = lookup.getLastGroupOpIndex(0);
if (lastGop != -1) {
DataSetGroup groupOp = lookup.getOperation(lastGop);
List<GroupFunction> columnList = groupOp.getGroupFunctions();
String newColumnId = targetId;
int counter = 1;
while (true) {
boolean unique = true;
for (int i=0; i<columnList.size() && unique; i++) {
GroupFunction gf = columnList.get(i);
if (gf != column && newColumnId.equals(gf.getColumnId())) {
newColumnId = targetId + "_" + (++counter);
unique = false;
}
}
if (unique) {
return newColumnId;
}
}
}
return targetId;
}
private int getGroupColumn(DataSetMetadata metatada) {
for (int i=0; i<metatada.getNumberOfColumns(); i++) {
ColumnType type = metatada.getColumnType(i);
if (type.equals(ColumnType.LABEL)) return i;
}
for (int i=0; i<metatada.getNumberOfColumns(); i++) {
ColumnType type = metatada.getColumnType(i);
if (type.equals(ColumnType.DATE)) return i;
}
return -1;
}
private int getTargetColumn(DataSetMetadata metatada, ColumnType type, Set<Integer> exclude) {
int target = -1;
for (int i=0; i<metatada.getNumberOfColumns(); i++) {
if (type.equals(metatada.getColumnType(i))) {
if (target == -1) {
target = i;
}
if (!exclude.contains(i)) {
return i;
}
}
}
return target;
}
private int getTargetColumn(DataSetMetadata metatada, Set<Integer> exclude) {
for (int i=0; i<metatada.getNumberOfColumns(); i++) {
if (!exclude.contains(i)) {
return i;
}
}
return 0;
}
}