package controller; import controller.effectiveoutlierness.Calculation; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import org.apache.commons.lang3.ArrayUtils; import db.Database; /** * The class {@code DataArrayWorker} is used to concurrently filter and create {@code ElementData} objects and finally * fill the range of the array with it. */ public class DataArrayWorker implements Runnable { /** * The {@link Database}, where all elements are stored. */ private Database database = null; /** * The prepared part of the needed query, that is generated for the worker. */ private String sharedSql = null; /** * The array we have to fill with our workload. */ private ElementData[] elements = null; /** * The method to calculate the effective outlierness by */ private Calculation calculateEffectiveOutlierness = null; /** * The groups, which currently exist. */ private Group[] groups = null; /** * The active features, used to filter only wanted values. */ private Feature[] features = null; /** * The start index of our array working range. */ private int start = 0; /** * The end index of our array working range. */ private int end = 0; /** * The array of ids, filter by constraints. */ private int[] uniqWorkerArray = null; /** * The group ids with their set of ids, used to resolve elements to their groups. */ private HashMap<Integer, HashSet<Integer>> uniqGroupIds = null; /** * Constructs a new {@code DataArrayWorker}. * * @param database * The {@link Database}, where all elements are stored. * @param sql * The prepared part of the needed query, that is generated for the worker. * @param elements * The array we have to fill with our workload. * @param calculateEffectiveOutlierness * The method to calculate the effective outlierness. * @param groups * The groups, which currently exist. * @param features * The active features, used to filter only wanted values. * @param start * The start index of our array working range. * @param end * The end index of our array working range. * @param uniqWorkerArray * The array of ids, filter by constraints. * @param uniqGroupIds * The group ids with their set of ids, used to resolve elements to their groups. */ DataArrayWorker(Database database, String sql, ElementData[] elements, Calculation calculateEffectiveOutlierness, Group[] groups, Feature[] features, int start, int end, int[] uniqWorkerArray, HashMap<Integer, HashSet<Integer>> uniqGroupIds) { this.database = database; this.sharedSql = sql; this.elements = elements; this.calculateEffectiveOutlierness = calculateEffectiveOutlierness; this.groups = groups; this.features = features; this.start = start; this.end = end; this.uniqWorkerArray = uniqWorkerArray; this.uniqGroupIds = uniqGroupIds; } @Override public void run() { int[] featureIds = new int[this.features.length]; float[] values = new float[this.features.length]; // resolve feature ids for (int p = 1; p < this.features.length; ++p) { featureIds[p] = this.features[p].getId(); } try { // we need to get all objects for our block [end-start], b/c we have no constraints if (uniqWorkerArray == null) { this.generateAllElements(featureIds, values); } else { this.generateElementsFromRange(featureIds, values); } } catch (SQLException e) { // we couldn't finish our block, so we re-fill it with NaN to gracefully handle this situation this.invalidateRange(featureIds, values); } // done here Thread.yield(); } /** * generates elements if we do not have any constraints. * * fast query, slow object creation * * @param featureIds * the list of features * @param values * the list to store the values in * @throws SQLException * if database access failed */ private void generateAllElements(int[] featureIds, float[] values) throws SQLException { int fetchSize = (end - start) < 1 ? 0 : (end - start); Statement stmt = this.database.getConnection().createStatement(); stmt.setFetchSize(fetchSize); // query = shared + specific ResultSet rs = stmt.executeQuery(sharedSql + "WHERE Id >= " + (start + 1) + " AND Id < " + (end + 1) + ";"); // fill elements from start to end for (int i = this.start; i < this.end && rs.next(); ++i) { // walk over each table column in order to get all values for (int j = 0; j < this.features.length; ++j) { // offset +1, b/c counting starts at 1 values[j] = rs.getFloat(j + 1); // gracefully handle NaN, so we are always returning floats if (rs.wasNull()) { values[j] = Float.NaN; } } ElementData element = this.generateElementData(i + 1, featureIds, values, true); this.calculateEffectiveOutlierness.calculate(features, element); this.elements[i] = element; } stmt.close(); } /** * Generates elements based on constraints. * * slow query, fast object creation (assuming filtered ids << all ids) * * @param featureIds * the list of features * @param values * the array to store the values in * @throws SQLException * if database access failed */ private void generateElementsFromRange(int[] featureIds, float[] values) throws SQLException { int fetchSize = (end - start) < 1 ? 0 : (end - start); Statement stmt = this.database.getConnection().createStatement(); stmt.setFetchSize(fetchSize); // only get ids from the set for (int k = this.start; k < this.end; ++k) { int id = this.uniqWorkerArray[k]; ResultSet rs = stmt.executeQuery(sharedSql + "WHERE Id== " + id + ";"); // walk over each table column in order to get all values for (int l = 0; l < this.features.length; ++l) { // offset +1, b/c counting starts at 1 values[l] = rs.getFloat(l + 1); // gracefully handle NaN, so we are always returning floats if (rs.wasNull()) { values[l] = Float.NaN; } } ElementData element = this.generateElementData(id, featureIds, values, false); this.calculateEffectiveOutlierness.calculate(features, element); this.elements[k] = element; } stmt.close(); } /** * Invalidates the specific range of the worker array, by setting NaN as values. * * By doing so, we are able to handle interrupted worker threads gracefully. * * @param featureIds * the list of features * @param values * the array to store the values in */ private void invalidateRange(int[] featureIds, float[] values) { // fill elements from start to end for (int i = this.start; i < this.end; ++i) { // fill each value of the element with NaN for (int j = 0; j < this.features.length; ++j) { values[j] = Float.NaN; } ElementData element = this.generateElementData(i + 1, featureIds, values, true); // calculate the effective outlierness for this element this.calculateEffectiveOutlierness.calculate(features, element); // add the element to the final list this.elements[i] = element; } } /** * Generate ElementData and assign groups * * @param id element id * @param featureIds feature ids of required features * @param values feature values * @param emptyGroup flags, if empty group should add to elements * @return element data */ private ElementData generateElementData(int id, int[] featureIds, float[] values, boolean emptyGroup) { int[] groupIds; // get group ids from groups, which select all ids ArrayList<Integer> inGroup = new ArrayList<Integer>(); if (emptyGroup) { // add "empty" group to elements for (Group group : this.groups) { if (group.isVisible() && group.getConstraints().length == 0) { inGroup.add(group.getId()); } } } // check for occurrence of id and get the groupId from the mapping for (Map.Entry<Integer, HashSet<Integer>> entry : this.uniqGroupIds.entrySet()) { if (entry.getValue().contains(id) && !inGroup.contains(entry.getKey())) { inGroup.add(entry.getKey()); } } if (inGroup.isEmpty()) { groupIds = new int[0]; } else { // we have to convert the ArrayList to an array for the ElementData ctor groupIds = this.convertGroupMapping(inGroup); inGroup.clear(); } // finally, fire up the constructor! return new ElementData(id, featureIds, values, resolvGroups(groupIds)); } /** * Converts an ArrayList of group ids to a native int array for ease of usage. * * @param inGroup * the ArrayList to convert * @return the result in array */ private int[] convertGroupMapping(ArrayList<Integer> inGroup) { Integer[] integerArray = new Integer[inGroup.size()]; inGroup.toArray(integerArray); return ArrayUtils.toPrimitive(integerArray); } /** * Resolves group ids to group objects. * * @param groupIds * the list of group ids * @return the list of groups */ private Group[] resolvGroups(int[] groupIds) { ArrayList<Group> inGroup = new ArrayList<Group>(); // get specific group objects by their ids for (Group group : this.groups) { if (group.isVisible()) { for (int groupId : groupIds) { if (group.getId() == groupId) { inGroup.add(group); } } } } Group[] groups = new Group[inGroup.size()]; for (int i = 0; i < inGroup.size(); ++i) { groups[i] = inGroup.get(i); } return groups; } }