/* * 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; } }