package controller;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import util.Failure;
import util.Operator;
import db.Database;
import db.DatabaseAccessException;
import db.DatabaseConfiguration;
/**
* The class {@code Group} represents a subset of points. These points are specified by {@link Constraint}s.
*/
public class Group {
/**
* A counter to set the id for a new created {@link StaticConstraint}. It should be set in front of first usage of
* {@code Group} and hold the last used id.
*/
private static int idCountStaticConstraint;
/**
* A unique identifier, set by the {@link Database}.
*/
private final int id;
/**
* The name of this {@code Group}, set by the user in the UI.
*/
private String name;
/**
* Determines, whether the points belonging to this {@code Group} are visible in the UI.
*/
private boolean visible;
/**
* The color visualizing this {@code Group} in the UI.
*/
private int color;
/**
* The {@link Feature} used to calculate the color dynamically in the UI.
*/
private Feature colorFeature;
/**
* The description to this {@code Group}.
*/
private String description;
/**
* This list holds all {@link Constraint}s, referring to this group.
*/
private ArrayList<Constraint> constraints = new ArrayList<Constraint>();
/**
* The {@link Database}, where this {@code Group} is stored.
*/
private final Database database;
/**
* The {@link GroupController} this {@code Group} was created at.
*/
private final GroupController groupController;
/**
* Constructs a new {@code Group}. The attribute {@code valid} is set true.
*
* @param groupController
* the {@link GroupController} this {@code Group} was cerated at.
* @param database
* The {@link Database}, where this {@code Group} is stored. The parameter may not be {@code null}.
* @param id
* A unique identifier, it should not be negative.
* @param name
* The name of this {@code Group}, may not be {@code null}.
* @param visible
* Determines, whether this {@code Group} is visible in the UI.
* @param color
* The color of this {@code Group} in the UI.
* @param colorFeature
* {@link Feature} used to calculate the color dynamically. {@code null}, if there is no such
* {@link Feature}.
* @param description
* The description of this {@code Group}, may not be {@code null}.
*/
public Group(GroupController groupController, Database database, int id, String name, boolean visible, int color,
Feature colorFeature, String description) {
if (groupController == null || database == null || id < 1 || name == null
|| DatabaseConfiguration.VARCHARLENGTH < name.length() || description == null) {
throw new IllegalArgumentException(
"database, name and description may not be null and the id has to be positive");
}
this.database = database;
this.groupController = groupController;
this.id = id;
this.name = name;
this.visible = visible;
this.color = color;
this.colorFeature = colorFeature;
this.description = description;
this.constraints = new ArrayList<Constraint>();
}
/**
* Return the unique identifier of this {@code Group}.
*
* @return the identifier.
*/
public int getId() {
return this.id;
}
/**
* Return the name of this {@code Group}, shown in the UI.
*
* @return the name of this {@code Group}.
*/
public String getName() {
return this.name;
}
/**
* Returns whether this {@code Group} is currently visible in the UI.
*
* @return True, if it is visible.
*/
public boolean isVisible() {
return this.visible;
}
/**
* Returns the color visualizing this {@code Group} in the UI.
*
* @return The color of the {@code Group}.
*/
public int getColor() {
return this.color;
}
/**
* Returns the {@link Feature} used to calculate the color in the UI dynamically. Returns {@code null} if there is
* no such {@link Feature}.
*
* @return The Feature.
*/
public Feature getColorFeature() {
return this.colorFeature;
}
/**
* Returns the description of this {@code Group}.
*
* @return The description.
*/
public String getDescription() {
return this.description;
}
/**
* Gives this {@code Group} a new name and updates it in the {@link Database}.
*
* @param name
* The new name, may not be {@code null}.
* @throws DatabaseAccessException
* if the write operation in the {@link Database} failed.
*/
public void setName(String name) throws DatabaseAccessException {
if (name == null || DatabaseConfiguration.VARCHARLENGTH < name.length()) {
throw new IllegalArgumentException("name may not be null or is to long");
}
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"UPDATE Groups SET Name=? WHERE Id=?;");
prepStmt.setString(1, name);
prepStmt.setInt(2, this.id);
prepStmt.execute();
prepStmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
this.name = name;
}
/**
* Determines whether the {@code Group} is currently visible in the UI and updates it in the {@link Database}.
*
* @param visible
* True, if it is visible.
* @throws DatabaseAccessException
* if the write operation in the {@link Database} failed.
*/
public void setVisible(boolean visible) throws DatabaseAccessException {
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"UPDATE Groups SET Visibility=? WHERE Id=?;");
prepStmt.setBoolean(1, visible);
prepStmt.setInt(2, this.id);
prepStmt.execute();
prepStmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
this.visible = visible;
}
/**
* Gives this {@code Group} a new color, used to visualize it in the UI, and updates it in the {@link Database}.
*
* @param color
* The new color.
* @throws DatabaseAccessException
* if the write operation in the {@link Database} failed.
*/
public void setColor(int color) throws DatabaseAccessException {
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"UPDATE Groups SET Color=? WHERE Id=?;");
prepStmt.setInt(1, color);
prepStmt.setInt(2, this.id);
prepStmt.execute();
prepStmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
this.color = color;
}
/**
* Gives this {@code Group} a new {@link Feature}, used to calculate the color in the UI dynamically, and updates it
* in the {@link Database}.
*
* @param colorFeature
* The new Feature or {@code null} if there is no such {@link Feature}.
* @throws DatabaseAccessException
* if the write operation in the {@link Database} failed.
*/
public void setColorFeature(Feature colorFeature) throws DatabaseAccessException {
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"UPDATE Groups SET ColorCalculatedByFeature=? WHERE Id=?;");
prepStmt.setInt(1, colorFeature == null ? 0 : colorFeature.getId());
prepStmt.setInt(2, this.id);
prepStmt.execute();
prepStmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
this.colorFeature = colorFeature;
}
/**
* Changes the description of this {@code Group} and updates it in the {@link Database}.
*
* @param description
* The new description, may not be {@code null}.
* @throws DatabaseAccessException
* if the write operation in the {@link Database} failed.
*/
public void setDescription(String description) throws DatabaseAccessException {
if (description == null) {
throw new IllegalArgumentException("description may not be null");
}
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"UPDATE Groups SET Description=? WHERE Id=?;");
prepStmt.setString(1, description);
prepStmt.setInt(2, this.id);
prepStmt.execute();
prepStmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
this.description = description;
}
/**
* This method creates a new {@link DynamicConstraint}, stores it in the {@link database} and adds it to the list of
* all constraints.
*
* @param feature
* The {@link Feature} of the new {@link DynamicConstraint}, may not be {@code null}.
* @param operator
* The {@link Operator} of the new {@link DynamicConstraint}.
* @param value
* The value of the new {@link DynamicConstraint}.
* @return The new constructed {@link DynamicConstraint}.
* @throws DatabaseAccessException
* if the write operation failed in {@link Database}.
*/
public DynamicConstraint createDynamicConstraint(Feature feature, Operator operator, float value)
throws DatabaseAccessException {
if (feature == null || operator == null) {
throw new IllegalArgumentException("feature and operator may not be null");
}
// The newly created constraint
DynamicConstraint newConstraint = null;
// Create a new constraint, pass it to the database and get its new id.
try {
PreparedStatement prepStmt = this.database.getConnection().prepareStatement(
"INSERT INTO DynamicConstraints VALUES(NULL,?,?,?,?,?);");
prepStmt.setInt(1, operator.ordinal());
prepStmt.setInt(2, feature.getId());
prepStmt.setInt(3, this.id);
prepStmt.setFloat(4, value);
prepStmt.setBoolean(5, true);
prepStmt.execute();
prepStmt.close();
Statement stmt = this.database.getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT MAX(Id) FROM DynamicConstraints;");
newConstraint = new DynamicConstraint(this.groupController, this.database, rs.getInt(1), feature, operator,
value, true);
stmt.close();
constraints.add(newConstraint);
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
return newConstraint;
}
/**
* This method creates a new {@link StaticConstraint}, stores it in the {@link database} and adds it to the list of
* all constraints.
*
* @param selection
* A list of ids from points. The parameter may not be {@code null} and the list has at least one item.
* @return The new constructed {@link StaticConstraint}.
* @throws DatabaseAccessException
* if the write operation failed in {@link Database}.
*/
public StaticConstraint createStaticConstraint(int[] selection) throws DatabaseAccessException {
if (selection == null || selection.length == 0) {
throw new IllegalArgumentException("selection is null or the array has no element");
}
// The newly created constraint
StaticConstraint newConstraint = null;
// Create a new constraint, pass it to the database and get its new id.
try {
Connection connection = this.database.getConnection();
// increment to determine the id for the next constraint
idCountStaticConstraint++;
PreparedStatement prepStmt = connection.prepareStatement("INSERT INTO StaticConstraints VALUES(?,?,?,?);");
prepStmt.setInt(1, idCountStaticConstraint);
prepStmt.setInt(2, this.id);
prepStmt.setBoolean(4, true);
// add all single points to the batch
for (int current : selection) {
prepStmt.setInt(3, current);
prepStmt.addBatch();
}
// execute the batch
connection.setAutoCommit(false);
prepStmt.executeBatch();
connection.setAutoCommit(true);
prepStmt.close();
newConstraint = new StaticConstraint(this.groupController, this.database, idCountStaticConstraint, this.id,
selection, true);
constraints.add(newConstraint);
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
return newConstraint;
}
/**
* Returns a list of all {@link Constraint}s belonging to this {@code Group}.
*
* @return The list of constraints or an empty array if there are no constraints.
*/
public Constraint[] getConstraints() {
Constraint[] constraintsArray = new Constraint[constraints.size()];
this.constraints.toArray(constraintsArray);
return constraintsArray;
}
/**
* Remove the {@link Constraint} from the list of constraints and from the {@link Database}.
*
* @param constraint
* The constraint to delete.
* @throws DatabaseAccessException
* if the write operation failed in {@link Database}.
*/
public void removeConstraint(Constraint constraint) throws DatabaseAccessException {
constraint.remove();
this.constraints.remove(constraint);
}
/**
* Delete this {@code Group} from the {@link Database}.
*
* @throws DatabaseAccessException
* if write operation failed in {@link Database}.
*/
public void remove() throws DatabaseAccessException {
try {
for (Constraint current : this.constraints) {
current.remove();
}
Statement stmt = this.database.getConnection().createStatement();
stmt.execute("DELETE FROM Groups WHERE Id=" + this.id + ";");
stmt.close();
this.groupController.informOberserver();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.WRITE);
}
}
/**
* Returns the max length for a {@code String} handled by the {@code Database}.
*
* @return the max length.
*/
public int maxStringLength() {
return DatabaseConfiguration.VARCHARLENGTH;
}
@Override
public String toString() {
return this.getName();
}
/**
* The method is used to set the the static parameter {@code idCountStaticConstraint}.
*
* @param idCountStaticConstraint
* the start count.
*/
public static void setIdCountStaticConstraint(int idCountStaticConstraint) {
Group.idCountStaticConstraint = idCountStaticConstraint;
}
/**
* This method dismisses the current intern list of {@link Constraint}s and rebuilds it with the {@link Constraint}s
* stored in the {@link Database}.
*
* @throws DatabaseAccessException
* if read operation failed in {@link Database}.
*/
public void rebuildConstraintsFromDatabase() throws DatabaseAccessException {
this.constraints = new ArrayList<Constraint>();
try {
// get all needed features and build the features from database
Feature[] features = getRequiredFeaturesToRebuildDynamicConstraints();
rebuildDynamicConstraints(features);
rebuildStaticConstraints();
} catch (SQLException e) {
throw new DatabaseAccessException(Failure.READ);
}
}
/**
* The method gets all needed features to rebuild the list of dynamic constraints and creates the matching features.
*
* @return the created list of {@link Feature}s.
* @throws SQLException
* error in database access.
*/
private Feature[] getRequiredFeaturesToRebuildDynamicConstraints() throws SQLException {
Statement stmt = this.database.getConnection().createStatement();
ArrayList<Integer> neededFeatures = new ArrayList<Integer>();
// get all needed Features form the database
ResultSet rs = stmt.executeQuery("SELECT FeatureReference FROM DynamicConstraints WHERE GroupReference="
+ this.id + ";");
while (rs.next()) {
neededFeatures.add(rs.getInt(1));
}
Feature[] features = new Feature[neededFeatures.size()];
for (int i = 0; i < features.length; i++) {
int featureId = neededFeatures.get(i);
if (featureId < 0) {
// this is legal because we don't need this values when using this function
features[i] = new Feature(this.groupController.getSubspaceController(), this.database, -1,
"Effect. Outlierness", false, true, 0, 1);
} else {
rs = stmt.executeQuery("SELECT Name, OutlierFlag, Min, Max"
+ " FROM Features WHERE Id=" + featureId + ";");
features[i] = new Feature(this.groupController.getSubspaceController(), this.database, featureId,
rs.getString(1), rs.getBoolean(2), false, rs.getFloat(3), rs
.getFloat(4));
}
}
stmt.close();
return features;
}
/**
* The method rebuilds all {@link DynamicConstraint}s and adds them to the list of {@code Constraint}s.
*
* @param features
* the list of {@link Feature}s required in the {@link DynamicConstraints}.
* @throws SQLException
* error in database access.
*/
private void rebuildDynamicConstraints(Feature[] features) throws SQLException {
Statement stmt = this.database.getConnection().createStatement();
// get all dynamic constraints and add them to the constraint list
ResultSet rs = stmt.executeQuery("SELECT FeatureReference, Id, Operator, Value, Active"
+ " FROM DynamicConstraints WHERE GroupReference=" + this.id + ";");
while (rs.next()) {
boolean found = false;
Feature feature = null;
int searchId = rs.getInt(1);
// get the required feature
for (int j = 0; j < features.length && !found; j++) {
if (features[j].getId() == searchId) {
feature = features[j];
found = true;
}
}
this.constraints.add(new DynamicConstraint(this.groupController, this.database, rs.getInt(2), feature,
Operator.values()[rs.getInt(3)], rs.getFloat(4), rs.getBoolean(5)));
}
stmt.close();
}
/**
* The method rebuilds all {@link StaticConstraint}s and adds them to the list of constraints.
*
* @throws SQLException
* error in database access.
*/
private void rebuildStaticConstraints() throws SQLException {
Statement stmt = this.database.getConnection().createStatement();
ResultSet rs = stmt.executeQuery("SELECT DISTINCT Id FROM StaticConstraints WHERE GroupReference=" + this.id
+ ";");
ArrayList<Integer> staticConstraintIds = new ArrayList<Integer>();
while (rs.next()) {
staticConstraintIds.add(rs.getInt(1));
}
for (int i = 0; i < staticConstraintIds.size(); i++) {
int constraintID = staticConstraintIds.get(i);
rs = stmt.executeQuery("SELECT Active, ObjectReference"
+ " FROM StaticConstraints WHERE GroupReference=" + this.id + " AND Id="
+ constraintID + ";");
ArrayList<Integer> selection = new ArrayList<Integer>();
boolean active = rs.getBoolean(1);
selection.add(rs.getInt(2));
while (rs.next()) {
selection.add(rs.getInt(2));
}
int[] selectionArray = new int[selection.size()];
for (int j = 0; j < selection.size(); j++) {
selectionArray[j] = selection.get(j);
}
this.constraints.add(new StaticConstraint(this.groupController, this.database,
constraintID, this.id, selectionArray, active));
}
stmt.close();
}
}