/*
* 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;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.deidentifier.arx.ARXConfiguration.ARXConfigurationInternal;
import org.deidentifier.arx.certificate.elements.ElementData;
import org.deidentifier.arx.framework.lattice.SolutionSpace;
import org.deidentifier.arx.framework.lattice.Transformation;
import org.deidentifier.arx.metric.InformationLoss;
import org.deidentifier.arx.metric.Metric;
import cern.colt.list.LongArrayList;
import com.carrotsearch.hppc.IntObjectOpenHashMap;
import com.carrotsearch.hppc.LongObjectOpenHashMap;
import de.linearbits.jhpl.JHPLIterator.LongIterator;
/**
* This class implements a representation of the generalization lattice that is
* exposed to users of the API.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class ARXLattice implements Serializable {
/**
* The internal accessor class.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class Access implements Serializable {
/** SVUID */
private static final long serialVersionUID = 6654627605797832468L;
/** Lattice */
private final ARXLattice lattice;
/**
* Constructor
*
* @param lattice
*/
public Access(final ARXLattice lattice) {
this.lattice = lattice;
}
/**
* Accessor method
*
* @return
*/
public Map<String, Integer> getAttributeMap() {
return bottom.headermap;
}
/**
* Accessor method
*
* @param bottom
*/
public void setBottom(final ARXNode bottom) {
lattice.bottom = bottom;
}
/**
* Accessor method
*
* @param levels
*/
public void setLevels(final ARXNode[][] levels) {
lattice.levels = levels;
}
/**
* Accessor method
*
* @param model
*/
public void setQualityModel(final Metric<?> model) {
lattice.metric = model;
}
/**
* Accessor method
*
* @param config
*/
public void setMonotonicity(ARXConfiguration config) {
lattice.setMonotonicity(config.isSuppressionAlwaysEnabled(), config.getAbsoluteMaxOutliers());
}
/**
* Accessor method
*
* @param node
*/
public void setOptimum(final ARXNode node) {
lattice.optimum = node;
}
/**
* Accessor method
*
* @param size
*/
public void setSize(final int size) {
lattice.size = size;
}
/**
* Updates the solution space
* @param solutions
*/
public void setSolutionSpace(SolutionSpace solutions) {
lattice.solutions = solutions;
}
/**
* Accessor method
*
* @param top
*/
public void setTop(final ARXNode top) {
lattice.top = top;
}
/**
* Accessor method
*
* @param uncertainty
*/
public void setUncertainty(final boolean uncertainty) {
lattice.uncertainty = uncertainty;
}
}
/**
* ReflectS different anonymity properties
*/
public static enum Anonymity {
/** ANONYMOUS */
ANONYMOUS,
/** NOT_ANONYMOUS */
NOT_ANONYMOUS,
/** UNKNOWN */
UNKNOWN,
/** PROBABLY_ANONYMOUS */
PROBABLY_ANONYMOUS,
/** PROBABLY_NOT_ANONYMOUS */
PROBABLY_NOT_ANONYMOUS
}
/**
* A node in the lattice.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class ARXNode {
/**
* Internal access class.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class Access {
/** Node */
private final ARXNode node;
/**
* Accessor class
*
* @param node
*/
public Access(final ARXNode node) {
this.node = node;
}
/**
* Sets the anonymity.
*
* @param anonymity
*/
public void setAnonymity(final Anonymity anonymity) {
node.anonymity = anonymity;
}
/**
* Set anonymous.
*/
public void setAnonymous() {
node.anonymity = Anonymity.ANONYMOUS;
}
/**
* Sets the attributes.
*
* @param attributes
*/
public void setAttributes(final Map<Integer, Object> attributes) {
node.attributes = attributes;
}
/**
* Set checked.
*
* @param checked
*/
public void setChecked(final boolean checked) {
node.checked = checked;
}
/**
* Sets the headermap.
*
* @param headermap
*/
public void setHeadermap(final Map<String, Integer> headermap) {
node.headermap = headermap;
}
/**
* Sets the lower bound.
*
* @param a
*/
public void setLowerBound(final InformationLoss<?> a) {
node.lowerBound = InformationLoss.createInformationLoss(a, metric, getDeserializationContext().minLevel, getDeserializationContext().maxLevel);
}
/**
* Sets the maximal information loss.
*
* @param a
*/
public void setHighestScore(final InformationLoss<?> a) {
node.maxInformationLoss = InformationLoss.createInformationLoss(a, metric, getDeserializationContext().minLevel, getDeserializationContext().maxLevel);
}
/**
* Sets the minimal information loss.
*
* @param a
*/
public void setLowestScore(final InformationLoss<?> a) {
node.minInformationLoss = InformationLoss.createInformationLoss(a, metric, getDeserializationContext().minLevel, getDeserializationContext().maxLevel);
}
/**
* Set not anonymous.
*/
public void setNotAnonymous() {
node.anonymity = Anonymity.NOT_ANONYMOUS;
}
/**
* Sets the predecessors.
*
* @param predecessors
*/
public void setPredecessors(final ARXNode[] predecessors) {
node.predecessors = predecessors;
}
/**
* Sets the successors.
*
* @param successors
*/
public void setSuccessors(final ARXNode[] successors) {
node.successors = successors;
}
/**
* Sets the transformation.
*
* @param transformation
*/
public void setTransformation(final int[] transformation) {
node.transformation = transformation;
}
}
/** Id. */
private Integer id = null;
/** The access. */
private final Access access = new Access(this);
/** Is it anonymous. */
private Anonymity anonymity;
/** Attributes. */
private Map<Integer, Object> attributes = new HashMap<Integer, Object>();
/** Has the node been checked. */
private boolean checked;
/** The header map. */
private Map<String, Integer> headermap;
/** The lower bound. */
private InformationLoss<?> lowerBound;
/** The max information loss. */
private InformationLoss<?> maxInformationLoss;
/** The min information loss. */
private InformationLoss<?> minInformationLoss;
/** The predecessors. */
private ARXNode[] predecessors;
/** The successors. */
private ARXNode[] successors;
/** The transformation. */
private int[] transformation;
/** The underlying lattice */
private final ARXLattice lattice;
/**
* Internal constructor for deserialization.
*
* @param lattice
*/
public ARXNode(ARXLattice lattice) {
this.lattice = lattice;
}
/**
* Constructor.
*
* @param lattice
* @param solutions
* @param transformation
* @param headermap
*/
private ARXNode(final ARXLattice lattice,
final SolutionSpace solutions,
final Transformation transformation,
final Map<String, Integer> headermap) {
// Set properties
this.lattice = lattice;
this.headermap = headermap;
this.transformation = transformation.getGeneralization();
this.minInformationLoss = transformation.getInformationLoss();
this.maxInformationLoss = transformation.getInformationLoss();
this.lowerBound = transformation.getLowerBound();
this.checked = transformation.hasProperty(solutions.getPropertyChecked());
// Transfer anonymity property without uncertainty
if (transformation.hasProperty(solutions.getPropertyChecked())){
if (transformation.hasProperty(solutions.getPropertyAnonymous())) {
this.anonymity = Anonymity.ANONYMOUS;
} else if(transformation.hasProperty(solutions.getPropertyNotAnonymous())) {
this.anonymity = Anonymity.NOT_ANONYMOUS;
} else {
if (!complete) {
this.anonymity = Anonymity.UNKNOWN;
} else {
throw new IllegalStateException("Missing information about transformations");
}
}
// This is a node for which the property is unknown
} else {
if (transformation.hasProperty(solutions.getPropertyAnonymous())) {
this.anonymity = uncertainty ? Anonymity.PROBABLY_ANONYMOUS : Anonymity.ANONYMOUS;
} else if (transformation.hasProperty(solutions.getPropertyNotAnonymous())) {
this.anonymity = uncertainty ? Anonymity.PROBABLY_NOT_ANONYMOUS : Anonymity.NOT_ANONYMOUS;
} else if (transformation.hasProperty(solutions.getPropertyNotKAnonymous())) {
this.anonymity = Anonymity.NOT_ANONYMOUS;
} else if (transformation.hasProperty(solutions.getPropertyInsufficientUtility())) {
this.anonymity = Anonymity.UNKNOWN;
} else {
if (!complete) {
this.anonymity = Anonymity.UNKNOWN;
} else {
throw new IllegalStateException("Missing information about transformations");
}
}
}
// Make sure that we have information loss available
// Important for expand operations
if (!complete) {
if (this.maxInformationLoss == null) {
this.maxInformationLoss = metric.createInstanceOfHighestScore();
}
if (this.minInformationLoss == null) {
this.minInformationLoss = metric.createInstanceOfLowestScore();
}
}
}
/**
* Alter associated fields.
*
* @return
*/
public Access access() {
return access;
}
/**
* Materializes any non-materialized predecessors and successors
*/
public void expand() {
this.lattice.expand(this);
}
/**
* Returns the anonymity property.
*
* @return
*/
public Anonymity getAnonymity() {
return anonymity;
}
/**
* Returns the attributes.
*
* @return
*/
public Map<Integer, Object> getAttributes() {
return attributes;
}
/**
* Returns the index of an attribute.
*
* @param attr
* @return
*/
public int getDimension(final String attr) {
return headermap.get(attr);
}
/**
* Returns the generalization for the attribute.
*
* @param attribute
* @return
*/
public int getGeneralization(final String attribute) {
final Integer index = headermap.get(attribute);
if (index == null) { return 0; }
return transformation[index];
}
/**
* Returns the highest score. Lower is better.
* @return
*/
public InformationLoss<?> getHighestScore() {
return maxInformationLoss;
}
/**
* Returns the highest score. Lower is better.
* @return
*/
public InformationLoss<?> getLowestScore() {
return minInformationLoss;
}
/**
* Returns the maximal information loss.
* This method is deprecated. Please use getHighestScore() instead.
* @return
*/
@Deprecated
public InformationLoss<?> getMaximumInformationLoss() {
return maxInformationLoss;
}
/**
* Returns the minimal information loss.
* This method is deprecated. Please use getLowestScore() instead.
* @return
*/
@Deprecated
public InformationLoss<?> getMinimumInformationLoss() {
return minInformationLoss;
}
/**
* The predecessors.
*
* @return
*/
public ARXNode[] getPredecessors() {
return predecessors;
}
/**
* Returns the quasi identifiers.
*
* @return
*/
public String[] getQuasiIdentifyingAttributes() {
final String[] result = new String[headermap.size()];
for (final String key : headermap.keySet()) {
result[headermap.get(key)] = key;
}
return result;
}
/**
* The successors.
*
* @return
*/
public ARXNode[] getSuccessors() {
return successors;
}
/**
* Returns the sum of all generalization levels.
*
* @return
*/
public int getTotalGeneralizationLevel() {
int level = 0;
for (int i : transformation) {
level += i;
}
return level;
}
/**
* Returns the transformation as an array.
*
* @return
*/
public int[] getTransformation() {
return transformation;
}
/**
* Returns the anonymity property.
*
* @return
*/
@Deprecated
public Anonymity isAnonymous() {
return anonymity;
}
/**
* Returns if the node has been checked explicitly.
*
* @return
*/
public boolean isChecked() {
return checked;
}
/**
* Renders this object
* @return
*/
public ElementData render() {
ElementData result = new ElementData("Transformation");
result.addProperty("Anonymity", this.anonymity);
result.addProperty("Minimum information loss", this.minInformationLoss.toString());
result.addProperty("Maximum information loss", this.maxInformationLoss.toString());
result.addProperty(null, renderGeneralizationScheme());
return result;
}
/**
* Renders this object
* @return
*/
private ElementData renderGeneralizationScheme() {
ElementData result = new ElementData("Generalization scheme");
for (String qi : this.getQuasiIdentifyingAttributes()) {
result.addProperty(qi, this.getGeneralization(qi) + "/" +
this.lattice.getTop().getGeneralization(qi));
}
return result;
}
/**
* De-serialization.
*
* @param aInputStream
* @throws ClassNotFoundException
* @throws IOException
*/
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
// Default de-serialization
aInputStream.defaultReadObject();
// Translate information loss, if necessary
this.lowerBound = InformationLoss.createInformationLoss(this.lowerBound,
metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
this.maxInformationLoss = InformationLoss.createInformationLoss(this.maxInformationLoss,
metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
this.minInformationLoss = InformationLoss.createInformationLoss(this.minInformationLoss,
metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
}
/**
* Returns a node's internal id.
*
* @return
*/
protected Integer getId(){
return this.id;
}
/**
* Returns a node's lower bound, if any.
*
* @return
*/
protected InformationLoss<?> getLowerBound(){
return this.lowerBound;
}
/**
* Internal method that sets the id.
*
* @param id
*/
protected void setId(int id) {
this.id = id;
}
}
/**
* Context for deserialization.
*
* @author Florian Kohlmayer
*/
public static class LatticeDeserializationContext {
/** Min level */
public int minLevel = 0;
/** Max level */
public int maxLevel = 0;
}
/** Deserialization context. */
private static LatticeDeserializationContext deserializationContext = new LatticeDeserializationContext();
/** SVUID */
private static final long serialVersionUID = -8790104959905019184L;
/**
* Returns the deserialization context.
*
* @return
*/
public static LatticeDeserializationContext getDeserializationContext() {
return deserializationContext;
}
/** The accessor. */
private final Access access = new Access(this);
/** The bottom node. */
private transient ARXNode bottom;
/** The levels in the lattice. */
private transient ARXNode[][] levels;
/** Metric. */
private Metric<?> metric;
/** The optimum. */
private transient ARXNode optimum;
/** The number of nodes. */
private int size;
/** The virtual size */
private Long virtualSize;
/** The top node. */
private transient ARXNode top;
/** Is practical monotonicity being assumed. */
private boolean uncertainty;
/** Is this the result of an optimal algorithm */
private Boolean complete;
/** Monotonicity of information loss. */
private boolean monotonicAnonymous;
/** Monotonicity of information loss. */
private boolean monotonicNonAnonymous;
/** Minimum loss in the lattice. */
private InformationLoss<?> minimumInformationLoss = null;
/** Maximum loss in the lattice. */
private InformationLoss<?> maximumInformationLoss = null;
/** The solution space */
private transient SolutionSpace solutions;
/**
* Constructor.
*
* @param solutions The solution space
* @param complete Is the solution space characterized by an optimal algorithm
* @param optimum The optimum
* @param header The header
* @param config The config
*/
ARXLattice(final SolutionSpace solutions,
final boolean complete,
final Transformation optimum,
final String[] header,
final ARXConfigurationInternal config) {
// Init
this.solutions = solutions;
this.metric = config.getQualityModel();
this.setMonotonicity(config.isSuppressionAlwaysEnabled(), config.getAbsoluteMaxOutliers());
this.complete = complete;
this.virtualSize = solutions.getSize();
// Set this flag to true, if practical monotonicity is being assumed
this.uncertainty = config.isPracticalMonotonicity();
// Build header map
final Map<String, Integer> headermap = new HashMap<String, Integer>();
int index = 0;
for (int i = 0; i < header.length; i++) {
headermap.put(header[i], index++);
}
// Build lattice
if (complete) {
buildComplete(optimum, headermap);
} else {
buildIncomplete(optimum, headermap);
}
// find bottom node
outer: for (int i = 0; i < this.levels.length; i++) {
final ARXNode[] level = this.levels[i];
for (int j = 0; j < level.length; j++) {
final ARXNode node = level[j];
if (node != null) {
this.bottom = node;
break outer;
}
}
}
// find top node
outer: for (int i = this.levels.length - 1; i >= 0; i--) {
final ARXNode[] level = this.levels[i];
for (int j = 0; j < level.length; j++) {
final ARXNode node = level[j];
if (node != null) {
this.top = node;
break outer;
}
}
}
// Estimate information loss of all nodes
estimateInformationLoss();
}
/**
* Access fields of this class.
*
* @return
*/
public Access access() {
return access;
}
/**
* Materializes any non-materialized predecessors and successors
*/
public void expand(ARXNode center) {
// Initialize
int[] indices = center.getTransformation();
Transformation transformation = solutions.getTransformation(indices);
// Collect neighbors
LongArrayList neighbors = transformation.getPredecessors();
LongArrayList successors = transformation.getSuccessors();
neighbors.addAllOfFromTo(successors, 0, successors.size() - 1);
// Find missing neighbors and initialize variables
Map<String, Integer> headermap = null;
LongObjectOpenHashMap<ARXNode> map = new LongObjectOpenHashMap<ARXNode>();
Set<Long> missing = new HashSet<Long>();
for (int i = 0; i < neighbors.size(); i++) {
missing.add(neighbors.getQuick(i));
}
for (ARXNode[] level : this.levels) {
for (ARXNode node : level) {
headermap = headermap != null ? headermap : node.headermap;
Long id = solutions.getTransformation(node.getTransformation()).getIdentifier();
map.put(id, node);
missing.remove(id);
}
}
// Materialize missing nodes
Map<Integer, List<ARXNode>> levels = new HashMap<Integer, List<ARXNode>>();
for (long id : missing) {
// Materialize
transformation = solutions.getTransformation(id);
ARXNode node = new ARXNode(this, solutions, transformation, headermap);
// Store in global map
map.put(id, node);
// Store in map of levels
if (!levels.containsKey(transformation.getLevel())) {
levels.put(transformation.getLevel(), new ArrayList<ARXNode>());
}
levels.get(transformation.getLevel()).add(node);
}
// Insert missing nodes into level Arrays
for (int level : levels.keySet()) {
// Sort nodes to insert, lexicographically
List<ARXNode> nodes = levels.get(level);
Collections.sort(nodes, new Comparator<ARXNode>(){
public int compare(ARXNode o1, ARXNode o2) {
return compareLexicographically(o1, o2);
}
});
// Initialize new level
List<ARXNode> list = new ArrayList<ARXNode>();
// Now add all nodes in one pass
int index = 0;
for (ARXNode node : this.levels[level]) {
while (index < nodes.size() && compareLexicographically(nodes.get(index), node) < 0) {
list.add(nodes.get(index++));
}
list.add(node);
}
// Add remaining
while (index < nodes.size()) {
list.add(nodes.get(index++));
}
// Convert
this.levels[level] = list.toArray(new ARXNode[list.size()]);
}
// Build relationships from/to missing nodes
for (long id : missing) {
this.createExpandedRelationships(solutions, map, id);
}
// Update information loss
if (!missing.isEmpty()) {
this.estimateInformationLoss();
}
}
/**
* Returns the bottom node.
*
* @return
*/
public ARXNode getBottom() {
return bottom;
}
/**
* Returns the highest score. Lower is better.
* @return
*/
public InformationLoss<?> getHighestScore(){
return this.getMaximumInformationLoss();
}
/**
* Returns the levels of the generalization lattice.
*
* @return
*/
public ARXNode[][] getLevels() {
return levels;
}
/**
* Returns the lowest score. Lower is better.
* @return
*/
public InformationLoss<?> getLowestScore(){
return this.getMinimumInformationLoss();
}
/**
* Returns the maximal information loss.
* This method is deprecated. Please use getHighestScore() instead.
* @return
*/
@Deprecated
public InformationLoss<?> getMaximumInformationLoss(){
if (this.maximumInformationLoss == null) {
this.estimateInformationLoss();
}
return this.maximumInformationLoss;
}
/**
* Returns the minimal information loss.
* This method is deprecated. Please use getLowestScore() instead.
* @return
*/
@Deprecated
public InformationLoss<?> getMinimumInformationLoss(){
if (this.minimumInformationLoss == null) {
this.estimateInformationLoss();
}
return this.minimumInformationLoss;
}
/**
* Returns the number of nodes.
*
* @return
*/
public int getSize() {
return size;
}
/**
* Returns the top node.
*
* @return
*/
public ARXNode getTop() {
return top;
}
/**
* Returns the virtual size of the solution space
* @return
*/
public long getVirtualSize() {
return virtualSize != null ? virtualSize : size;
}
/**
* Returns whether the search space has been characterized completely
* (i.e. whether an optimal solution has been determined, *not* whether
* all transformations have been materialized).
* @return
*/
public boolean isComplete() {
return this.complete;
}
/**
* Renders this object
* @return
*/
public ElementData render() {
ElementData result = new ElementData("Search space");
result.addProperty("Size", this.virtualSize);
result.addProperty("Materialized", this.size);
result.addProperty("Completely classified", this.complete);
return result;
}
/**
* Build an ARX lattice for a completely classified solution space
* @param optimum
* @param headermap
*/
private void buildComplete(final Transformation optimum, Map<String, Integer> headermap) {
// Init
this.size = (int) solutions.getSize();
int[] offsets = solutions.getMultipliersForLowDimensionalData();
int[] maxLevels = solutions.getTop().getGeneralization();
int[] minLevels = solutions.getBottom().getGeneralization();
int topLevel = solutions.getTop().getLevel();
// Create nodes
int[] levelsizes = new int[topLevel + 1];
ARXNode[] cache = new ARXNode[size];
for (int identifier = 0; identifier < cache.length; identifier++) {
// Create ARXNode
Transformation transformation = solutions.getTransformation(identifier);
cache[identifier] = new ARXNode(this,
this.solutions,
transformation,
headermap);
// Store optimum
if (optimum != null && identifier == optimum.getIdentifier()) {
this.optimum = cache[identifier];
}
// Count successors and predecessors
int numSuccessors = 0;
int numPredecessors = 0;
int[] generalization = transformation.getGeneralization();
for (int dimension = 0; dimension<generalization.length; dimension++) {
numPredecessors += generalization[dimension] > minLevels[dimension] ? 1 : 0;
numSuccessors += generalization[dimension] < maxLevels[dimension] ? 1 : 0;
}
// Increase level size
levelsizes[transformation.getLevel()]++;
// Initialize arrays
cache[identifier].successors = new ARXNode[numSuccessors];
cache[identifier].predecessors = new ARXNode[numPredecessors];
}
// Generate level arrays
this.levels = new ARXNode[topLevel + 1][];
for (int i = 0; i < levels.length; i++) {
levels[i] = new ARXNode[levelsizes[i]];
}
// Generate links to successors and predecessors
int[] successorIndices = new int[size];
int[] predecessorIndices = new int[size];
for (int identifier = 0; identifier < cache.length; identifier++) {
ARXNode node = cache[identifier];
int level = node.getTotalGeneralizationLevel();
--levelsizes[level];
levels[level][levels[level].length - 1 - levelsizes[level]] = node;
int[] generalization = node.getTransformation();
for (int dimension = 0; dimension < generalization.length; dimension++) {
if (generalization[dimension] < maxLevels[dimension]) {
int successorIdentifier = identifier + offsets[dimension];
ARXNode successor = cache[successorIdentifier];
node.successors[successorIndices[identifier]++] = successor;
successor.predecessors[predecessorIndices[successorIdentifier]++] = node;
}
}
}
}
/**
* Build an ARX lattice for an incompletely classified solution space
* @param optimum
* @param headermap
*/
private void buildIncomplete(final Transformation optimum, Map<String, Integer> headermap) {
// Create nodes
final LongObjectOpenHashMap<ARXNode> map = new LongObjectOpenHashMap<ARXNode>();
final IntObjectOpenHashMap<List<ARXNode>> levels = new IntObjectOpenHashMap<List<ARXNode>>();
int size = 0;
int maxlevel = 0;
for (LongIterator iterator = solutions.getMaterializedTransformations(); iterator.hasNext();) {
Transformation transformation = solutions.getTransformation(iterator.next());
if (!levels.containsKey(transformation.getLevel())) {
levels.put(transformation.getLevel(), new ArrayList<ARXNode>());
}
ARXNode node = new ARXNode(this, solutions, transformation, headermap);
map.put(transformation.getIdentifier(), node);
levels.get(transformation.getLevel()).add(node);
if (optimum != null && transformation.getIdentifier() == optimum.getIdentifier()) {
this.optimum = node;
}
maxlevel = Math.max(maxlevel, transformation.getLevel());
size++;
}
// Make sure that bottom and top are in the resulting solution space
Transformation top = solutions.getTop();
Transformation bottom = solutions.getBottom();
if (!map.containsKey(top.getIdentifier())) {
if (!levels.containsKey(top.getLevel())) {
levels.put(top.getLevel(), new ArrayList<ARXNode>());
}
ARXNode node = new ARXNode(this, solutions, top, headermap);
map.put(top.getIdentifier(), node);
levels.get(top.getLevel()).add(node);
maxlevel = top.getLevel();
size++;
}
if (!map.containsKey(bottom.getIdentifier())) {
if (!levels.containsKey(bottom.getLevel())) {
levels.put(bottom.getLevel(), new ArrayList<ARXNode>());
}
ARXNode node = new ARXNode(this, solutions, bottom, headermap);
map.put(bottom.getIdentifier(), node);
levels.get(bottom.getLevel()).add(node);
size++;
}
// Create levels array
this.size = size;
this.levels = new ARXNode[maxlevel+1][];
for (int i = 0; i < this.levels.length; i++) {
if (levels.containsKey(i)) {
this.levels[i] = levels.get(i).toArray(new ARXNode[levels.get(i).size()]);
} else {
this.levels[i] = new ARXNode[0];
}
}
// Create relationships
for (LongIterator iterator = solutions.getMaterializedTransformations(); iterator.hasNext();) {
createRelationships(solutions, map, iterator.next());
}
createRelationships(solutions, map, solutions.getTop().getIdentifier());
createRelationships(solutions, map, solutions.getBottom().getIdentifier());
}
/**
* Compares the transformations of two nodes lexicographically
* @param first
* @param second
* @return
*/
private int compareLexicographically(ARXNode first, ARXNode second) {
int[] firstArray = first.getTransformation();
int[] secondArray = second.getTransformation();
for (int i = 0; i < firstArray.length; i++) {
if (firstArray[i] < secondArray[i]) {
return -1;
} else if (firstArray[i] > secondArray[i]) {
return +1;
}
}
return 0;
}
/**
* Creates all relationships
* @param solutions
* @param map
* @param id
*/
private void createExpandedRelationships(final SolutionSpace solutions,
final LongObjectOpenHashMap<ARXNode> map,
final long id) {
// Obtain given node
final ARXNode center = map.get(id);
final Transformation transformation = solutions.getTransformation(id);
// Collect materialized successors and predecessors
List<ARXNode> successors = new ArrayList<ARXNode>();
List<ARXNode> predecessors = new ArrayList<ARXNode>();
LongArrayList list1 = transformation.getSuccessors();
for (int i = 0; i < list1.size(); i++) {
ARXNode node = map.get(list1.getQuick(i));
if (node != null) {
successors.add(node);
}
}
LongArrayList list2 = transformation.getPredecessors();
for (int i = 0; i < list2.size(); i++) {
ARXNode node = map.get(list2.getQuick(i));
if (node != null) {
predecessors.add(node);
}
}
// Add successors and predecessors to given node
center.successors = successors.toArray(new ARXNode[successors.size()]);
center.predecessors = predecessors.toArray(new ARXNode[predecessors.size()]);
// Update predecessors
for (ARXNode node : predecessors) {
List<ARXNode> nodeSuccessors = new ArrayList<ARXNode>();
nodeSuccessors.addAll(Arrays.asList(node.successors));
int index = 0;
while (index < nodeSuccessors.size() && compareLexicographically(nodeSuccessors.get(index), center) < 0 ) {
index++;
}
// Subtract
index = index == 0 ? 0 : index - 1;
nodeSuccessors.add(index, center);
// Add and update
node.successors = nodeSuccessors.toArray(new ARXNode[nodeSuccessors.size()]);
}
// Update successors
for (ARXNode node : successors) {
List<ARXNode> nodePredecessors = new ArrayList<ARXNode>();
nodePredecessors.addAll(Arrays.asList(node.predecessors));
int index = 0;
while (index < nodePredecessors.size() && compareLexicographically(nodePredecessors.get(index), center) < 0 ) {
index++;
}
// Subtract
index = index == 0 ? 0 : index - 1;
// Add and update
nodePredecessors.add(index, center);
node.predecessors = nodePredecessors.toArray(new ARXNode[nodePredecessors.size()]);
}
}
/**
* Creates all relationships
* @param solutions
* @param map
* @param id
*/
private void createRelationships(final SolutionSpace solutions,
final LongObjectOpenHashMap<ARXNode> map,
final long id) {
final ARXNode fnode = map.get(id);
final Transformation transformation = solutions.getTransformation(id);
List<ARXNode> successors = new ArrayList<ARXNode>();
List<ARXNode> predecessors = new ArrayList<ARXNode>();
LongArrayList list1 = transformation.getSuccessors();
for (int i = 0; i < list1.size(); i++) {
ARXNode node = map.get(list1.getQuick(i));
if (node != null) {
successors.add(node);
}
}
LongArrayList list2 = transformation.getPredecessors();
for (int i = 0; i < list2.size(); i++) {
ARXNode node = map.get(list2.getQuick(i));
if (node != null) {
predecessors.add(node);
}
}
fnode.successors = successors.toArray(new ARXNode[successors.size()]);
fnode.predecessors = predecessors.toArray(new ARXNode[predecessors.size()]);
}
/**
* De-serialization.
*
* @param aInputStream
* @throws ClassNotFoundException
* @throws IOException
*/
private void readObject(ObjectInputStream aInputStream) throws ClassNotFoundException, IOException {
// Default de-serialization
aInputStream.defaultReadObject();
// Translate minimum and maximum
this.maximumInformationLoss = InformationLoss.createInformationLoss(this.maximumInformationLoss,
metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
this.minimumInformationLoss = InformationLoss.createInformationLoss(this.minimumInformationLoss,
metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
// Translate metric, if necessary
this.metric = Metric.createMetric(this.metric,
getDeserializationContext().minLevel,
getDeserializationContext().maxLevel);
// Set flag, if necessary
if (complete == null) {
complete = true;
}
}
/**
* Sets the monotonicity based on the current configuration
* @param isSuppressionAlwaysEnabled
* @param absoluteSuppressionLimit
*/
private void setMonotonicity(boolean isSuppressionAlwaysEnabled, int absoluteSuppressionLimit) {
this.monotonicNonAnonymous = (this.metric.isMonotonicWithSuppression() && isSuppressionAlwaysEnabled) ||
(this.metric.isMonotonicWithGeneralization() && !isSuppressionAlwaysEnabled);
this.monotonicAnonymous = this.metric.isMonotonic(absoluteSuppressionLimit);
}
/**
* This method triggers the estimation of the information loss of all nodes
* in the lattice regardless of whether they have been checked for anonymity
* or not. Additionally, it computes global upper and lower bounds on utility
*/
protected void estimateInformationLoss() {
if (complete) {
UtilityEstimator estimator = new UtilityEstimator(this, metric, monotonicAnonymous, monotonicNonAnonymous);
estimator.estimate();
this.minimumInformationLoss = estimator.getGlobalMinimum();
this.maximumInformationLoss = estimator.getGlobalMaximum();
} else {
this.minimumInformationLoss = null;
this.maximumInformationLoss = null;
for (ARXNode[] level : this.levels) {
for (ARXNode node : level) {
this.minimumInformationLoss = this.minimumInformationLoss == null ? node.getLowestScore() : this.minimumInformationLoss;
this.maximumInformationLoss = this.maximumInformationLoss == null ? node.getHighestScore() : this.maximumInformationLoss;
if (this.minimumInformationLoss.compareTo(node.getLowestScore()) > 0) {
this.minimumInformationLoss = node.getLowestScore().clone();
}
if (this.maximumInformationLoss.compareTo(node.getHighestScore()) < 0) {
this.maximumInformationLoss = node.getHighestScore().clone();
}
}
}
}
}
/**
* Returns the optimum, if any.
*
* @return
*/
protected ARXNode getOptimum() {
return optimum;
}
}