/*
* ARX: Powerful Data Anonymization
* Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors
*
* 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.deidentifier.arx.gui.view.impl.define;
import java.util.ArrayList;
import java.util.List;
import org.deidentifier.arx.AttributeType;
import org.deidentifier.arx.AttributeType.Hierarchy;
import org.deidentifier.arx.AttributeType.MicroAggregationFunctionDescription;
import org.deidentifier.arx.DataDefinition;
import org.deidentifier.arx.DataScale;
import org.deidentifier.arx.gui.Controller;
import org.deidentifier.arx.gui.model.Model;
import org.deidentifier.arx.gui.model.ModelAuditTrailEntry.AuditTrailEntryFindReplace;
import org.deidentifier.arx.gui.model.ModelEvent;
import org.deidentifier.arx.gui.model.ModelEvent.ModelPart;
import org.deidentifier.arx.gui.model.ModelRiskBasedCriterion;
import org.deidentifier.arx.gui.model.ModelTransformationMode;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.SWTUtil;
import org.deidentifier.arx.gui.view.def.IView;
import org.deidentifier.arx.gui.view.impl.common.ComponentHierarchy;
import org.deidentifier.arx.gui.view.impl.common.ComponentHierarchyMenu;
import org.deidentifier.arx.gui.view.impl.common.ComponentMultiStack;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.ModifyEvent;
import org.eclipse.swt.events.ModifyListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Combo;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Label;
/**
* This view displays basic attribute information.
*
* @author Fabian Prasser
*/
public class ViewAttributeTransformation implements IView {
/** Resource */
private static final AttributeType[] COMBO1_TYPES = new AttributeType[] {
AttributeType.INSENSITIVE_ATTRIBUTE,
AttributeType.SENSITIVE_ATTRIBUTE,
AttributeType.QUASI_IDENTIFYING_ATTRIBUTE,
AttributeType.IDENTIFYING_ATTRIBUTE };
/** Resource */
private static final String[] COMBO1_VALUES = new String[] {
Resources.getMessage("AttributeDefinitionView.0"), //$NON-NLS-1$
Resources.getMessage("AttributeDefinitionView.1"), //$NON-NLS-1$
Resources.getMessage("AttributeDefinitionView.2"), //$NON-NLS-1$
Resources.getMessage("AttributeDefinitionView.3") }; //$NON-NLS-1$
/** Resource */
private static final List<MicroAggregationFunctionDescription> FUNCTIONS = AttributeType.listMicroAggregationFunctions();
/** Resource. */
private static final String ITEM_ALL = Resources.getMessage("HierarchyView.0"); //$NON-NLS-1$
/** Model */
private String attribute = null;
/** Model */
private Model model;
/** Controller */
private final Controller controller;
/** Widget */
private final Combo cmbType;
/** Widget */
private final Combo cmbMode;
/** Widget */
private final Combo cmbFunction;
/** Widget */
private final Button btnMissing;
/** Widget. */
private final Combo cmbMin;
/** Widget. */
private final Combo cmbMax;
/** Widget. */
private final ComponentMultiStack stack;
/** View */
private final Composite root;
/** View */
private final ComponentHierarchyMenu menu;
/** View */
private final ComponentHierarchy hierarchy;
/**
* Constructor.
*
* @param parent
* @param attribute
* @param controller
*/
public ViewAttributeTransformation(final Composite parent,
final Controller controller) {
// Register
this.controller = controller;
this.controller.addListener(ModelPart.MODEL, this);
this.controller.addListener(ModelPart.INPUT, this);
this.controller.addListener(ModelPart.ATTRIBUTE_TYPE, this);
this.controller.addListener(ModelPart.ATTRIBUTE_VALUE, this);
this.controller.addListener(ModelPart.DATA_TYPE, this);
this.controller.addListener(ModelPart.HIERARCHY, this);
this.controller.addListener(ModelPart.SELECTED_ATTRIBUTE, this);
// Group
root = new Composite(parent, SWT.NULL);
root.setLayoutData(SWTUtil.createFillGridData());
final GridLayout groupInputGridLayout = new GridLayout();
groupInputGridLayout.numColumns = 1;
root.setLayout(groupInputGridLayout);
// Group
final Composite innerGroup = new Composite(root, SWT.NULL);
innerGroup.setLayoutData(SWTUtil.createFillHorizontallyGridData());
final GridLayout typeInputGridLayout = new GridLayout();
typeInputGridLayout.numColumns = 4;
innerGroup.setLayout(typeInputGridLayout);
// Combo for attribute type
final Label kLabel = new Label(innerGroup, SWT.PUSH);
kLabel.setText(Resources.getMessage("AttributeDefinitionView.7")); //$NON-NLS-1$
cmbType = new Combo(innerGroup, SWT.READ_ONLY);
cmbType.setLayoutData(SWTUtil.createFillGridData());
cmbType.setItems(COMBO1_VALUES);
cmbType.select(0);
cmbType.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionAttributeTypeChanged();
}
});
// Add combo for mode
final Label fLabel2 = new Label(innerGroup, SWT.PUSH);
fLabel2.setText(Resources.getMessage("ViewMicoaggregation.4")); //$NON-NLS-1$
cmbMode = new Combo(innerGroup, SWT.READ_ONLY);
cmbMode.setLayoutData(SWTUtil.createFillGridData());
cmbMode.setItems(new String[]{Resources.getMessage("ViewMicoaggregation.5"),
Resources.getMessage("ViewMicoaggregation.6")});
cmbMode.select(0);
cmbMode.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionTransformationModeChanged();
}
});
// Create multistack
stack = new ComponentMultiStack(innerGroup);
// First column
Composite first = stack.create(SWTUtil.createGridData());
Composite compositeLabelMin = new Composite(first, SWT.NONE);
GridLayout compositeLabelMinLayout = new GridLayout();
compositeLabelMinLayout.numColumns = 1;
compositeLabelMinLayout.marginLeft = 0;
compositeLabelMinLayout.marginRight = 0;
compositeLabelMinLayout.marginWidth = 0;
compositeLabelMin.setLayout(compositeLabelMinLayout);
Label labelMin = new Label(compositeLabelMin, SWT.PUSH);
labelMin.setText(Resources.getMessage("HierarchyView.4")); //$NON-NLS-1$
Composite compositelabelFunction = new Composite(first, SWT.NONE);
GridLayout compositelabelFunctionLayout = new GridLayout();
compositelabelFunctionLayout.numColumns = 1;
compositelabelFunctionLayout.marginLeft = 0;
compositelabelFunctionLayout.marginRight = 0;
compositelabelFunctionLayout.marginWidth = 0;
compositelabelFunction.setLayout(compositelabelFunctionLayout);
final Label labelFunction = new Label(compositelabelFunction, SWT.PUSH);
labelFunction.setText(Resources.getMessage("ViewMicoaggregation.0")); //$NON-NLS-1$
// Second column
Composite second = stack.create(SWTUtil.createFillHorizontallyGridData());
this.cmbMin = new Combo(second, SWT.READ_ONLY);
this.cmbMin.setLayoutData(SWTUtil.createFillHorizontallyGridData());
this.cmbMin.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionMinChanged();
}
});
this.cmbFunction = new Combo(second, SWT.READ_ONLY);
this.cmbFunction.setLayoutData(SWTUtil.createFillGridData());
this.cmbFunction.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionFunctionChanged();
}
});
// Third column
Composite third = stack.create(SWTUtil.createGridData());
Composite compositelabelMax = new Composite(third, SWT.NONE);
GridLayout compositelabelMaxLayout = new GridLayout();
compositelabelMaxLayout.numColumns = 1;
compositelabelMaxLayout.marginLeft = 0;
compositelabelMaxLayout.marginRight = 0;
compositelabelMaxLayout.marginWidth = 0;
compositelabelMax.setLayout(compositelabelMaxLayout);
Label labelMax = new Label(compositelabelMax, SWT.PUSH);
labelMax.setText(Resources.getMessage("HierarchyView.6")); //$NON-NLS-1$
Composite compositelabelMissing = new Composite(third, SWT.NONE);
GridLayout compositelabelMissingLayout = new GridLayout();
compositelabelMissingLayout.numColumns = 1;
compositelabelMissingLayout.marginLeft = 0;
compositelabelMissingLayout.marginRight = 0;
compositelabelMissingLayout.marginWidth = 0;
compositelabelMissing.setLayout(compositelabelMissingLayout);
Label labelMissing = new Label(compositelabelMissing, SWT.PUSH);
labelMissing.setText(Resources.getMessage("ViewMicoaggregation.7")); //$NON-NLS-1$
// Fourth column
Composite fourth = stack.create(SWTUtil.createFillHorizontallyGridData());
this.cmbMax = new Combo(fourth, SWT.READ_ONLY);
this.cmbMax.setLayoutData(SWTUtil.createFillHorizontallyGridData());
this.cmbMax.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionMaxChanged();
}
});
btnMissing = new Button(fourth, SWT.CHECK);
GridData btnMissingData = SWTUtil.createFillGridData();
btnMissingData.horizontalSpan = 2;
btnMissing.setLayoutData(btnMissingData);
btnMissing.setText(Resources.getMessage("ViewMicoaggregation.2")); //$NON-NLS-1$
btnMissing.setSelection(true);
btnMissing.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent arg0) {
actionMissingChanged();
}
});
// Collect info about children in stack
stack.pack();
stack.setLayer(0);
// Editor hierarchy
this.hierarchy = new ComponentHierarchy(root, new ModifyListener(){
@Override public void modifyText(ModifyEvent arg0) {
actionHierarchyChanged((Hierarchy)arg0.data);
}
});
this.menu = new ComponentHierarchyMenu(hierarchy, controller);
}
@Override
public void dispose() {
controller.removeListener(this);
menu.dispose();
}
@Override
public void reset() {
if (stack != null) stack.setLayer(0);
if (cmbType != null && cmbType.getItemCount() != 0) cmbType.select(0);
if (cmbMode != null && cmbMode.getItemCount() != 0) cmbMode.select(0);
if (cmbMin != null && cmbMin.getItemCount() != 0) cmbMin.select(0);
if (cmbMax != null && cmbMax.getItemCount() != 0) cmbMax.select(cmbMax.getItemCount() - 1);
if (cmbFunction != null && cmbFunction.getItemCount() != 0) cmbFunction.select(0);
if (hierarchy != null) hierarchy.actionReset();
SWTUtil.disable(root);
}
@Override
public void update(final ModelEvent event) {
if (event.part == ModelPart.SELECTED_ATTRIBUTE) {
if (model != null) {
SWTUtil.enable(root);
attribute = model.getSelectedAttribute();
updateAttributeType();
updateFunction();
updateMode();
updateMinMax();
hierarchy.setHierarchy(getHierarchy());
}
} else if (event.part == ModelPart.MODEL) {
SWTUtil.enable(root);
model = (Model) event.data;
attribute = model.getSelectedAttribute();
updateAttributeType();
updateFunction();
updateMode();
updateMinMax();
hierarchy.setHierarchy(getHierarchy());
} else if (event.part == ModelPart.ATTRIBUTE_TYPE) {
SWTUtil.enable(root);
final String attr = (String) event.data;
if (attr.equals(attribute)) {
updateAttributeType();
}
} else if (event.part == ModelPart.HIERARCHY) {
SWTUtil.enable(root);
if (attribute.equals(model.getSelectedAttribute())) {
hierarchy.setHierarchy(getHierarchy());
updateMinMax();
}
} else if (event.part == ModelPart.ATTRIBUTE_VALUE) {
SWTUtil.enable(root);
AuditTrailEntryFindReplace entry = (AuditTrailEntryFindReplace)event.data;
if (entry.getAttribute().equals(attribute)) {
hierarchy.setHierarchy(getHierarchy());
}
} else if (event.part == ModelPart.INPUT) {
SWTUtil.enable(root);
attribute = model.getSelectedAttribute();
updateAttributeType();
updateMode();
updateMinMax();
hierarchy.setHierarchy(getHierarchy());
} else if (event.part == ModelPart.DATA_TYPE) {
SWTUtil.enable(root);
if (attribute.equals(model.getSelectedAttribute())) {
updateFunction();
updateMode();
}
} else if (event.part == ModelPart.ATTRIBUTE_VALUE) {
SWTUtil.enable(root);
if (attribute.equals(model.getSelectedAttribute())) {
hierarchy.setHierarchy(getHierarchy());
}
}
}
/**
* Attribute type changed
*/
private void actionAttributeTypeChanged() {
if ((cmbType.getSelectionIndex() != -1) && (attribute != null)) {
if ((model != null) && (model.getInputConfig().getInput() != null)) {
final AttributeType type = COMBO1_TYPES[cmbType.getSelectionIndex()];
final DataDefinition definition = model.getInputDefinition();
// Handle QIs
if (type == null) {
definition.setAttributeType(attribute, Hierarchy.create());
} else {
definition.setAttributeType(attribute, type);
}
// Do we need to disable criteria?
boolean criteriaDisabled = false;
// Enable/disable criteria for sensitive attributes
if (type != AttributeType.SENSITIVE_ATTRIBUTE) {
if (model.getLDiversityModel().get(attribute).isEnabled() ||
model.getTClosenessModel().get(attribute).isEnabled() ||
model.getBLikenessModel().get(attribute).isEnabled() ||
model.getDDisclosurePrivacyModel().get(attribute).isEnabled()) {
criteriaDisabled = true;
}
model.getBLikenessModel().get(attribute).setEnabled(false);
model.getTClosenessModel().get(attribute).setEnabled(false);
model.getLDiversityModel().get(attribute).setEnabled(false);
model.getDDisclosurePrivacyModel().get(attribute).setEnabled(false);
}
// Enable/disable criteria for quasi-identifiers
if (definition.getQuasiIdentifyingAttributes().isEmpty()) {
if (model.getKAnonymityModel().isEnabled() ||
model.getDPresenceModel().isEnabled() ||
model.getStackelbergModel().isEnabled()) {
criteriaDisabled = true;
}
model.getKAnonymityModel().setEnabled(false);
model.getDPresenceModel().setEnabled(false);
model.getStackelbergModel().setEnabled(false);
for (ModelRiskBasedCriterion c : model.getRiskBasedModel()) {
if (c.isEnabled()) {
criteriaDisabled = true;
}
c.setEnabled(false);
}
}
// Update mode
updateMode();
// Update criteria
if (criteriaDisabled) {
controller.update(new ModelEvent(this,
ModelPart.CRITERION_DEFINITION,
null));
}
// Update the views
controller.update(new ModelEvent(this,
ModelPart.ATTRIBUTE_TYPE,
attribute));
}
}
}
/**
* Function changed
*/
private void actionFunctionChanged() {
if (cmbFunction.getSelectionIndex() != -1 && model != null && model.getInputConfig() != null) {
String function = cmbFunction.getItem(cmbFunction.getSelectionIndex());
model.getInputConfig().setMicroAggregationFunction(attribute, getMicroAggregationFunction(function));
}
}
/**
* Called when the hierarchy changes
* @param hierarchy
*/
private void actionHierarchyChanged(Hierarchy hierarchy) {
// Check
if (model == null || model.getInputConfig() == null) {
return;
}
// Update view
updateMinMax();
// Update model
if (hierarchy == null || hierarchy.getHierarchy() == null) {
model.getInputConfig().removeHierarchy(attribute);
} else {
model.getInputConfig().setHierarchy(attribute, hierarchy);
}
// Remove functional hierarchy
model.getInputConfig().removeHierarchyBuilder(attribute);
// Fire event
controller.update(new ModelEvent(ViewAttributeTransformation.this, ModelPart.HIERARCHY, hierarchy));
}
/**
* Updates the max generalization level.
*
* @return
*/
private boolean actionMaxChanged() {
if (cmbMax.getSelectionIndex() >= 0) {
if (cmbMax.getSelectionIndex() < (cmbMin.getSelectionIndex() - 1)) {
cmbMax.select(cmbMin.getSelectionIndex() - 1);
}
if (model != null) {
String val = cmbMax.getItem(cmbMax.getSelectionIndex());
model.getInputConfig().setMaximumGeneralization(attribute, val.equals(ITEM_ALL) ? null : Integer.valueOf(val));
return true;
}
}
return false;
}
/**
* Updates the min generalization level.
*
* @return
*/
private boolean actionMinChanged() {
if (cmbMin.getSelectionIndex() >= 0) {
if (cmbMin.getSelectionIndex() > (cmbMax.getSelectionIndex() + 1)) {
cmbMin.select(cmbMax.getSelectionIndex() + 1);
}
if (model != null) {
String val = cmbMin.getItem(cmbMin.getSelectionIndex());
model.getInputConfig().setMinimumGeneralization(attribute, val.equals(ITEM_ALL) ? null : Integer.valueOf(val));
return true;
}
}
return false;
}
/**
* Missing changed
*/
private void actionMissingChanged() {
if (model != null && model.getInputConfig() != null) {
model.getInputConfig().setMicroAggregationIgnoreMissingData(attribute, btnMissing.getSelection());
}
}
/**
* Transformation mode changed
*/
private void actionTransformationModeChanged() {
if (model == null || model.getInputConfig() == null) {
reset();
}
if (cmbMode.getSelectionIndex() == 0) {
model.getInputConfig().setTransformationMode(attribute, ModelTransformationMode.GENERALIZATION);
stack.setLayer(0);
} else {
model.getInputConfig().setTransformationMode(attribute, ModelTransformationMode.MICRO_AGGREGATION);
stack.setLayer(1);
}
}
/**
* Returns the current hierarchy
* @return
*/
private Hierarchy getHierarchy() {
if (model == null || model.getSelectedAttribute() == null || model.getInputConfig() == null) {
return null;
}
return model.getInputConfig().getHierarchy(model.getSelectedAttribute());
}
/**
* Returns the microaggregation function for the given label
* @param function
* @return
*/
private MicroAggregationFunctionDescription getMicroAggregationFunction(String label) {
for (MicroAggregationFunctionDescription function : FUNCTIONS) {
if (function.getLabel().equals(label)) {
return function;
}
}
return null;
}
/**
*
* Update the attribute type.
*/
private void updateAttributeType() {
if (model == null || model.getInputConfig() == null || model.getInputDefinition() == null) {
reset();
return;
}
AttributeType type = model.getInputDefinition().getAttributeType(attribute);
for (int i = 0; i < COMBO1_TYPES.length; i++) {
if (type == COMBO1_TYPES[i]) {
cmbType.select(i);
break;
}
}
}
/**
* Update function
*/
private void updateFunction() {
if (model != null && model.getInputConfig() != null && model.getInputDefinition() != null) {
DataScale scale = model.getInputDefinition().getDataType(attribute).getDescription().getScale();
List<String> functions = new ArrayList<String>();
for (MicroAggregationFunctionDescription function : FUNCTIONS) {
if (scale.provides(function.getRequiredScale())) {
functions.add(function.getLabel());
}
}
this.cmbFunction.setItems(functions.toArray(new String[functions.size()]));
MicroAggregationFunctionDescription description = model.getInputConfig().getMicroAggregationFunction(attribute);
int index = description == null ? -1 : functions.indexOf(description.getLabel());
if (index == -1) {
this.cmbFunction.select(0);
this.model.getInputConfig().setMicroAggregationFunction(attribute, getMicroAggregationFunction(functions.get(0)));
} else {
this.cmbFunction.select(index);
this.model.getInputConfig().setMicroAggregationFunction(attribute, getMicroAggregationFunction(functions.get(index)));
}
this.btnMissing.setSelection(this.model.getInputConfig().getMicroAggregationIgnoreMissingData(attribute));
}
}
/**
* Updates the combos.
*/
private void updateMinMax() {
// Check whether min & max are still ok
if (model == null || model.getInputConfig() == null || cmbMin == null || cmbMin.isDisposed() || model.getInputConfig().getInput() == null) {
reset();
return;
}
// Prepare lists
final List<String> minItems = new ArrayList<String>();
final List<String> maxItems = new ArrayList<String>();
minItems.add(ITEM_ALL);
int length = 0;
Hierarchy hierarchy = model.getInputConfig().getHierarchy(attribute);
if (!(hierarchy == null || hierarchy.getHierarchy() == null || hierarchy.getHierarchy().length == 0 || hierarchy.getHierarchy()[0] == null || hierarchy.getHierarchy()[0].length == 0)) {
length = hierarchy.getHierarchy()[0].length;
}
for (int i = 0; i < length; i++) {
minItems.add(String.valueOf(i));
maxItems.add(String.valueOf(i));
}
maxItems.add(ITEM_ALL);
// Determine min index
Integer minModel = model.getInputConfig().getMinimumGeneralization(attribute);
int minIndex = minModel != null ? minModel + 1 : 0;
// Determine max index
Integer maxModel = model.getInputConfig().getMaximumGeneralization(attribute);
int maxIndex = maxModel != null ? maxModel : maxItems.size() - 1;
// Fix indices
maxIndex = maxIndex > maxItems.size() - 1 ? maxItems.size() - 1 : maxIndex;
maxIndex = maxIndex < 0 ? maxItems.size() - 1 : maxIndex;
minIndex = minIndex > minItems.size() - 1 ? minItems.size() - 1 : minIndex;
minIndex = minIndex < 0 ? 0 : minIndex;
minIndex = minIndex > (maxIndex + 1) ? maxIndex + 1 : minIndex;
// Set items
cmbMin.setItems(minItems.toArray(new String[minItems.size()]));
cmbMax.setItems(maxItems.toArray(new String[maxItems.size()]));
// Select
cmbMin.select(minIndex);
cmbMax.select(maxIndex);
actionMinChanged();
actionMaxChanged();
}
/**
* Update mode
*/
private void updateMode() {
if (model == null || model.getInputConfig() == null || model.getInputDefinition() == null) {
reset();
return;
}
if (model.getInputDefinition().getQuasiIdentifyingAttributes().contains(attribute) && model != null && model.getInputConfig() != null) {
if (model.getInputConfig().getTransformationMode(attribute) == ModelTransformationMode.GENERALIZATION) {
stack.setLayer(0);
cmbMode.select(0);
} else {
stack.setLayer(1);
cmbMode.select(1);
}
} else {
cmbMode.select(0);
stack.setLayer(0);
}
}
}