/*
* 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.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import org.deidentifier.arx.DataHandleInternal.InterruptHandler;
import org.deidentifier.arx.aggregates.StatisticsBuilder;
import org.deidentifier.arx.framework.data.Dictionary;
/**
* An implementation of the DataHandle interface for input data.
*
* @author Fabian Prasser
* @author Florian Kohlmayer
*/
public class DataHandleInput extends DataHandle {
/** The data. */
protected int[][] data = null;
/** The dictionary. */
protected Dictionary dictionary = null;
/** The data. */
private int[][] dataGH = null;
/** The data. */
private int[][] dataDI = null;
/** The data. */
private int[][] dataIS = null;
/** Is this handle locked?. */
private boolean locked = false;
/**
* Creates a new data handle.
*
* @param data
*/
protected DataHandleInput(final Data data) {
// Obtain and check iterator
final Iterator<String[]> iterator = data.iterator();
if (!iterator.hasNext()) {
throw new IllegalArgumentException("Data object is empty!");
}
// Register
this.setRegistry(new DataRegistry());
this.getRegistry().updateInput(this);
this.definition = data.getDefinition().clone();
// Obtain header
final String[] columns = iterator.next();
super.header = Arrays.copyOf(columns, columns.length);
// Init dictionary
this.dictionary = new Dictionary(header.length);
// Encode data
List<int[]> vals = new ArrayList<int[]>();
while (iterator.hasNext()) {
// Process a tuple
final String[] strings = iterator.next();
final int[] tuple = new int[header.length];
for (int i = 0; i < strings.length; i++) {
tuple[i] = dictionary.register(i, strings[i]);
}
vals.add(tuple);
}
// Build array
this.data = vals.toArray(new int[vals.size()][]);
// finalize dictionary
this.dictionary.finalizeAll();
// Create datatype array
this.dataTypes = getDataTypeArray();
}
@Override
public String getAttributeName(final int column) {
checkRegistry();
checkColumn(column);
return header[column];
}
@Override
public int getGeneralization(final String attribute) {
checkRegistry();
return 0;
}
@Override
public int getNumColumns() {
checkRegistry();
return header.length;
}
@Override
public int getNumRows() {
checkRegistry();
return data.length;
}
@Override
public StatisticsBuilder getStatistics() {
return new StatisticsBuilder(new DataHandleInternal(this));
}
@Override
public String getValue(final int row, final int column) {
checkRegistry();
checkColumn(column);
checkRow(row, data.length);
return internalGetValue(row, column, false);
}
@Override
public boolean isOutlier(int row){
return false;
}
@Override
public Iterator<String[]> iterator() {
checkRegistry();
return new Iterator<String[]>() {
int index = -1;
@Override
public boolean hasNext() {
return (index < data.length);
}
@Override
public String[] next() {
if (index == -1) {
index++;
return header;
} else {
final String[] result = new String[header.length];
for (int i = 0; i < result.length; i++) {
result[i] = getValue(index, i);
}
index++;
return result;
}
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove is not supported by this iterator");
}
};
}
/**
* Swaps two rows.
*
* @param row1
* @param row2
* @param data
*/
private void swap(int row1, int row2, int[][] data){
final int[] temp = data[row1];
data[row1] = data[row2];
data[row2] = temp;
}
/**
* Releases all resources.
*/
protected void doRelease() {
this.setLocked(false);
dataGH = null;
dataDI = null;
dataIS = null;
}
@Override
protected DataType<?> getBaseDataType(final String attribute) {
return this.getDataType(attribute);
}
@Override
protected ARXConfiguration getConfiguration() {
return null;
}
@Override
protected DataType<?>[][] getDataTypeArray() {
checkRegistry();
DataType<?>[][] dataTypes = new DataType[1][header.length];
for (int i = 0; i < header.length; i++) {
final DataType<?> type = definition.getDataType(header[i]);
if (type != null) {
dataTypes[0][i] = type;
} else {
dataTypes[0][i] = DataType.STRING;
}
}
return dataTypes;
}
@Override
protected String[] getDistinctValues(final int column, final boolean ignoreSuppression, InterruptHandler handler) {
checkRegistry();
handler.checkInterrupt();
checkColumn(column);
handler.checkInterrupt();
final String[] dict = dictionary.getMapping()[column];
handler.checkInterrupt();
final String[] vals = new String[dict.length];
handler.checkInterrupt();
System.arraycopy(dict, 0, vals, 0, vals.length);
return vals;
}
/**
* Returns the input buffer
* @return
*/
protected int[][] getInputBuffer() {
checkRegistry();
return this.dataGH;
}
@Override
protected String internalGetValue(final int row, final int column, final boolean ignoreSuppression) {
return dictionary.getMapping()[column][data[row][column]];
}
@Override
protected boolean internalReplace(int column,
String original,
String replacement) {
String[] values = dictionary.getMapping()[column];
boolean found = false;
for (int i = 0; i < values.length; i++) {
if (values[i].equals(original)) {
values[i] = replacement;
found = true;
}
}
return found;
}
/**
* Swaps the rows.
*
* @param row1
* @param row2
*/
protected void internalSwap(final int row1, final int row2) {
// Check
checkRow(row1, data.length);
checkRow(row2, data.length);
// Swap
swap(row1, row2, data);
if (dataGH != null) swap(row1, row2, dataGH);
if (dataDI != null) swap(row1, row2, dataDI);
if (dataIS != null) swap(row1, row2, dataIS);
}
/**
* Is this handle locked?.
*
* @return
*/
protected boolean isLocked(){
return this.locked;
}
/**
* Overrides the handles data definition.
*
* @param definition
*/
protected void setDefinition(DataDefinition definition) {
this.definition = definition;
}
/**
* Lock/unlock this handle.
*
* @param locked
*/
protected void setLocked(boolean locked){
this.locked = locked;
}
/**
* Update the definition.
*
* @param data
*/
protected void update(Data data){
if (!this.isLocked()) {
this.definition = data.getDefinition().clone();
this.dataTypes = getDataTypeArray();
this.definition.setLocked(true);
}
}
/**
* Updates the definition with further data to swap.
*
* @param dataGH
* @param dataDI
* @param dataIS
*/
protected void update(int[][] dataGH, int[][] dataDI, int[][] dataIS) {
this.dataGH = dataGH;
this.dataDI = dataDI;
this.dataIS = dataIS;
}
}