/* * 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.io.IOException; import java.io.Serializable; import java.util.HashSet; import java.util.Iterator; import java.util.Set; import org.deidentifier.arx.ARXLattice; import org.deidentifier.arx.ARXLattice.ARXNode; import org.deidentifier.arx.ARXLattice.Anonymity; import org.deidentifier.arx.gui.resources.Resources; import org.deidentifier.arx.ARXResult; /** * This class implements a filter for a generalization lattice. * * @author Fabian Prasser */ public class ModelNodeFilter implements Serializable { /** SVUID. */ private static final long serialVersionUID = 5451641489562102719L; /** The anonymity properties allowed. */ private final Set<Anonymity> anonymity = new HashSet<Anonymity>(); /** The generalization levels allowed. */ private Set<Integer>[] generalizations = null; /** The initial number of nodes. */ private int maxNumNodesInitial = 0; /** Bound for min. score. */ private double minInformationLoss = 0d; /** Bound for max. score. */ private double maxInformationLoss = 1d; /** * Creates a new instance. * * @param maxLevels * @param maxNumNodesInitial */ @SuppressWarnings("unchecked") public ModelNodeFilter(final int[] maxLevels, final int maxNumNodesInitial) { this.maxNumNodesInitial = maxNumNodesInitial; this.generalizations = new Set[maxLevels.length]; for (int i = 0; i < generalizations.length; i++) { generalizations[i] = new HashSet<Integer>(); } } /** * Creates a new instance used for cloning. * * @param anonymity * @param generalizations * @param maxNumNodesInitial * @param minInformationLoss * @param maxInformationLoss */ @SuppressWarnings("unchecked") private ModelNodeFilter(Set<Anonymity> anonymity, Set<Integer>[] generalizations, int maxNumNodesInitial, double minInformationLoss, double maxInformationLoss) { for (Anonymity element : anonymity) { this.anonymity.add(element); } this.generalizations = new Set[generalizations.length]; for (int i = 0; i < generalizations.length; i++) { Set<Integer> current = generalizations[i]; this.generalizations[i] = new HashSet<Integer>(); for (Integer integer : current) { this.generalizations[i].add(integer); } } this.maxNumNodesInitial = maxNumNodesInitial; this.minInformationLoss = minInformationLoss; this.maxInformationLoss = maxInformationLoss; } /** * Allows transformations with any score to pass the filter. */ public void allowAllInformationLoss() { minInformationLoss = 0d; maxInformationLoss = 1d; } /** * Allows anonymous transformations to pass the filter. */ public void allowAnonymous() { anonymity.add(Anonymity.ANONYMOUS); } /** * Allows transformations with certain generalization level to pass the filter. * * @param dimension * @param level */ public void allowGeneralization(final int dimension, final int level) { generalizations[dimension].add(level); } /** * Allows transformations with certain score to pass the filter. * * @param min * @param max */ public void allowInformationLoss(final double min, final double max) { if (min<0d || min>1d || max <0d || max>1d) { throw new IllegalArgumentException(Resources.getMessage("Model.0a")); //$NON-NLS-1$ } minInformationLoss = min; maxInformationLoss = max; } /** * Allows non-anonymous transformations to pass the filter. */ public void allowNonAnonymous() { anonymity.add(Anonymity.NOT_ANONYMOUS); } /** * Allows unknown transformations to pass the filter. */ public void allowUnknown() { anonymity.add(Anonymity.PROBABLY_NOT_ANONYMOUS); anonymity.add(Anonymity.PROBABLY_ANONYMOUS); anonymity.add(Anonymity.UNKNOWN); } /** * Clones the ModelNodeFilter instance. */ public ModelNodeFilter clone() { return new ModelNodeFilter(anonymity, generalizations, maxNumNodesInitial, minInformationLoss, maxInformationLoss); } /** * Allows no transformaions to pass the filter. */ public void disallowAll() { anonymity.clear(); minInformationLoss = 0d; maxInformationLoss = 1d; for (int i = 0; i < generalizations.length; i++) { generalizations[i].clear(); } } /** * Filters out anonymous transformations. */ public void disallowAnonymous() { anonymity.remove(Anonymity.ANONYMOUS); } /** * Filters out transformations with certain generalization level. * * @param dimension * @param level */ public void disallowGeneralization(final int dimension, final int level) { generalizations[dimension].remove(level); } /** * Filters out non-anonymous transformations. */ public void disallowNonAnonymous() { anonymity.remove(Anonymity.NOT_ANONYMOUS); } /** * Filters out unknown transformations. */ public void disallowUnknown() { anonymity.remove(Anonymity.PROBABLY_ANONYMOUS); anonymity.remove(Anonymity.PROBABLY_NOT_ANONYMOUS); anonymity.remove(Anonymity.UNKNOWN); } /** * @return the anonymity */ public Set<Anonymity> getAllowedAnonymity() { return anonymity; } /** * * * @param dimension * @return the generalizations */ public Set<Integer> getAllowedGeneralizations(final int dimension) { return generalizations[dimension]; } /** * @return the maxInformationLoss */ public double getAllowedMaxInformationLoss() { return maxInformationLoss; } /** * @return the minInformationLoss */ public double getAllowedMinInformationLoss() { return minInformationLoss; } /** * Creates a node filter for the given result. * * @param result */ public void initialize(final ARXResult result) { disallowAll(); if (result.isResultAvailable()) { // Allow specializations and generalizations of optimum allowAnonymous(); final double min = 0d; final double max = 1d; allowInformationLoss(min, max); final int[] optimum = result.getGlobalOptimum().getTransformation(); for (int i = 0; i < optimum.length; i++) { allowGeneralization(i, optimum[i]); } // Build sets of visible and hidden nodes final Set<ARXNode> visible = new HashSet<ARXNode>(); final Set<ARXNode> hidden = new HashSet<ARXNode>(); visible.add(result.getGlobalOptimum()); for (final ARXNode[] level : result.getLattice().getLevels()) { for (final ARXNode node : level) { if (node.getAnonymity() == Anonymity.ANONYMOUS) { if (!node.equals(result.getGlobalOptimum())) { hidden.add(node); } } } } // Determine max generalization int maxgen = 0; for (int i = 0; i < optimum.length; i++) { maxgen = Math.max(result.getLattice() .getTop() .getTransformation()[i], maxgen); } // Show less generalized nodes for (int j = 1; j <= maxgen; j++) { for (int i = 0; i < optimum.length; i++) { final int gen = optimum[i] - j; if (gen >= 0) { allowGeneralization(i, gen); final int current = count(result.getLattice(), visible, hidden); if (current > maxNumNodesInitial) { disallowGeneralization(i, gen); return; } } } } // Show more generalized nodes for (int j = 1; j <= maxgen; j++) { for (int i = 0; i < optimum.length; i++) { final int gen = optimum[i] + j; if (gen <= result.getLattice().getTop().getTransformation()[i]) { allowGeneralization(i, gen); final int current = count(result.getLattice(), visible, hidden); if (current > maxNumNodesInitial) { disallowGeneralization(i, gen); return; } } } } // Clean up clean(result.getLattice(), visible, optimum); } else { // Allow generalizations of bottom allowNonAnonymous(); final double max = 1d; final double min = 0d; allowInformationLoss(min, max); final int[] base = result.getLattice() .getBottom() .getTransformation(); for (int i = 0; i < base.length; i++) { allowGeneralization(i, base[i]); } // Build sets of visible and hidden nodes final Set<ARXNode> visible = new HashSet<ARXNode>(); final Set<ARXNode> hidden = new HashSet<ARXNode>(); visible.add(result.getLattice().getBottom()); for (final ARXNode[] level : result.getLattice().getLevels()) { for (final ARXNode node : level) { if (node.getAnonymity() == Anonymity.NOT_ANONYMOUS) { if (!node.equals(result.getLattice().getBottom())) { hidden.add(node); } } } } // Determine max generalization int maxgen = 0; for (int i = 0; i < base.length; i++) { maxgen = Math.max(result.getLattice() .getTop() .getTransformation()[i], maxgen); } // Show more generalized nodes for (int j = 1; j <= maxgen; j++) { for (int i = 0; i < base.length; i++) { final int gen = base[i] + j; if (gen <= result.getLattice().getTop().getTransformation()[i]) { allowGeneralization(i, gen); final int current = count(result.getLattice(), visible, hidden); if (current > maxNumNodesInitial) { disallowGeneralization(i, gen); return; } } } } // Clean up clean(result.getLattice(), visible, base); } } /** * Returns whether the given node is allowed to pass this filter. * * @param lattice * @param node * @return */ public boolean isAllowed(final ARXLattice lattice, final ARXNode node) { double max = node.getHighestScore().relativeTo(lattice.getLowestScore(), lattice.getHighestScore()); double min = node.getLowestScore().relativeTo(lattice.getLowestScore(), lattice.getHighestScore()); if (max < minInformationLoss) { return false; } else if (min > maxInformationLoss) { return false; } else if (!anonymity.contains(node.getAnonymity())) { return false; } final int[] transformation = node.getTransformation(); for (int i = 0; i < transformation.length; i++) { if (!generalizations[i].contains(transformation[i])) { return false; } } return true; } /** * Returns setting. * * @return */ public boolean isAllowedAnonymous() { return anonymity.contains(Anonymity.ANONYMOUS); } /** * Returns whether the given generalization is allowed. * * @param dimension * @param level * @return */ public boolean isAllowedGeneralization(final int dimension, final int level) { return generalizations[dimension].contains(level); } /** * Returns setting. * * @return */ public boolean isAllowedNonAnonymous() { return anonymity.contains(Anonymity.NOT_ANONYMOUS); } /** * Returns setting. * * @return */ public boolean isAllowedUnknown() { return anonymity.contains(Anonymity.PROBABLY_ANONYMOUS) || anonymity.contains(Anonymity.PROBABLY_NOT_ANONYMOUS) || anonymity.contains(Anonymity.UNKNOWN); } /** * Cleans up the settings. * * @param lattice * @param visible * @param optimum */ private void clean(final ARXLattice lattice, final Set<ARXNode> visible, final int[] optimum) { // Remove hidden from visible final Iterator<ARXNode> i = visible.iterator(); while (i.hasNext()) { final ARXNode node = i.next(); if (!isAllowed(lattice, node)) { i.remove(); } } // Build sets @SuppressWarnings("unchecked") final Set<Integer>[] required = new HashSet[optimum.length]; for (int j = 0; j < optimum.length; j++) { required[j] = new HashSet<Integer>(); } for (final ARXNode node : visible) { for (int j = 0; j < optimum.length; j++) { required[j].add(node.getTransformation()[j]); } } // Clean the settings for (int j = 0; j < optimum.length; j++) { final Iterator<Integer> it = generalizations[j].iterator(); while (it.hasNext()) { final int l = it.next(); if (!required[j].contains(l)) { it.remove(); } } } } /** * Counts the number of visible nodes. * * @param lattice * @param visible * @param hidden * @return */ private int count(final ARXLattice lattice, final Set<ARXNode> visible, final Set<ARXNode> hidden) { final Iterator<ARXNode> i = hidden.iterator(); while (i.hasNext()) { final ARXNode node = i.next(); if (isAllowed(lattice, node)) { i.remove(); visible.add(node); } } return visible.size(); } /** * * * @param stream * @throws IOException * @throws ClassNotFoundException */ private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); if (this.minInformationLoss < 0d) { this.minInformationLoss = 0d; } if (this.minInformationLoss > 1d) { this.minInformationLoss = 1d; } if (this.maxInformationLoss < 0d) { this.maxInformationLoss = 0d; } if (this.maxInformationLoss > 1d) { this.maxInformationLoss = 1d; } } }