/*
* 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.model;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.deidentifier.arx.ARXLattice;
import org.deidentifier.arx.ARXLattice.ARXNode;
import org.deidentifier.arx.ARXLattice.Anonymity;
import org.deidentifier.arx.ARXResult;
import org.deidentifier.arx.DataDefinition;
import org.deidentifier.arx.gui.resources.Resources;
import org.deidentifier.arx.gui.view.impl.explore.ViewClipboard;
import org.deidentifier.arx.metric.InformationLoss;
/**
* A model for the clipboard.
*
* @author Fabian Prasser
*/
public class ModelClipboard {
/** The clipboard, an ordered list of nodes. */
private transient List<ARXNode> clipboard = new ArrayList<ARXNode>();
/** Is this modified. */
private boolean modified = false;
/**
* Add a set of elements to the clipboard.
*
* @param list
*/
public void addAllToClipboard(List<ARXNode> list) {
this.setModified();
if (this.clipboard == null) {
this.clipboard = new ArrayList<ARXNode>();
}
this.clipboard.addAll(list);
}
/**
* Extracts interesting transformations from the given result
* @param model
*/
public void addInterestingTransformations(Model model) {
// If there is no result, return
ARXResult result = model.getResult();
if (model == null || result == null || !result.isResultAvailable()) {
return;
}
// Collect top-10 in terms of best score
List<ARXNode> utility = new ArrayList<ARXNode>();
utility.add(result.getGlobalOptimum());
collectTopSolutions(utility, result.getLattice(), new Comparator<ARXNode>(){
@Override
public int compare(ARXNode o1, ARXNode o2) {
return o1.getHighestScore().compareTo(o2.getHighestScore());
}
}, 10);
// Collect top-10 in terms of lowest generalization degree
final DataDefinition definition = model.getOutputDefinition() != null ? model.getOutputDefinition() : model.getInputDefinition();
List<ARXNode> generalization = new ArrayList<ARXNode>();
collectTopSolutions(generalization, result.getLattice(), new Comparator<ARXNode>(){
@Override
public int compare(ARXNode o1, ARXNode o2) {
double val1 = 0d;
double val2 = 0d;
for (int i = 0; i< o1.getTransformation().length; i++) {
double max = (double)definition.getMaximumGeneralization(o1.getQuasiIdentifyingAttributes()[i]);
max = max > 0d ? max : 1d;
val1 += (double)o1.getTransformation()[i] / max;
val2 += (double)o2.getTransformation()[i] / max;
}
int cmp = Double.valueOf(val1).compareTo(val2);
if (cmp == 0) {
return o1.getHighestScore().compareTo(o2.getHighestScore());
} else {
return cmp;
}
}
}, 10);
// TODO: It would be interesting to collect the number of suppressed records per transformation
// TODO: and extract the top-10 solutions as well
// Add to clip board
ARXNode optimum = utility.isEmpty() ? null : utility.remove(0);
if (optimum != null) {
optimum.getAttributes().put(ViewClipboard.NODE_COMMENT, Resources.getMessage("ModelClipboard.0")); //$NON-NLS-1$
this.addToClipboard(optimum);
}
int rank = 2;
for (ARXNode node : utility) {
node.getAttributes().put(ViewClipboard.NODE_COMMENT, Resources.getMessage("ModelClipboard.1") + (rank++) +Resources.getMessage("ModelClipboard.2")); //$NON-NLS-1$ //$NON-NLS-2$
this.addToClipboard(node);
}
optimum = generalization.isEmpty() ? null : generalization.remove(0);
if (optimum != null) {
optimum.getAttributes().put(ViewClipboard.NODE_COMMENT, Resources.getMessage("ModelClipboard.3")); //$NON-NLS-1$
this.addToClipboard(optimum);
}
rank = 2;
for (ARXNode node : generalization) {
node.getAttributes().put(ViewClipboard.NODE_COMMENT, Resources.getMessage("ModelClipboard.4") + (rank++) +Resources.getMessage("ModelClipboard.5")); //$NON-NLS-1$ //$NON-NLS-2$
this.addToClipboard(node);
}
this.setModified();
}
/**
* Add a node to the clipboard.
*
* @param node
*/
public void addToClipboard(ARXNode node) {
if (this.clipboard == null) {
this.clipboard = new ArrayList<ARXNode>();
}
if (!this.clipboard.contains(node)) {
setModified();
clipboard.add(node);
}
}
/**
* Clear the clipboard.
*/
public void clearClipboard() {
if (this.clipboard == null) {
this.clipboard = new ArrayList<ARXNode>();
}
if (!this.clipboard.isEmpty()) {
setModified();
}
clipboard.clear();
}
/**
* Returns a copy of all clipboard entries.
*
* @return
*/
public List<ARXNode> getClipboardEntries() {
if (this.clipboard == null) {
this.clipboard = new ArrayList<ARXNode>();
}
return new ArrayList<ARXNode>(this.clipboard);
}
/**
* Is the clipboard modified.
*
* @return
*/
public boolean isModified() {
return this.modified;
}
/**
* Moves the entry down.
*
* @param node
*/
public void moveEntryDown(ARXNode node){
int index = clipboard.indexOf(node);
if (index<clipboard.size()-1){
clipboard.remove(index);
clipboard.add(index+1, node);
}
}
/**
* Moves the entry up.
*
* @param node
*/
public void moveEntryUp(ARXNode node){
int index = clipboard.indexOf(node);
if (index>0){
clipboard.remove(index);
clipboard.add(index-1, node);
}
}
/**
* Removes an entry from the clip board.
*
* @param node
*/
public void removeFromClipboard(ARXNode node) {
if (this.clipboard == null) {
this.clipboard = new ArrayList<ARXNode>();
}
if (this.clipboard.remove(node)){
setModified();
}
}
/**
* Sets as unmodified.
*/
public void setUnmodified(){
this.modified = false;
}
/**
* Sorts all nodes according to their minimal score.
*/
public void sort() {
Collections.sort(clipboard, new Comparator<ARXNode>(){
@Override
public int compare(ARXNode arg0, ARXNode arg1) {
InformationLoss<?> loss0 = arg0.getLowestScore();
InformationLoss<?> loss1 = arg1.getLowestScore();
if (loss0==null && loss1==null) return 0;
else if (loss0==null && loss1!=null) return -1;
else if (loss0!=null && loss1==null) return +1;
else return loss0.compareTo(loss1);
}
});
}
/**
* Returns the top-n solutions
* @param elements
* @param lattice
* @param comparator
* @param n
*/
private void collectTopSolutions(List<ARXNode> elements,
ARXLattice lattice,
Comparator<ARXNode> comparator,
int n) {
// For each node
for (ARXNode[] level : lattice.getLevels()) {
for (ARXNode node : level) {
// If not already contained
if (node.getAnonymity() == Anonymity.ANONYMOUS && !elements.contains(node)) {
// See if it can be inserted at some point
boolean canbeinserted = false;
int i = elements.size() - 1;
for (; i >= -1; i--) {
// Break
if (i==-1) {
canbeinserted = true;
break;
}
// Yes
if (comparator.compare(node, elements.get(i)) < 0) {
canbeinserted = true;
// Maybe
} else {
break;
}
}
// Insert
if (canbeinserted) {
elements.add(i + 1, node);
// Ensure that we do not return more than n elements
while (elements.size() > n) {
elements.remove(elements.size() - 1);
}
} else if (elements.size() < n) {
// If it was not inserted but there is still space left, insert
elements.add(node);
}
}
}
}
}
/**
* Sets as modified.
*/
private void setModified(){
this.modified = true;
}
}