/*
* Copyright (c) 2010, SQL Power Group Inc.
*
* This file is part of SQL Power Library.
*
* SQL Power Library is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* SQL Power Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package ca.sqlpower.sqlobject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import javax.sql.RowSetMetaData;
import ca.sqlpower.object.SPObject;
import ca.sqlpower.object.SPObjectNameComparator;
import ca.sqlpower.object.annotation.Accessor;
import ca.sqlpower.object.annotation.Constructor;
import ca.sqlpower.object.annotation.ConstructorParameter;
import ca.sqlpower.object.annotation.ConstructorParameter.ParameterType;
import ca.sqlpower.object.annotation.Mutator;
import ca.sqlpower.object.annotation.NonProperty;
import ca.sqlpower.sql.JDBCDataSourceType;
import ca.sqlpower.sqlobject.SQLTypePhysicalPropertiesProvider.PropertyType;
import ca.sqlpower.util.SQLPowerUtils;
/**
* A container class that stores several specific database platform-dependent
* physical properties for a {@link JDBCSQLType} for a single specific physical platform
*/
public class SQLTypePhysicalProperties extends SQLObject implements SQLCheckConstraintContainer {
/**
* {@link SQLTypePhysicalProperties} supports check constraints, and
* enumeration constraints, but not both
*/
public enum SQLTypeConstraint {
/**
* This SQLType has neither check nor enumeration constraints on this
* platform
*/
NONE,
/**
* This SQLType has a check constraint for this platform
*/
CHECK,
/**
* This SQLType has an enumeration constraint for this platform
*/
ENUM
}
/**
* The physical precision property for this type. {@link RowSetMetaData}
* {@link #setPrecision(int)} defines it as 'the total number of decimal
* digits'.
*/
private Integer precision;
/**
* The physical scale property for this type. {@link RowSetMetaData}
* {@link #setScale(int)} defines it as 'the number of digits to right of
* decimal point'.
*/
private Integer scale;
/**
* A String representation of the Default value for the physical type. Note
* that the proper String representation will be dependent on the type and
* physical platform.
*/
private String defaultValue;
/**
* Indicates which constraint type applies to this physical type.
*/
private SQLTypeConstraint constraintType;
/**
* Check constraints on this SQLType. Note that you cannot use both this AND
* the enumeration.
*/
private List<SQLCheckConstraint> checkConstraints = new ArrayList<SQLCheckConstraint>();
/**
* Enumeration constraint. It is a list Strings representing values that the
* type is constrained to. Note that you cannot use both this AND the
* checkConstraints.
*/
private List<SQLEnumeration> enumerations = new ArrayList<SQLEnumeration>();
/**
* The logical name of the physical database platform.
*
* This would be the same value that you would get if you called
* {@link JDBCDataSourceType#getName()} on the {@link JDBCDataSourceType}
* representing the physical platform of this type. This can also be set
* to {@link SQLTypePhysicalPropertiesProvider#GENERIC_PLATFORM}.
*/
private final String platform;
/**
* The {@link PropertyType} of the precision. It determines if it is meant to
* be variable ({@link PropertyType#VARIABLE}), have a constant value (
* {@link PropertyType#CONSTANT}), or it doesn't apply to this type (
* {@link PropertyType#NOT_APPLICABLE}).
*/
private PropertyType precisionType;
/**
* The {@link PropertyType} value for scale. It determines if it is meant to
* be variable ({@link PropertyType#VARIABLE}), have a constant value (
* {@link PropertyType#CONSTANT}), or it doesn't apply to this type (
* {@link PropertyType#NOT_APPLICABLE}).
*/
private PropertyType scaleType;
/**
* List of allowed child types, which is empty since
* {@link SQLTypePhysicalProperties} has no children
*/
public static final List<Class<? extends SPObject>> allowedChildTypes =
Collections.unmodifiableList(new ArrayList<Class<? extends SPObject>>(
Arrays.asList(SQLCheckConstraint.class, SQLEnumeration.class)));
/**
* Copies all non-final properties (except UUID) and children from one
* {@link SQLTypePhysicalProperties} object to another.
*
* @param target
* The target {@link SQLTypePhysicalProperties} to copy to.
* @param source
* The source {@link SQLTypePhysicalProperties} to copy from.
*/
static final void copyProperties(
final SQLTypePhysicalProperties target,
final SQLTypePhysicalProperties source) {
if (!areEqual(target, source)) {
target.begin("Matching properties");
target.setName(source.getName());
target.setPhysicalName(source.getPhysicalName());
target.setPrecision(source.getPrecision());
target.setPrecisionType(source.getPrecisionType());
target.setScale(source.getScale());
target.setScaleType(source.getScaleType());
target.setDefaultValue(source.getDefaultValue());
target.setConstraintType(source.getConstraintType());
final List<SQLCheckConstraint> sourceCheckConstraints =
new ArrayList<SQLCheckConstraint>(source.getCheckConstraints());
final List<SQLCheckConstraint> targetCheckConstraints =
new ArrayList<SQLCheckConstraint>(target.getCheckConstraints());
final List<SQLEnumeration> sourceEnumerations =
new ArrayList<SQLEnumeration>(source.getChildrenWithoutPopulating(SQLEnumeration.class));
final List<SQLEnumeration> targetEnumerations =
new ArrayList<SQLEnumeration>(target.getChildrenWithoutPopulating(SQLEnumeration.class));
final SPObjectNameComparator nameComparator = new SPObjectNameComparator();
Collections.sort(sourceCheckConstraints, nameComparator);
Collections.sort(targetCheckConstraints, nameComparator);
Collections.sort(sourceEnumerations, nameComparator);
Collections.sort(targetEnumerations, nameComparator);
for (int i = 0, j = 0; i < sourceCheckConstraints.size() || j < targetCheckConstraints.size();) {
int compare = 0;
SQLCheckConstraint sourceConstraint;
if (i < sourceCheckConstraints.size()) {
sourceConstraint = sourceCheckConstraints.get(i);
} else {
sourceConstraint = null;
compare = 1;
}
SQLCheckConstraint targetConstraint;
if (j < targetCheckConstraints.size()) {
targetConstraint = targetCheckConstraints.get(i);
} else {
targetConstraint = null;
compare = -1;
}
if (compare == 0) {
compare = nameComparator.compare(sourceConstraint, targetConstraint);
}
if (compare < 0) {
target.addCheckConstraint(new SQLCheckConstraint(sourceConstraint));
i++;
} else if (compare > 0) {
target.removeCheckConstraint(targetConstraint);
j++;
} else {
if (!sourceConstraint.getConstraint().equals(targetConstraint.getConstraint())) {
targetConstraint.setConstraint(sourceConstraint.getConstraint());
}
i++;
j++;
}
}
for (int i = 0, j = 0; i < sourceEnumerations.size() || j < targetEnumerations.size();) {
int compare = 0;
SQLEnumeration sourceEnumeration;
if (i < sourceEnumerations.size()) {
sourceEnumeration = sourceEnumerations.get(i);
} else {
sourceEnumeration = null;
compare = 1;
}
SQLEnumeration targetEnumeration;
if (j < targetEnumerations.size()) {
targetEnumeration = targetEnumerations.get(i);
} else {
targetEnumeration = null;
compare = -1;
}
if (compare == 0) {
compare = nameComparator.compare(sourceEnumeration, targetEnumeration);
}
if (compare < 0) {
target.addEnumeration(new SQLEnumeration(sourceEnumeration));
i++;
} else if (compare > 0) {
target.removeEnumeration(targetEnumeration);
j++;
} else {
i++;
j++;
}
}
target.commit();
}
}
@Constructor
public SQLTypePhysicalProperties(@ConstructorParameter(parameterType=ParameterType.PROPERTY, propertyName="platform") String platformName) {
platform = platformName;
setName("SQLTypePhysicalProperties for " + platform);
}
/**
* Copy constructor.
*
* @param properties
* The {@link SQLTypePhysicalProperties} to copy to this
* instance.
*/
public SQLTypePhysicalProperties(SQLTypePhysicalProperties properties) {
platform = properties.getPlatform();
updateToMatch(properties);
}
@Override
protected void addChildImpl(SPObject child, int index) {
if (child instanceof SQLCheckConstraint) {
addCheckConstraint((SQLCheckConstraint) child, index);
} else if (child instanceof SQLEnumeration) {
addEnumeration((SQLEnumeration) child, index);
} else {
throw new IllegalArgumentException("The child " + child.getName() +
" of type " + child.getClass() + " is not a valid child type of " +
getClass() + ".");
}
}
public void addCheckConstraint(SQLCheckConstraint checkConstraint) {
addCheckConstraint(checkConstraint, getChildrenWithoutPopulating(SQLCheckConstraint.class).size());
}
public void addCheckConstraint(SQLCheckConstraint checkConstraint, int index) {
checkConstraints.add(index, checkConstraint);
checkConstraint.setParent(this);
fireChildAdded(SQLCheckConstraint.class, checkConstraint, index);
}
public void addEnumeration(SQLEnumeration child) {
addEnumeration(child, getChildrenWithoutPopulating(SQLEnumeration.class).size());
}
public void addEnumeration(SQLEnumeration child, int index) {
enumerations.add(index, child);
child.setParent(this);
fireChildAdded(SQLEnumeration.class, child, index);
}
/**
* Returns the precision property as an Integer.
*
* @return An Integer instance representing the precision property value. If it
* returns null, then the precision has not been set for this
* {@link SQLTypePhysicalProperties} instance.
*/
@Accessor
public Integer getPrecision() {
return precision;
}
@Mutator
public void setPrecision(Integer precision) {
begin("Setting precision.");
Integer oldValue = this.precision;
this.precision = precision;
firePropertyChange("precision", (Integer) oldValue, (Integer) precision);
commit();
}
/**
* Returns the scale property as an Integer.
*
* @return An Integer instance representing the scale property value. If it
* returns null, then the scale has not been set for this
* {@link SQLTypePhysicalProperties} instance.
*/
@Accessor
public Integer getScale() {
return scale;
}
@Mutator
public void setScale(Integer scale) {
begin("Setting scale.");
Integer oldValue = this.scale;
this.scale = scale;
firePropertyChange("scale", (Integer) oldValue, (Integer) scale);
commit();
}
@Accessor
public String getDefaultValue() {
return defaultValue;
}
@Mutator
public void setDefaultValue(String defaultValue) {
begin("Setting default value.");
String oldValue = this.defaultValue;
this.defaultValue = defaultValue;
firePropertyChange("defaultValue", oldValue, defaultValue);
commit();
}
@Accessor
public SQLTypeConstraint getConstraintType() {
return constraintType;
}
@Mutator
public void setConstraintType(SQLTypeConstraint constraint) {
begin("Setting constraint type.");
SQLTypeConstraint oldValue = this.constraintType;
this.constraintType = constraint;
firePropertyChange("constraintType", oldValue, constraintType);
commit();
}
@Override
public List<? extends SQLObject> getChildrenWithoutPopulating() {
List<SQLObject> children = new ArrayList<SQLObject>();
children.addAll(checkConstraints);
children.addAll(enumerations);
return Collections.unmodifiableList(children);
}
@Override
public String getShortDisplayName() {
return getName();
}
@Override
protected void populateImpl() throws SQLObjectException {
// no children, so no-op
}
@Override
protected boolean removeChildImpl(SPObject child) {
if (child instanceof SQLCheckConstraint) {
return removeCheckConstraint((SQLCheckConstraint) child);
} else if (child instanceof SQLEnumeration) {
return removeEnumeration((SQLEnumeration) child);
}
return false;
}
public boolean removeCheckConstraint(SQLCheckConstraint child) {
int index = checkConstraints.indexOf(child);
if (index != -1) {
checkConstraints.remove(index);
fireChildRemoved(SQLCheckConstraint.class, child, index);
child.setParent(null);
return true;
}
return false;
}
public boolean removeEnumeration(SQLEnumeration child) {
int index = enumerations.indexOf(child);
if (index != -1) {
enumerations.remove(index);
fireChildRemoved(SQLEnumeration.class, child, index);
child.setParent(null);
return true;
}
return false;
}
@NonProperty
public List<Class<? extends SPObject>> getAllowedChildTypes() {
return allowedChildTypes;
}
public List<? extends SPObject> getDependencies() {
return Collections.emptyList();
}
public void removeDependency(SPObject dependency) {
// No dependencies, so no-op
}
@Accessor
public String getPlatform() {
return platform;
}
@Accessor
public PropertyType getPrecisionType() {
return precisionType;
}
@Mutator
public void setPrecisionType(PropertyType precisionType) {
begin("Setting precision type.");
PropertyType oldValue = this.precisionType;
this.precisionType = precisionType;
firePropertyChange("precisionType", oldValue, precisionType);
commit();
}
@Accessor
public PropertyType getScaleType() {
return scaleType;
}
@Mutator
public void setScaleType(PropertyType scaleType) {
begin("Setting scale type.");
PropertyType oldValue = this.scaleType;
this.scaleType = scaleType;
firePropertyChange("scaleType", oldValue, scaleType);
commit();
}
/**
* Updates the properties of this properties object to match the parameter.
* Does not change this properties platform or parent.
*
* @param matchMe
* the SQLTypePhysicalProperties to match
*/
@Override
public void updateToMatch(SQLObject matchMe) {
if (!(matchMe instanceof SQLTypePhysicalProperties)) {
throw new ClassCastException("Only " +
SQLTypePhysicalProperties.class.getSimpleName() +
" can be copied to " +
SQLTypePhysicalProperties.class.getSimpleName() + ".");
}
copyProperties(this, (SQLTypePhysicalProperties) matchMe);
}
/**
* Compares two {@link SQLTypePhysicalProperties} objects to see if they are
* equal in all properties (except UUID) and children (
* {@link SQLCheckConstraint}s and {@link SQLEnumeration}s).
*
* @param prop1
* The first of two {@link SQLTypePhysicalProperties} objects to
* compare.
* @param prop2
* The second of two {@link SQLTypePhysicalProperties} objects to
* compare.
* @return true iff the two {@link SQLTypePhysicalProperties} objects are
* equal.
*/
public static boolean areEqual(SQLTypePhysicalProperties prop1, SQLTypePhysicalProperties prop2) {
boolean equal = SQLPowerUtils.areEqual(prop1.getName(), prop2.getName())
&& SQLPowerUtils.areEqual(prop1.getPhysicalName(), prop2.getPhysicalName())
&& SQLPowerUtils.areEqual(prop1.getPrecision(), prop2.getPrecision())
&& SQLPowerUtils.areEqual(prop1.getPrecisionType(), prop2.getPrecisionType())
&& SQLPowerUtils.areEqual(prop1.getScale(), prop2.getScale())
&& SQLPowerUtils.areEqual(prop1.getScaleType(), prop2.getScaleType())
&& SQLPowerUtils.areEqual(prop1.getDefaultValue(), prop2.getDefaultValue())
&& SQLPowerUtils.areEqual(prop1.getConstraintType(), prop2.getConstraintType());
equal &= prop1.getCheckConstraints().size() == prop2.getCheckConstraints().size();
equal &= prop1.getChildrenWithoutPopulating(SQLEnumeration.class).size() == prop2.getChildrenWithoutPopulating(SQLEnumeration.class).size();
if (!equal) {
return false;
}
List<SQLCheckConstraint> checkConstraints1 =
new ArrayList<SQLCheckConstraint>(prop1.getCheckConstraints());
List<SQLCheckConstraint> checkConstraints2 =
new ArrayList<SQLCheckConstraint>(prop2.getCheckConstraints());
List<SQLEnumeration> enumerations1 =
new ArrayList<SQLEnumeration>(prop1.getChildrenWithoutPopulating(SQLEnumeration.class));
List<SQLEnumeration> enumerations2 =
new ArrayList<SQLEnumeration>(prop2.getChildrenWithoutPopulating(SQLEnumeration.class));
SPObjectNameComparator nameComparator = new SPObjectNameComparator();
Collections.sort(checkConstraints1, nameComparator);
Collections.sort(checkConstraints2, nameComparator);
for (int i = 0; i < checkConstraints1.size() && equal; i++) {
SQLCheckConstraint constraint1 = checkConstraints1.get(i);
SQLCheckConstraint constraint2 = checkConstraints2.get(i);
equal &= SQLPowerUtils.areEqual(constraint1.getName(), constraint2.getName());
equal &= SQLPowerUtils.areEqual(constraint1.getConstraint(), constraint2.getConstraint());
}
if (!equal) {
return false;
}
Collections.sort(enumerations1, nameComparator);
Collections.sort(enumerations2, nameComparator);
for (int i = 0; i < enumerations1.size() && equal; i++) {
SQLEnumeration enum1 = enumerations1.get(i);
SQLEnumeration enum2 = enumerations2.get(i);
equal &= SQLPowerUtils.areEqual(enum1.getName(), enum2.getName());
}
return equal;
}
@NonProperty
public List<SQLCheckConstraint> getCheckConstraints() {
return getChildrenWithoutPopulating(SQLCheckConstraint.class);
}
}