/* * DBeaver - Universal Database Manager * Copyright (C) 2010-2017 Serge Rider (serge@jkiss.org) * * 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.jkiss.dbeaver.ui.controls.resultset; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.swt.graphics.Color; import org.jkiss.code.NotNull; import org.jkiss.code.Nullable; import org.jkiss.dbeaver.Log; import org.jkiss.dbeaver.model.DBPDataKind; import org.jkiss.dbeaver.model.DBUtils; import org.jkiss.dbeaver.model.data.*; import org.jkiss.dbeaver.model.exec.*; import org.jkiss.dbeaver.model.exec.trace.DBCTrace; import org.jkiss.dbeaver.model.runtime.AbstractJob; import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor; import org.jkiss.dbeaver.model.struct.DBSAttributeBase; import org.jkiss.dbeaver.model.struct.DBSDataManipulator; import org.jkiss.dbeaver.model.struct.DBSEntity; import org.jkiss.dbeaver.model.virtual.DBVColorOverride; import org.jkiss.dbeaver.model.virtual.DBVEntity; import org.jkiss.dbeaver.model.virtual.DBVUtils; import org.jkiss.dbeaver.ui.UIUtils; import org.jkiss.utils.ArrayUtils; import org.jkiss.utils.CommonUtils; import java.util.*; /** * Result set model */ public class ResultSetModel { private static final Log log = Log.getLog(ResultSetModel.class); // Attributes private DBDAttributeBinding[] attributes = new DBDAttributeBinding[0]; private List<DBDAttributeBinding> visibleAttributes = new ArrayList<>(); private DBDAttributeBinding documentAttribute = null; private DBDDataFilter dataFilter; private boolean singleSourceCells; private DBCExecutionSource executionSource; // Data private List<ResultSetRow> curRows = new ArrayList<>(); private Long totalRowCount = null; private int changesCount = 0; private volatile boolean hasData = false; // Flag saying that edited values update is in progress private volatile boolean updateInProgress = false; // Coloring private Map<DBDAttributeBinding, List<AttributeColorSettings>> colorMapping = new HashMap<>(); private DBCStatistics statistics; private DBCTrace trace; private transient boolean metadataChanged; private transient boolean metadataDynamic; public static class AttributeColorSettings { private DBCLogicalOperator operator; private Object[] attributeValues; private Color colorForeground; private Color colorBackground; public AttributeColorSettings(DBVColorOverride co) { this.operator = co.getOperator(); this.colorForeground = UIUtils.getSharedColor(co.getColorForeground()); this.colorBackground = UIUtils.getSharedColor(co.getColorBackground()); this.attributeValues = co.getAttributeValues(); } public DBCLogicalOperator getOperator() { return operator; } public Object[] getAttributeValues() { return attributeValues; } public Color getColorForeground() { return colorForeground; } public Color getColorBackground() { return colorBackground; } public boolean evaluate(Object cellValue) { return operator.evaluate(cellValue, attributeValues); } } private final Comparator<DBDAttributeBinding> POSITION_SORTER = new Comparator<DBDAttributeBinding>() { @Override public int compare(DBDAttributeBinding o1, DBDAttributeBinding o2) { final DBDAttributeConstraint c1 = dataFilter.getConstraint(o1); final DBDAttributeConstraint c2 = dataFilter.getConstraint(o2); if (c1 == null) { log.debug("Missing constraint for " + o1); return -1; } if (c2 == null) { log.debug("Missing constraint for " + o2); return 1; } return c1.getVisualPosition() - c2.getVisualPosition(); } }; public ResultSetModel() { dataFilter = createDataFilter(); } @NotNull public DBDDataFilter createDataFilter() { fillVisibleAttributes(); List<DBDAttributeConstraint> constraints = new ArrayList<>(attributes.length); for (DBDAttributeBinding binding : attributes) { addConstraints(constraints, binding); } return new DBDDataFilter(constraints); } private void addConstraints(List<DBDAttributeConstraint> constraints, DBDAttributeBinding binding) { DBDAttributeConstraint constraint = new DBDAttributeConstraint(binding); constraint.setVisible(visibleAttributes.contains(binding) || binding.getParentObject() != null); constraints.add(constraint); List<DBDAttributeBinding> nestedBindings = binding.getNestedBindings(); if (nestedBindings != null) { for (DBDAttributeBinding nested : nestedBindings) { addConstraints(constraints, nested); } } } public boolean isSingleSource() { return singleSourceCells; } /** * Returns single source of this result set. Usually it is a table. * If result set is a result of joins or contains synthetic attributes then * single source is null. If driver doesn't support meta information * for queries then is will null. * * @return single source entity */ @Nullable public DBSEntity getSingleSource() { if (!singleSourceCells) { return null; } for (DBDAttributeBinding attr : attributes) { DBDRowIdentifier rowIdentifier = attr.getRowIdentifier(); if (rowIdentifier != null) { return rowIdentifier.getEntity(); } } return null; } public void resetCellValue(DBDAttributeBinding attr, ResultSetRow row) { if (row.getState() == ResultSetRow.STATE_REMOVED) { row.setState(ResultSetRow.STATE_NORMAL); } else if (row.changes != null && row.changes.containsKey(attr)) { DBUtils.resetValue(getCellValue(attr, row)); updateCellValue(attr, row, row.changes.get(attr), false); row.resetChange(attr); if (row.getState() == ResultSetRow.STATE_NORMAL) { changesCount--; } } } public void refreshChangeCount() { changesCount = 0; for (ResultSetRow row : curRows) { if (row.getState() != ResultSetRow.STATE_NORMAL) { changesCount++; } else if (row.changes != null) { changesCount += row.changes.size(); } } } public DBDAttributeBinding getDocumentAttribute() { return documentAttribute; } @NotNull public DBDAttributeBinding[] getAttributes() { return attributes; } @NotNull public DBDAttributeBinding getAttribute(int index) { return attributes[index]; } @NotNull public List<DBDAttributeBinding> getVisibleAttributes() { return visibleAttributes; } public int getVisibleAttributeCount() { return visibleAttributes.size(); } @Nullable public List<DBDAttributeBinding> getVisibleAttributes(DBDAttributeBinding parent) { final List<DBDAttributeBinding> nestedBindings = parent.getNestedBindings(); if (nestedBindings == null || nestedBindings.isEmpty()) { return null; } List<DBDAttributeBinding> result = new ArrayList<>(nestedBindings); for (Iterator<DBDAttributeBinding> iter = result.iterator(); iter.hasNext(); ) { final DBDAttributeConstraint constraint = dataFilter.getConstraint(iter.next()); if (constraint == null || !constraint.isVisible()) { iter.remove(); } } return result; } @NotNull public DBDAttributeBinding getVisibleAttribute(int index) { return visibleAttributes.get(index); } public void setAttributeVisibility(@NotNull DBDAttributeBinding attribute, boolean visible) { DBDAttributeConstraint constraint = dataFilter.getConstraint(attribute); if (constraint != null && constraint.isVisible() != visible) { constraint.setVisible(visible); if (attribute.getParentObject() == null) { if (visible) { visibleAttributes.add(attribute); } else { visibleAttributes.remove(attribute); } } } } @Nullable public DBDAttributeBinding getAttributeBinding(@Nullable DBSAttributeBase attribute) { return DBUtils.findBinding(attributes, attribute); } @Nullable DBDAttributeBinding getAttributeBinding(@Nullable DBSEntity entity, @NotNull String attrName) { for (DBDAttributeBinding attribute : visibleAttributes) { DBDRowIdentifier rowIdentifier = attribute.getRowIdentifier(); if ((entity == null || (rowIdentifier != null && rowIdentifier.getEntity() == entity)) && attribute.getName().equals(attrName)) { return attribute; } } return null; } public boolean isEmpty() { return getRowCount() <= 0 || visibleAttributes.size() <= 0; } public int getRowCount() { return curRows.size(); } @NotNull public List<ResultSetRow> getAllRows() { return curRows; } @NotNull public Object[] getRowData(int index) { return curRows.get(index).values; } @NotNull public ResultSetRow getRow(int index) { return curRows.get(index); } public Long getTotalRowCount() { return totalRowCount; } void setTotalRowCount(Long totalRowCount) { this.totalRowCount = totalRowCount; } @Nullable public Object getCellValue(@NotNull DBDAttributeBinding attribute, @NotNull ResultSetRow row) { int depth = attribute.getLevel(); if (depth == 0) { final int index = attribute.getOrdinalPosition(); if (index >= row.values.length) { log.debug("Bad attribute - index out of row values' bounds"); return null; } else { return row.values[index]; } } Object curValue = row.values[attribute.getTopParent().getOrdinalPosition()]; for (int i = 0; i < depth; i++) { if (curValue == null) { break; } DBDAttributeBinding attr = attribute.getParent(depth - i - 1); assert attr != null; try { curValue = attr.extractNestedValue(curValue); } catch (DBCException e) { log.debug("Error reading nested value of [" + attr.getName() + "]", e); curValue = null; break; } } return curValue; } /** * Updates cell value. Saves previous value. * * @param attr Attribute * @param row row index * @param value new value * @return true on success */ public boolean updateCellValue(@NotNull DBDAttributeBinding attr, @NotNull ResultSetRow row, @Nullable Object value) { return updateCellValue(attr, row, value, true); } public boolean updateCellValue(@NotNull DBDAttributeBinding attr, @NotNull ResultSetRow row, @Nullable Object value, boolean updateChanges) { int depth = attr.getLevel(); int rootIndex; if (depth == 0) { rootIndex = attr.getOrdinalPosition(); } else { rootIndex = attr.getTopParent().getOrdinalPosition(); } Object rootValue = row.values[rootIndex]; Object ownerValue = depth > 0 ? rootValue : null; { // Obtain owner value and create all intermediate values for (int i = 0; i < depth; i++) { if (ownerValue == null) { // Create new owner object log.warn("Null owner value"); return false; } if (i == depth - 1) { break; } DBDAttributeBinding ownerAttr = attr.getParent(depth - i - 1); assert ownerAttr != null; try { ownerValue = ownerAttr.extractNestedValue(ownerValue); } catch (DBCException e) { log.warn("Error getting field [" + ownerAttr.getName() + "] value", e); return false; } } } // Get old value Object oldValue = rootValue; if (ownerValue != null) { try { oldValue = attr.extractNestedValue(ownerValue); } catch (DBCException e) { log.error("Error getting [" + attr.getName() + "] value", e); } } if ((value instanceof DBDValue && value == oldValue && ((DBDValue) value).isModified()) || !CommonUtils.equalObjects(oldValue, value)) { // If DBDValue was updated (kind of CONTENT?) or actual value was changed if (ownerValue == null && DBUtils.isNullValue(oldValue) && DBUtils.isNullValue(value)) { // Both nulls - nothing to update return false; } // Do not add edited cell for new/deleted rows if (row.getState() == ResultSetRow.STATE_NORMAL) { boolean cellWasEdited = row.changes != null && row.changes.containsKey(attr); Object oldOldValue = !cellWasEdited ? null : row.changes.get(attr); if (cellWasEdited && !CommonUtils.equalObjects(oldValue, oldOldValue) && !CommonUtils.equalObjects(oldValue, value)) { // Value rewrite - release previous stored old value DBUtils.releaseValue(oldValue); } else if (updateChanges) { if (value instanceof DBDValue || !CommonUtils.equalObjects(value, oldValue)) { row.addChange(attr, oldValue); } else { updateChanges = false; } } if (updateChanges && row.getState() == ResultSetRow.STATE_NORMAL && !cellWasEdited) { changesCount++; } } if (ownerValue != null) { if (ownerValue instanceof DBDComposite) { ((DBDComposite) ownerValue).setAttributeValue(attr.getAttribute(), value); } else { log.warn("Value [" + ownerValue + "] edit is not supported"); } } else { row.values[rootIndex] = value; } return true; } return false; } boolean isDynamicMetadata() { return metadataDynamic; } boolean isMetadataChanged() { return metadataChanged; } /** * Sets new metadata of result set * * @param resultSet resultset * @param newAttributes attributes metadata */ public void setMetaData(@NotNull DBCResultSet resultSet, @NotNull DBDAttributeBinding[] newAttributes) { boolean update = false; DBCStatement sourceStatement = resultSet.getSourceStatement(); if (sourceStatement != null) { this.executionSource = sourceStatement.getStatementSource(); } else { this.executionSource = null; } if (resultSet instanceof DBCResultSetTrace) { this.trace = ((DBCResultSetTrace) resultSet).getExecutionTrace(); } else { this.trace = null; } if (this.attributes == null || this.attributes.length == 0 || this.attributes.length != newAttributes.length || isDynamicMetadata()) { update = true; } else { for (int i = 0; i < this.attributes.length; i++) { if (!ResultSetUtils.equalAttributes(this.attributes[i].getMetaAttribute(), newAttributes[i].getMetaAttribute())) { update = true; break; } } } this.metadataChanged = update; if (update) { if (!ArrayUtils.isEmpty(this.attributes) && !ArrayUtils.isEmpty(newAttributes) && isDynamicMetadata() && this.attributes[0].getTopParent().getMetaAttribute().getSource() == newAttributes[0].getTopParent().getMetaAttribute().getSource()) { // the same source metadataChanged = false; } else { metadataChanged = true; } } this.clearData(); this.attributes = newAttributes; this.documentAttribute = null; metadataDynamic = this.attributes.length > 0 && this.attributes[0].getTopParent().getDataSource().getInfo().isDynamicMetadata(); { // Detect document attribute // It has to be only one attribute in list (excluding pseudo attributes). DBDAttributeBinding realAttr = null; if (attributes.length == 1) { realAttr = attributes[0]; } else { for (DBDAttributeBinding attr : attributes) { if (!attr.isPseudoAttribute()) { if (realAttr != null) { // more than one realAttr = null; break; } realAttr = attr; } } } if (realAttr != null) { if (realAttr.getDataKind() == DBPDataKind.DOCUMENT || realAttr.getDataKind() == DBPDataKind.CONTENT) { documentAttribute = realAttr; } } updateColorMapping(); } } public void setData(@NotNull List<Object[]> rows) { // Clear previous data this.clearData(); { // Extract nested attributes from single top-level attribute if (attributes.length == 1) { DBDAttributeBinding topAttr = attributes[0]; if (topAttr.getDataKind() == DBPDataKind.DOCUMENT || topAttr.getDataKind() == DBPDataKind.STRUCT) { List<DBDAttributeBinding> nested = topAttr.getNestedBindings(); if (nested != null && !nested.isEmpty()) { attributes = nested.toArray(new DBDAttributeBinding[nested.size()]); fillVisibleAttributes(); } } } } // Add new data appendData(rows); if (metadataChanged) { this.dataFilter = createDataFilter(); } else { DBDDataFilter prevFilter = dataFilter; this.dataFilter = createDataFilter(); updateDataFilter(prevFilter); } Collections.sort(this.visibleAttributes, POSITION_SORTER); { // Check single source flag this.singleSourceCells = true; DBSEntity sourceTable = null; for (DBDAttributeBinding attribute : visibleAttributes) { if (attribute.isPseudoAttribute()) { continue; } DBDRowIdentifier rowIdentifier = attribute.getRowIdentifier(); if (rowIdentifier != null) { if (sourceTable == null) { sourceTable = rowIdentifier.getEntity(); } else if (sourceTable != rowIdentifier.getEntity()) { singleSourceCells = false; break; } } else { // Do not mark it a multi-source. // It is just some column without identifier, probably a constant or an expression //singleSourceCells = false; //break; } } updateColorMapping(); } hasData = true; } boolean hasColorMapping(DBDAttributeBinding binding) { return colorMapping.containsKey(binding); } void updateColorMapping() { colorMapping.clear(); DBSEntity entity = getSingleSource(); if (entity == null) { return; } DBVEntity virtualEntity = DBVUtils.findVirtualEntity(entity, false); if (virtualEntity != null) { List<DBVColorOverride> coList = virtualEntity.getColorOverrides(); if (!CommonUtils.isEmpty(coList)) { for (DBVColorOverride co : coList) { DBDAttributeBinding binding = getAttributeBinding(entity, co.getAttributeName()); if (binding != null) { List<AttributeColorSettings> cmList = colorMapping.get(binding); if (cmList == null) { cmList = new ArrayList<>(); colorMapping.put(binding, cmList); } cmList.add(new AttributeColorSettings(co)); } } } } updateRowColors(curRows); } private void updateRowColors(List<ResultSetRow> rows) { if (colorMapping.isEmpty()) { for (ResultSetRow row : rows) { row.foreground = null; row.background = null; } } else { for (Map.Entry<DBDAttributeBinding, List<AttributeColorSettings>> entry : colorMapping.entrySet()) { for (ResultSetRow row : rows) { final DBDAttributeBinding binding = entry.getKey(); final Object cellValue = getCellValue(binding, row); //final String cellStringValue = binding.getValueHandler().getValueDisplayString(binding, cellValue, DBDDisplayFormat.NATIVE); for (AttributeColorSettings acs : entry.getValue()) { if (acs.evaluate(cellValue)) { row.foreground = acs.colorForeground; row.background = acs.colorBackground; break; } } } } } } public void appendData(@NotNull List<Object[]> rows) { int rowCount = rows.size(); int firstRowNum = curRows.size(); List<ResultSetRow> newRows = new ArrayList<>(rowCount); for (int i = 0; i < rowCount; i++) { newRows.add( new ResultSetRow(firstRowNum + i, rows.get(i))); } curRows.addAll(newRows); updateRowColors(newRows); } void clearData() { // Refresh all rows this.releaseAll(); hasData = false; } public boolean hasData() { return hasData; } public boolean isDirty() { return changesCount != 0; } public boolean isAttributeReadOnly(@NotNull DBDAttributeBinding attribute) { if (!isSingleSource()) { return true; } if (attribute.getMetaAttribute().isReadOnly()) { return true; } DBDRowIdentifier rowIdentifier = attribute.getRowIdentifier(); if (rowIdentifier == null || !(rowIdentifier.getEntity() instanceof DBSDataManipulator)) { return true; } DBSDataManipulator dataContainer = (DBSDataManipulator) rowIdentifier.getEntity(); return (dataContainer.getSupportedFeatures() & DBSDataManipulator.DATA_UPDATE) == 0; } public boolean isUpdateInProgress() { return updateInProgress; } void setUpdateInProgress(boolean updateInProgress) { this.updateInProgress = updateInProgress; } @NotNull ResultSetRow addNewRow(int rowNum, @NotNull Object[] data) { ResultSetRow newRow = new ResultSetRow(curRows.size(), data); newRow.setVisualNumber(rowNum); newRow.setState(ResultSetRow.STATE_ADDED); shiftRows(newRow, 1); curRows.add(rowNum, newRow); changesCount++; return newRow; } /** * Removes row with specified index from data * * @param row row * @return true if row was physically removed (only in case if this row was previously added) * or false if it just marked as deleted */ boolean deleteRow(@NotNull ResultSetRow row) { if (row.getState() == ResultSetRow.STATE_ADDED) { cleanupRow(row); return true; } else { // Mark row as deleted row.setState(ResultSetRow.STATE_REMOVED); changesCount++; return false; } } void cleanupRow(@NotNull ResultSetRow row) { row.release(); this.curRows.remove(row.getVisualNumber()); this.shiftRows(row, -1); } boolean cleanupRows(Collection<ResultSetRow> rows) { if (rows != null && !rows.isEmpty()) { // Remove rows (in descending order to prevent concurrent modification errors) List<ResultSetRow> rowsToRemove = new ArrayList<>(rows); Collections.sort(rowsToRemove, new Comparator<ResultSetRow>() { @Override public int compare(ResultSetRow o1, ResultSetRow o2) { return o1.getVisualNumber() - o2.getVisualNumber(); } }); for (ResultSetRow row : rowsToRemove) { cleanupRow(row); } return true; } else { return false; } } private void shiftRows(@NotNull ResultSetRow relative, int delta) { for (ResultSetRow row : curRows) { if (row.getVisualNumber() >= relative.getVisualNumber()) { row.setVisualNumber(row.getVisualNumber() + delta); } if (row.getRowNumber() >= relative.getRowNumber()) { row.setRowNumber(row.getRowNumber() + delta); } } } private void releaseAll() { final List<ResultSetRow> oldRows = curRows; this.curRows = new ArrayList<>(); this.totalRowCount = null; // Cleanup in separate job. // Sometimes model cleanup takes much time (e.g. freeing LOB values) // So let's do it in separate job to avoid UI locking new AbstractJob("Cleanup model") { { setSystem(true); } @Override protected IStatus run(DBRProgressMonitor monitor) { for (ResultSetRow row : oldRows) { row.release(); } return Status.OK_STATUS; } }.schedule(); } public DBDDataFilter getDataFilter() { return dataFilter; } /** * Sets new data filter * * @param dataFilter data filter * @return true if visible attributes were changed. Spreadsheet has to be refreshed */ boolean setDataFilter(DBDDataFilter dataFilter) { this.dataFilter = dataFilter; // Check if filter misses some attributes List<DBDAttributeConstraint> newConstraints = new ArrayList<>(); for (DBDAttributeBinding binding : attributes) { if (dataFilter.getConstraint(binding) == null) { addConstraints(newConstraints, binding); } } if (!newConstraints.isEmpty()) { dataFilter.addConstraints(newConstraints); } List<DBDAttributeBinding> newBindings = new ArrayList<>(); for (DBSAttributeBase attr : this.dataFilter.getOrderedVisibleAttributes()) { DBDAttributeBinding binding = getAttributeBinding(attr); if (binding != null) { newBindings.add(binding); } } if (!newBindings.equals(visibleAttributes)) { visibleAttributes = newBindings; return true; } return false; } void updateDataFilter(DBDDataFilter filter) { this.visibleAttributes.clear(); Collections.addAll(this.visibleAttributes, this.attributes); for (DBDAttributeConstraint constraint : filter.getConstraints()) { DBDAttributeConstraint filterConstraint = this.dataFilter.getConstraint(constraint.getAttribute(), true); if (filterConstraint == null) { //log.warn("Constraint for attribute [" + constraint.getAttribute().getName() + "] not found"); continue; } if (constraint.getOperator() != null) { filterConstraint.setOperator(constraint.getOperator()); filterConstraint.setReverseOperator(constraint.isReverseOperator()); filterConstraint.setValue(constraint.getValue()); } else { filterConstraint.setCriteria(constraint.getCriteria()); } filterConstraint.setOrderPosition(constraint.getOrderPosition()); filterConstraint.setOrderDescending(constraint.isOrderDescending()); filterConstraint.setVisible(constraint.isVisible()); filterConstraint.setVisualPosition(constraint.getVisualPosition()); if (filterConstraint.getAttribute() instanceof DBDAttributeBinding) { if (!constraint.isVisible()) { visibleAttributes.remove(filterConstraint.getAttribute()); } else { if (!visibleAttributes.contains(filterConstraint.getAttribute())) { DBDAttributeBinding attribute = (DBDAttributeBinding) filterConstraint.getAttribute(); if (attribute.getParentObject() == null) { // Add only root attributes visibleAttributes.add(attribute); } } } } } if (filter.getConstraints().size() != attributes.length) { // Update visibility for (Iterator<DBDAttributeBinding> iter = visibleAttributes.iterator(); iter.hasNext(); ) { final DBDAttributeBinding attr = iter.next(); if (filter.getConstraint(attr, true) == null) { // No constraint for this attribute: use default visibility if (!isVisibleByDefault(attr)) { iter.remove(); } } } } Collections.sort(this.visibleAttributes, POSITION_SORTER); this.dataFilter.setWhere(filter.getWhere()); this.dataFilter.setOrder(filter.getOrder()); this.dataFilter.setAnyConstraint(filter.isAnyConstraint()); } public void resetOrdering() { final boolean hasOrdering = dataFilter.hasOrdering(); // Sort locally final List<DBDAttributeConstraint> orderConstraints = dataFilter.getOrderConstraints(); Collections.sort(curRows, new Comparator<ResultSetRow>() { @Override public int compare(ResultSetRow row1, ResultSetRow row2) { if (!hasOrdering) { return row1.getRowNumber() - row2.getRowNumber(); } int result = 0; for (DBDAttributeConstraint co : orderConstraints) { final DBDAttributeBinding binding = getAttributeBinding(co.getAttribute()); if (binding == null) { continue; } Object cell1 = getCellValue(binding, row1); Object cell2 = getCellValue(binding, row2); if (cell1 == cell2) { result = 0; } else if (DBUtils.isNullValue(cell1)) { result = 1; } else if (DBUtils.isNullValue(cell2)) { result = -1; } else if (cell1 instanceof Comparable) { result = ((Comparable) cell1).compareTo(cell2); } else { String str1 = String.valueOf(cell1); String str2 = String.valueOf(cell2); result = str1.compareTo(str2); } if (co.isOrderDescending()) { result = -result; } if (result != 0) { break; } } return result; } }); for (int i = 0; i < curRows.size(); i++) { curRows.get(i).setVisualNumber(i); } } private void fillVisibleAttributes() { this.visibleAttributes.clear(); if (executionSource != null && executionSource.getDataContainer() instanceof DBSEntity) { // Filter pseudo attributes if we query single entity for (DBDAttributeBinding binding : this.attributes) { if (isVisibleByDefault(binding)) { // Make visible "real" attributes this.visibleAttributes.add(binding); } } } else { Collections.addAll(this.visibleAttributes, this.attributes); } } private static boolean isVisibleByDefault(DBDAttributeBinding binding) { return !binding.isPseudoAttribute(); } public DBCStatistics getStatistics() { return statistics; } public void setStatistics(DBCStatistics statistics) { this.statistics = statistics; } public DBCTrace getTrace() { return trace; } }