/* * 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.explore; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; import org.deidentifier.arx.ARXResult; import org.deidentifier.arx.DataDefinition; import org.deidentifier.arx.gui.Controller; import org.deidentifier.arx.gui.model.Model; import org.deidentifier.arx.gui.model.ModelEvent; import org.deidentifier.arx.gui.model.ModelEvent.ModelPart; import org.deidentifier.arx.gui.model.ModelNodeFilter; 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.ComponentFilterTable; import org.deidentifier.arx.gui.view.impl.common.ComponentTitledFolder; import org.deidentifier.arx.gui.view.impl.common.ComponentTitledFolderButtonBar; import org.eclipse.jface.layout.GridDataFactory; import org.eclipse.jface.layout.GridLayoutFactory; import org.eclipse.swt.SWT; import org.eclipse.swt.events.MouseAdapter; import org.eclipse.swt.events.MouseEvent; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import de.linearbits.swt.rangeslider.RangeSlider; /** * This class displays a filter for the lattice. * * @author Fabian Prasser */ public class ViewFilter implements IView { /** Scale size. */ private static final int SCALE_MAX_VALUE = 1000; /** Scale update interval. */ private static final int SCALE_UPDATE_INTERVAL = 100; /** Widget. */ private Composite root; /** Widget. */ private ComponentFilterTable generalization; /** Widget. */ private Button anonymous; /** Widget. */ private Button nonanonymous; /** Widget. */ private Button unknown; /** Widget */ private RangeSlider slider; /** Model. */ private Controller controller; /** Model. */ private ModelNodeFilter filter = null; /** Model. */ private Model model = null; /** Model. */ private ARXResult result = null; /** Model. */ private boolean mouseDown = false; /** Fire the event */ private volatile Boolean fireEvent = false; /** * Creates a new instance. * * @param parent * @param controller */ public ViewFilter(final Composite parent, final Controller controller) { // Listen this.controller = controller; this.controller.addListener(ModelPart.RESULT, this); this.controller.addListener(ModelPart.INPUT, this); this.controller.addListener(ModelPart.MODEL, this); this.controller.addListener(ModelPart.FILTER, this); // Images Image imageReset = controller.getResources().getManagedImage("arrow_refresh.png"); //$NON-NLS-1$ Image imageOptimum = controller.getResources().getManagedImage("bullet_yellow.png"); //$NON-NLS-1$ // Bar ComponentTitledFolderButtonBar bar = new ComponentTitledFolderButtonBar("id-21"); //$NON-NLS-1$ bar.add(Resources.getMessage("ViewFilter.0"), imageOptimum, new Runnable(){ //$NON-NLS-1$ public void run() { actionShowOptimum(); } }); bar.add(Resources.getMessage("ViewFilter.1"), imageReset, new Runnable(){ //$NON-NLS-1$ public void run() { actionReset(); } }); // Border ComponentTitledFolder border = new ComponentTitledFolder(parent, controller, bar, null); //$NON-NLS-1$ border.setLayoutData(SWTUtil.createFillGridData()); // Create root root = border.createItem(Resources.getMessage("NodeFilterView.3"), //$NON-NLS-1$ null); GridLayout groupLayout = new GridLayout(); groupLayout.numColumns = 2; root.setLayout(groupLayout); create(root); border.setSelection(0); border.setEnabled(true); reset(); parent.getDisplay().timerExec(SCALE_UPDATE_INTERVAL, new Runnable(){ public void run() { synchronized(fireEvent) { if (fireEvent) { fireEvent = false; actionInfoLossChanged(); } } if (!parent.isDisposed() && !parent.getDisplay().isDisposed()) { parent.getDisplay().timerExec(SCALE_UPDATE_INTERVAL, this); } } }); } @Override public void dispose() { controller.removeListener(this); } @Override public void reset() { filter = null; result = null; anonymous.setSelection(false); nonanonymous.setSelection(false); unknown.setSelection(false); slider.setSelection(slider.getMinimum(), slider.getMaximum()); slider.setEnabled(false); generalization.clear(); SWTUtil.disable(root); } @Override public void update(final ModelEvent event) { if (event.part == ModelPart.INPUT) { reset(); } else if (event.part == ModelPart.RESULT) { ARXResult result = (ARXResult)event.data; if (result == null || result.getLattice() == null){ reset(); SWTUtil.disable(root); } else { initialize(result, model.getNodeFilter()); SWTUtil.enable(root); } } else if (event.part == ModelPart.MODEL) { model = (Model) event.data; reset(); } else if (event.part == ModelPart.FILTER) { // Only update if we receive a new filter if ((filter == null) || (model.getNodeFilter() != filter)) { if (model.getResult() == null || model.getResult().getLattice() == null){ reset(); SWTUtil.disable(root); } else { initialize(model.getResult(), model.getNodeFilter()); SWTUtil.enable(root); } } } } /** * Action. */ private void actionAnonymousChanged() { if (filter != null) { if (!anonymous.getSelection()) filter.disallowAnonymous(); else filter.allowAnonymous(); fireModelEvent(); } } /** * Action. */ private void actionInfoLossChanged() { if (filter != null) { double maxLoss = (double)slider.getUpperValue() / (double)SCALE_MAX_VALUE; double minLoss = (double)slider.getLowerValue() / (double)SCALE_MAX_VALUE; filter.allowInformationLoss(minLoss, maxLoss); fireModelEvent(); } } /** * Action. */ private void actionNonAnonymousChanged() { if (filter != null) { if (!nonanonymous.getSelection()) filter.disallowNonAnonymous(); else filter.allowNonAnonymous(); fireModelEvent(); } } /** * Action. */ private void actionReset() { if (filter != null) { // Create ordered list of qis DataDefinition definition = model.getOutputDefinition(); if (definition == null) { reset(); return; } List<String> attributes = new ArrayList<String>(); attributes.addAll(definition.getQuasiIdentifiersWithGeneralization()); Collections.sort(attributes, new Comparator<String>(){ public int compare(String arg0, String arg1) { return model.getOutput().getColumnIndexOf(arg0)- model.getOutput().getColumnIndexOf(arg1); } }); int dimension=0; for (String attribute : attributes) { int attributeMin = definition.getMinimumGeneralization(attribute); int attributeMax = definition.getMaximumGeneralization(attribute); for (int i=attributeMin; i<=attributeMax; i++){ filter.allowGeneralization(dimension, i); } dimension++; } filter.allowAllInformationLoss(); filter.allowAnonymous(); filter.allowNonAnonymous(); filter.allowUnknown(); update(); fireModelEvent(); } } /** * Action. */ private void actionShowOptimum() { if (filter != null) { filter.initialize(model.getResult()); update(); fireModelEvent(); } } /** * Action. */ private void actionUnknownChanged() { if (filter != null) { if (!unknown.getSelection()) filter.disallowUnknown(); else filter.allowUnknown(); fireModelEvent(); } } /** * Fires the according events */ private void actionUpdateSlider(){ synchronized(fireEvent){ fireEvent = true; } } /** * Creates the view. * * @param parent */ private void create(final Composite parent) { // Add table generalization = new ComponentFilterTable(parent, controller, new ArrayList<String>()); GridData data = SWTUtil.createFillGridData(); data.heightHint = 70; data.horizontalSpan = 2; generalization.setLayoutData(data); generalization.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { if (filter != null && generalization.getSelectedEntry() != null && generalization.getSelectedProperty() != null) { String entry = generalization.getSelectedEntry(); String property = generalization.getSelectedProperty(); int dimension = generalization.getEntries().indexOf(entry); int level = Integer.valueOf(property); boolean enabled = generalization.isSelected(entry, property); if (enabled) { filter.allowGeneralization(dimension, level); } else { filter.disallowGeneralization(dimension, level); } } fireModelEvent(); } }); Composite composite = new Composite(parent, SWT.NONE); GridData gdata = SWTUtil.createFillHorizontallyGridData(); gdata.horizontalSpan = 2; composite.setLayoutData(gdata); composite.setLayout(GridLayoutFactory.swtDefaults().numColumns(6) .spacing(0, 0).margins(0, 0).create()); anonymous = new Button(composite, SWT.CHECK | SWT.NO_FOCUS); anonymous.setLayoutData(GridDataFactory.swtDefaults().grab(false, false).create()); anonymous.setToolTipText(Resources.getMessage("ViewFilter.2")); //$NON-NLS-1$ anonymous.setText(""); //$NON-NLS-1$ anonymous.setSelection(false); anonymous.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { actionAnonymousChanged(); } }); final Label anonymousLabel = new Label(composite, SWT.NONE); anonymousLabel.setText(Resources.getMessage("ViewFilter.4")); //$NON-NLS-1$ anonymousLabel.setLayoutData(GridDataFactory.swtDefaults().grab(true, false).create()); anonymousLabel.addMouseListener(new MouseAdapter(){ public void mouseDown(MouseEvent arg0) { anonymous.setSelection(!anonymous.getSelection()); actionAnonymousChanged(); } }); nonanonymous = new Button(composite, SWT.CHECK | SWT.NO_FOCUS); nonanonymous.setToolTipText(Resources.getMessage("ViewFilter.5")); //$NON-NLS-1$ nonanonymous.setText(""); //$NON-NLS-1$ nonanonymous.setSelection(false); nonanonymous.setLayoutData(GridDataFactory.swtDefaults().grab(false, false).create()); nonanonymous.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { actionNonAnonymousChanged(); } }); final Label nonanonymousLabel = new Label(composite, SWT.NONE); nonanonymousLabel.setText(Resources.getMessage("ViewFilter.7")); //$NON-NLS-1$ nonanonymousLabel.setLayoutData(GridDataFactory.swtDefaults().grab(true, false).create()); nonanonymousLabel.addMouseListener(new MouseAdapter(){ public void mouseDown(MouseEvent arg0) { nonanonymous.setSelection(!nonanonymous.getSelection()); actionNonAnonymousChanged(); } }); unknown = new Button(composite, SWT.CHECK | SWT.NO_FOCUS); unknown.setToolTipText(Resources.getMessage("ViewFilter.8")); //$NON-NLS-1$ unknown.setText(""); //$NON-NLS-1$ unknown.setLayoutData(GridDataFactory.swtDefaults().grab(false, false).create()); unknown.setSelection(false); unknown.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent arg0) { actionUnknownChanged(); } }); final Label unknownLabel = new Label(composite, SWT.NONE); unknownLabel.setText(Resources.getMessage("ViewFilter.10")); //$NON-NLS-1$ unknownLabel.setLayoutData(GridDataFactory.swtDefaults().grab(true, false).create()); unknownLabel.addMouseListener(new MouseAdapter(){ public void mouseDown(MouseEvent arg0) { unknown.setSelection(!unknown.getSelection()); actionUnknownChanged(); } }); final Label tableItem5 = new Label(parent, SWT.NONE); tableItem5.setText(Resources.getMessage("NodeFilterView.22")); //$NON-NLS-1$ this.slider = new RangeSlider(parent, SWT.HORIZONTAL); this.slider.setMaximum(SCALE_MAX_VALUE); this.slider.setMinimum(0); this.slider.setLayoutData(SWTUtil.createFillHorizontallyGridData()); this.slider.addSelectionListener(new SelectionAdapter() { public void widgetSelected(final SelectionEvent arg0) { actionUpdateSlider(); } }); this.slider.addListener(SWT.MouseMove, new Listener() { public void handleEvent(Event e) { if (mouseDown == true) { actionUpdateSlider(); } } }); this.slider.addListener(SWT.MouseDown, new Listener() { public void handleEvent(Event e) { mouseDown = true; } }); this.slider.addListener(SWT.MouseUp, new Listener() { public void handleEvent(Event e) { mouseDown = false; } }); this.slider.addListener(SWT.KeyDown, new Listener() { public void handleEvent(Event e) { actionUpdateSlider(); } }); this.slider.addListener(SWT.KeyUp, new Listener() { public void handleEvent(Event e) { actionUpdateSlider(); } }); } /** * Fires a model event when the filter changes. */ private void fireModelEvent(){ controller.update(new ModelEvent(this, ModelPart.FILTER, filter)); } /** * Initializes the view. * * @param result * @param nodeFilter */ private void initialize(final ARXResult result, final ModelNodeFilter nodeFilter) { // Reset reset(); // Return if there is no lattice if (result==null || result.getLattice() == null) { return; } // Store data definition this.result = result; // Reset filter if (nodeFilter == null) { throw new IllegalStateException(Resources.getMessage("ViewFilter.11")); //$NON-NLS-1$ } filter = nodeFilter; // Update this.update(); } /** * Initializes the everything. */ private void update() { // Check if (result == null) { reset(); return; } // Disable drawing this.root.setRedraw(false); // Clear this.generalization.clear(); // Create ordered list of qis String[] attributes = result.getLattice().getBottom().getQuasiIdentifyingAttributes(); int min = Integer.MAX_VALUE; int max = Integer.MIN_VALUE; for (String attribute : attributes) { min = Math.min(min, result.getLattice().getBottom().getGeneralization(attribute)); max = Math.max(max, result.getLattice().getTop().getGeneralization(attribute)); } List<String> properties = new ArrayList<String>(); for (int i=min; i<=max; i++){ properties.add(String.valueOf(i)); } generalization.setProperties(properties); for (String attribute : attributes) { List<String> attributeProperties = new ArrayList<String>(); int attributeMin = result.getLattice().getBottom().getGeneralization(attribute); int attributeMax = result.getLattice().getTop().getGeneralization(attribute); for (int i=attributeMin; i<=attributeMax; i++){ attributeProperties.add(String.valueOf(i)); } generalization.addEntry(attribute, attributeProperties); } int i=0; for (String attribute : attributes) {; Set<Integer> selected = filter.getAllowedGeneralizations(i); Set<Integer> unselected = new HashSet<Integer>(); for (String property : properties){ unselected.add(Integer.valueOf(property)); } unselected.removeAll(selected); for (int level : selected) { generalization.setSelected(attribute, String.valueOf(level), true); } for (int level : unselected) { generalization.setSelected(attribute, String.valueOf(level), false); } i++; } // Initialize classification anonymous.setSelection(filter.isAllowedAnonymous()); nonanonymous.setSelection(filter.isAllowedNonAnonymous()); unknown.setSelection(filter.isAllowedUnknown()); // Min and max this.slider.setSelection((int)Math.round(filter.getAllowedMinInformationLoss() * (double)SCALE_MAX_VALUE), (int)Math.round(filter.getAllowedMaxInformationLoss() * (double)SCALE_MAX_VALUE)); // Draw this.root.setRedraw(true); this.root.redraw(); // Enable SWTUtil.enable(root); } }