summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian S. O'Neill <bronee@gmail.com>2006-08-30 01:50:06 +0000
committerBrian S. O'Neill <bronee@gmail.com>2006-08-30 01:50:06 +0000
commit8365fcc3a5b2285fc1fe442d6f2eb8a90cbbab3a (patch)
tree62c8fdc91b116224aa5dd988b7ef1e1f2c754617
parent59bf9d28054da49fc2e984fce43a7840ddee5424 (diff)
Add standard capabilities
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/ConstraintDefinition.java111
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/FloatConstraint.java201
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/IntegerConstraint.java233
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/LengthConstraint.java173
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/TextConstraint.java152
-rw-r--r--src/main/java/com/amazon/carbonado/constraint/package-info.java24
6 files changed, 894 insertions, 0 deletions
diff --git a/src/main/java/com/amazon/carbonado/constraint/ConstraintDefinition.java b/src/main/java/com/amazon/carbonado/constraint/ConstraintDefinition.java
new file mode 100644
index 0000000..22514fc
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/ConstraintDefinition.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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 com.amazon.carbonado.constraint;
+
+import java.lang.annotation.*;
+
+/**
+ * Allows annotations to be defined that restrict property values. The
+ * annotation is just a pointer to a constraint checking class. If the
+ * constraint class is not explicitly provided, it defaults to a static inner
+ * class named "Constraint" in the annotation itself.
+ *
+ * <p>The constraint class must have a public constructor that accepts the
+ * annotation that has the ConstraintDefinition annotation. It must also define
+ * several "constrain" methods which perform constraint checks on specific
+ * property types.
+ * <p>
+ * Example integer constraint:
+ * <pre>
+ * &#64;Documented
+ * <b>&#64;Retention(RetentionPolicy.RUNTIME)</b>
+ * <b>&#64;Target(ElementType.METHOD)</b>
+ * <b>&#64;ConstraintDefinition</b>
+ * public &#64;interface IntegerConstraint {
+ * int min() default Integer.MIN_VALUE;
+ *
+ * int max() default Integer.MAX_VALUE;
+ *
+ * public static class Constraint {
+ * private final String propertyName;
+ * private final int min;
+ * private final int max;
+ *
+ * // Constructor may throw a MalformedTypeException if
+ * // params supplied by annotation are illegal.
+ *
+ * /**
+ * * @param type optional type of object that contains the constrained property
+ * * @param propertyName name of property with constraint
+ * * @param annotation specific annotation that binds to this constraint class
+ * *&#47;
+ * public Constraint(Class type, String propertyName, IntegerConstraint annotation) {
+ * this.propertyName = propertyName;
+ * this.min = annotation.min();
+ * this.max = annotation.max();
+ * }
+ *
+ * // Define a constrain method for each supported property type.
+ *
+ * /**
+ * * @param propertyValue specific value to constrain
+ * *&#47;
+ * public void constrain(int propertyValue) throws IllegalArgumentException {
+ * if (propertyValue < min || propertyValue > max) {
+ * throw new IllegalArgumentException
+ * ("Value for \"" + propertyName + "\" must be in range " +
+ * min + ".." + max + ": " + propertyValue);
+ * }
+ * }
+ * }
+ * }
+ * </pre>
+ *
+ * The newly defined integer constraint can be applied to property mutators.
+ *
+ * <pre>
+ * public interface UserInfo extends Storable {
+ * ...
+ *
+ * // Constraint is called before setting age.
+ * <b>&#64;IntegerConstraint(min=0, max=120)</b>
+ * void setAge(int value);
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface ConstraintDefinition {
+ /**
+ * Specify class which will perform constraint checking. Must have a public
+ * constructor with the signature
+ * <code>(Class type, String propertyName, <i>Annotation</i>)</code>,
+ * where <code><i>Annotation</i></code> refers to the annotation with the
+ * constraint definition.
+ *
+ * <p>The implementation class need not be explicitly specified. By
+ * default, the constraint class must be a static inner class of the
+ * annotation, named "Constraint".
+ */
+ // Use void.class to mean default.
+ Class implementation() default void.class;
+}
diff --git a/src/main/java/com/amazon/carbonado/constraint/FloatConstraint.java b/src/main/java/com/amazon/carbonado/constraint/FloatConstraint.java
new file mode 100644
index 0000000..31b6503
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/FloatConstraint.java
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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 com.amazon.carbonado.constraint;
+
+import java.lang.annotation.*;
+import java.util.Arrays;
+
+import com.amazon.carbonado.MalformedTypeException;
+
+/**
+ * Limits the value of a property to be a member of a specific set. The
+ * property value may be a boxed or unboxed float, double, String,
+ * CharSequence, char, Character, or character array. If the property value is
+ * outside the set, an IllegalArgumentException is thrown.
+ *
+ * <p>Example:<pre>
+ * public interface PolarCoordinate extends Storable {
+ * double getTheta();
+ *
+ * <b>&#64;FloatConstraint(min=0, max=Math.PI * 2, disallowed=Double.NaN)</b>
+ * void setTheta(double radians);
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see IntegerConstraint
+ * @see TextConstraint
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@ConstraintDefinition
+public @interface FloatConstraint {
+ /**
+ * Specific allowed values for property. Default is unlimited.
+ */
+ double[] allowed() default {};
+
+ /**
+ * Specific disallowed values for property. Default is none.
+ */
+ double[] disallowed() default {};
+
+ /**
+ * Specify minimum allowed value for float/double property. Default is unlimited.
+ */
+ double min() default Double.MIN_VALUE;
+
+ /**
+ * Specify maximum allowed value for float/double property. Default is unlimited.
+ */
+ double max() default Double.MAX_VALUE;
+
+ /**
+ * Constraint implementation for {@link FloatConstraint}.
+ */
+ public static class Constraint {
+ private final String mPropertyName;
+ private final double mMinValue;
+ private final double mMaxValue;
+
+ /** Disallowed values, sorted for binary search. */
+ private final double[] mDisallowed;
+
+ /** Allowed values, sorted for binary search. */
+ private final double[] mAllowed;
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param ann specific annotation that binds to this constraint class
+ */
+ public Constraint(Class<?> type, String propertyName, FloatConstraint ann) {
+ this(type, propertyName,
+ ann.min(), ann.max(), ann.allowed(), ann.disallowed());
+ }
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param min minimum allowed value
+ * @param max maximum allowed value
+ * @param allowed optional set of allowed values
+ * @param disallowed optional set of disallowed values
+ */
+ public Constraint(Class<?> type, String propertyName,
+ double min, double max, double[] allowed, double[] disallowed) {
+ mPropertyName = propertyName;
+ mMinValue = min;
+ mMaxValue = max;
+ if (mMaxValue < mMinValue) {
+ throw new MalformedTypeException
+ (type, "Illegal range for float constraint on property \"" +
+ propertyName + "\": " + rangeString());
+ }
+
+ if (disallowed == null || disallowed.length == 0) {
+ disallowed = null;
+ } else {
+ disallowed = disallowed.clone();
+ Arrays.sort(disallowed);
+ }
+
+ if (allowed == null || allowed.length == 0) {
+ allowed = null;
+ } else {
+ allowed = allowed.clone();
+ Arrays.sort(allowed);
+ for (double value : allowed) {
+ if (value < mMinValue || value > mMaxValue ||
+ (disallowed != null && Arrays.binarySearch(disallowed, value) >= 0)) {
+ throw new MalformedTypeException
+ (type, "Allowed value contradiction for float constraint " +
+ "on property \"" + propertyName + "\": " + value);
+ }
+ }
+
+ // No need to have a set of disallowed values.
+ disallowed = null;
+ }
+
+ mDisallowed = disallowed;
+ mAllowed = allowed;
+ }
+
+ public void constrain(double propertyValue) {
+ if (propertyValue < mMinValue || propertyValue > mMaxValue) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" must be in range " +
+ rangeString() + ": " + propertyValue);
+ }
+ if (mDisallowed != null && Arrays.binarySearch(mDisallowed, propertyValue) >= 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is disallowed: " + propertyValue);
+ }
+ if (mAllowed != null && Arrays.binarySearch(mAllowed, propertyValue) < 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not allowed: " + propertyValue);
+ }
+ }
+
+ public void constrain(CharSequence propertyValue) {
+ if (propertyValue != null) {
+ try {
+ constrain(Double.parseDouble(propertyValue.toString()));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not a number: " +
+ propertyValue);
+ }
+ }
+ }
+
+ public void constrain(char propertyValue) {
+ if ('0' <= propertyValue && propertyValue <= '9') {
+ constrain((double) (propertyValue - '0'));
+ } else {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not a number: " + propertyValue);
+ }
+ }
+
+ public void constrain(char[] propertyValue) {
+ if (propertyValue != null) {
+ constrain(new String(propertyValue));
+ }
+ }
+
+ private String rangeString() {
+ StringBuilder b = new StringBuilder();
+ b.append('(');
+ if (mMinValue > Double.MIN_VALUE) {
+ b.append(mMinValue);
+ }
+ b.append("..");
+ if (mMaxValue < Double.MAX_VALUE) {
+ b.append(mMaxValue);
+ }
+ b.append(')');
+ return b.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/constraint/IntegerConstraint.java b/src/main/java/com/amazon/carbonado/constraint/IntegerConstraint.java
new file mode 100644
index 0000000..69fa5cc
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/IntegerConstraint.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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 com.amazon.carbonado.constraint;
+
+import java.lang.annotation.*;
+import java.util.Arrays;
+
+import com.amazon.carbonado.MalformedTypeException;
+
+/**
+ * Limits the value of a property to be a member of a specific set. The
+ * property value may be a boxed or unboxed byte, short, int, long, float,
+ * double, String, CharSequence, char, Character, or character array. If the
+ * property value is outside the set, an IllegalArgumentException is thrown.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable {
+ * int getAge();
+ *
+ * <b>&#64;IntegerConstraint(min=0, max=120)</b>
+ * void setAge(int value);
+ *
+ * int getRoleID();
+ *
+ * <b>&#64;IntegerConstraint(allowed={ROLE_REGULAR, ROLE_ADMIN})</b>
+ * void setRoleID(int role);
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see FloatConstraint
+ * @see TextConstraint
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@ConstraintDefinition
+public @interface IntegerConstraint {
+ /**
+ * Specific allowed values for property. Default is unlimited.
+ */
+ long[] allowed() default {};
+
+ /**
+ * Specific disallowed values for property. Default is none.
+ */
+ long[] disallowed() default {};
+
+ /**
+ * Specify minimum allowed value for integer property. Default is unlimited.
+ */
+ long min() default Long.MIN_VALUE;
+
+ /**
+ * Specify maximum allowed value for integer property. Default is unlimited.
+ */
+ long max() default Long.MAX_VALUE;
+
+ /**
+ * Constraint implementation for {@link IntegerConstraint}.
+ */
+ public static class Constraint {
+ private final String mPropertyName;
+
+ private final long mMinValue;
+ private final long mMaxValue;
+
+ /** Disallowed values, sorted for binary search. */
+ private final long[] mDisallowed;
+
+ /** Allowed values, sorted for binary search. */
+ private final long[] mAllowed;
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param ann specific annotation that binds to this constraint class
+ */
+ public Constraint(Class<?> type, String propertyName, IntegerConstraint ann) {
+ this(type, propertyName,
+ ann.min(), ann.max(), ann.allowed(), ann.disallowed());
+ }
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param min minimum allowed value
+ * @param max maximum allowed value
+ * @param allowed optional set of allowed values
+ * @param disallowed optional set of disallowed values
+ */
+ public Constraint(Class<?> type, String propertyName,
+ long min, long max, long[] allowed, long[] disallowed) {
+ mPropertyName = propertyName;
+ mMinValue = min;
+ mMaxValue = max;
+ if (mMaxValue < mMinValue) {
+ throw new MalformedTypeException
+ (type, "Illegal range for integer constraint on property \"" +
+ propertyName + "\": " + rangeString());
+ }
+
+ if (disallowed == null || disallowed.length == 0) {
+ disallowed = null;
+ } else {
+ disallowed = disallowed.clone();
+ Arrays.sort(disallowed);
+ }
+
+ if (allowed == null || allowed.length == 0) {
+ allowed = null;
+ } else {
+ allowed = allowed.clone();
+ Arrays.sort(allowed);
+ for (long value : allowed) {
+ if (value < mMinValue || value > mMaxValue ||
+ (disallowed != null && Arrays.binarySearch(disallowed, value) >= 0)) {
+ throw new MalformedTypeException
+ (type, "Allowed value contradiction for integer constraint " +
+ "on property \"" + propertyName + "\": " + value);
+ }
+ }
+
+ // No need to have a set of disallowed values.
+ disallowed = null;
+ }
+
+ mDisallowed = disallowed;
+ mAllowed = allowed;
+ }
+
+ public void constrain(long propertyValue) {
+ if (propertyValue < mMinValue || propertyValue > mMaxValue) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" must be in range " +
+ rangeString() + ": " + propertyValue);
+ }
+ if (mDisallowed != null && Arrays.binarySearch(mDisallowed, propertyValue) >= 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is disallowed: " + propertyValue);
+ }
+ if (mAllowed != null && Arrays.binarySearch(mAllowed, propertyValue) < 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not allowed: " + propertyValue);
+ }
+ }
+
+ public void constrain(double propertyValue) {
+ if (propertyValue < mMinValue || propertyValue > mMaxValue) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" must be in range " +
+ rangeString() + ": " + propertyValue);
+ }
+ if (mDisallowed != null) {
+ long longValue = (long) propertyValue;
+ if (longValue == propertyValue &&
+ Arrays.binarySearch(mDisallowed, longValue) >= 0) {
+
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is disallowed: " + propertyValue);
+ }
+ }
+ if (mAllowed != null) {
+ long longValue = (long) propertyValue;
+ if (longValue != propertyValue ||
+ Arrays.binarySearch(mAllowed, longValue) < 0) {
+
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not allowed: " + propertyValue);
+ }
+ }
+ }
+
+ public void constrain(CharSequence propertyValue) {
+ if (propertyValue != null) {
+ try {
+ constrain(Long.parseLong(propertyValue.toString()));
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not an integer: " +
+ propertyValue);
+ }
+ }
+ }
+
+ public void constrain(char propertyValue) {
+ if ('0' <= propertyValue && propertyValue <= '9') {
+ constrain((long) (propertyValue - '0'));
+ } else {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not an integer: " + propertyValue);
+ }
+ }
+
+ public void constrain(char[] propertyValue) {
+ if (propertyValue != null) {
+ constrain(new String(propertyValue));
+ }
+ }
+
+ private String rangeString() {
+ StringBuilder b = new StringBuilder();
+ b.append('(');
+ if (mMinValue > Long.MIN_VALUE) {
+ b.append(mMinValue);
+ }
+ b.append("..");
+ if (mMaxValue < Long.MAX_VALUE) {
+ b.append(mMaxValue);
+ }
+ b.append(')');
+ return b.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/constraint/LengthConstraint.java b/src/main/java/com/amazon/carbonado/constraint/LengthConstraint.java
new file mode 100644
index 0000000..7e17a1f
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/LengthConstraint.java
@@ -0,0 +1,173 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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 com.amazon.carbonado.constraint;
+
+import java.lang.annotation.*;
+
+import com.amazon.carbonado.MalformedTypeException;
+
+/**
+ * Limits the value of a property to lie within a specific length range. The
+ * property value may be a String, CharSequence, or any kind of array. If the
+ * set property size is outside the range, an IllegalArgumentException is
+ * thrown.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable {
+ * int getFirstName();
+ *
+ * <b>&#64;LengthConstraint(min=1, max=50)</b>
+ * void setFirstName(String name);
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@ConstraintDefinition
+public @interface LengthConstraint {
+ /**
+ * Specify minimum allowed size for property. Default is zero.
+ */
+ int min() default 0;
+
+ /**
+ * Specify maximum allowed value for property. Default is unlimited.
+ */
+ int max() default Integer.MAX_VALUE;
+
+ /**
+ * Constraint implementation for {@link LengthConstraint}.
+ */
+ public static class Constraint {
+ private final String mPropertyName;
+ private final int mMinLength;
+ private final int mMaxLength;
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param ann specific annotation that binds to this constraint class
+ */
+ public Constraint(Class<?> type, String propertyName, LengthConstraint ann) {
+ this(type, propertyName, ann.min(), ann.max());
+ }
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param min minimum allowed size
+ * @param max maximum allowed size
+ */
+ public Constraint(Class<?> type, String propertyName, int min, int max) {
+ mPropertyName = propertyName;
+ mMinLength = min;
+ mMaxLength = max;
+ if (mMinLength < 0 || mMaxLength < mMinLength) {
+ throw new MalformedTypeException
+ (type, "Illegal length constraint for property \"" + propertyName +
+ "\": " + rangeString());
+ }
+ }
+
+ public void constrain(CharSequence str) {
+ if (str != null) {
+ constrainLength(str.length());
+ }
+ }
+
+ public void constrain(boolean[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(byte[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(short[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(char[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(int[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(long[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(float[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(double[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ public void constrain(Object[] array) {
+ if (array != null) {
+ constrainLength(array.length);
+ }
+ }
+
+ private void constrainLength(int length) {
+ if (length < mMinLength || length > mMaxLength) {
+ throw new IllegalArgumentException
+ ("Value length for \"" + mPropertyName + "\" must be in range " +
+ rangeString() + ": " + length);
+ }
+ }
+
+ private String rangeString() {
+ StringBuilder b = new StringBuilder();
+ b.append('(');
+ b.append(mMinLength);
+ b.append("..");
+ if (mMaxLength < Integer.MAX_VALUE) {
+ b.append(mMaxLength);
+ }
+ b.append(')');
+ return b.toString();
+ }
+ }
+}
diff --git a/src/main/java/com/amazon/carbonado/constraint/TextConstraint.java b/src/main/java/com/amazon/carbonado/constraint/TextConstraint.java
new file mode 100644
index 0000000..08574e8
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/TextConstraint.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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 com.amazon.carbonado.constraint;
+
+import java.lang.annotation.*;
+import java.util.Arrays;
+
+import com.amazon.carbonado.MalformedTypeException;
+
+/**
+ * Limits the value of a property to be a member of a specific set. The
+ * property value may be a String, CharSequence, char, Character, or character
+ * array. If the property value is outside the set, an IllegalArgumentException
+ * is thrown.
+ *
+ * <p>Example:<pre>
+ * public interface UserInfo extends Storable {
+ * char isActive();
+ *
+ * <b>&#64;TextConstraint(allowed={"Y", "N"})</b>
+ * void setActive(char value);
+ *
+ * ...
+ * }
+ * </pre>
+ *
+ * @author Brian S O'Neill
+ * @see IntegerConstraint
+ * @see FloatConstraint
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+@ConstraintDefinition
+public @interface TextConstraint {
+ /**
+ * Specific allowed values for property. Default is unlimited.
+ */
+ String[] allowed() default {};
+
+ /**
+ * Specific disallowed values for property. Default is none.
+ */
+ String[] disallowed() default {};
+
+ /**
+ * Constraint implementation for {@link TextConstraint}.
+ */
+ public static class Constraint {
+ private final String mPropertyName;
+
+ /** Allowed values, sorted for binary search. */
+ private final String[] mAllowed;
+
+ /** Disallowed values, sorted for binary search. */
+ private final String[] mDisallowed;
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param ann specific annotation that binds to this constraint class
+ */
+ public Constraint(Class<?> type, String propertyName, TextConstraint ann) {
+ this(type, propertyName, ann.allowed(), ann.disallowed());
+ }
+
+ /**
+ * @param type type of object that contains the constrained property
+ * @param propertyName name of property with constraint
+ * @param allowed optional set of allowed values
+ * @param disallowed optional set of disallowed values
+ */
+ public Constraint(Class<?> type, String propertyName,
+ String[] allowed, String[] disallowed) {
+ mPropertyName = propertyName;
+
+ if (disallowed == null || disallowed.length == 0) {
+ disallowed = null;
+ } else {
+ disallowed = disallowed.clone();
+ Arrays.sort(disallowed);
+ }
+
+ if (allowed == null || allowed.length == 0) {
+ allowed = null;
+ } else {
+ allowed = allowed.clone();
+ Arrays.sort(allowed);
+ if (disallowed != null) {
+ for (String value : allowed) {
+ if (Arrays.binarySearch(disallowed, value) >= 0) {
+ throw new MalformedTypeException
+ (type, "Allowed value contradiction for text constraint " +
+ "on property \"" + propertyName + "\": " + value);
+ }
+ }
+
+ // No need to have a set of disallowed values.
+ disallowed = null;
+ }
+ }
+
+ mDisallowed = disallowed;
+ mAllowed = allowed;
+ }
+
+ public void constrain(CharSequence propertyValue) {
+ constrain(propertyValue.toString());
+ }
+
+ public void constrain(String propertyValue) {
+ if (propertyValue == null) {
+ return;
+ }
+ if (mDisallowed != null && Arrays.binarySearch(mDisallowed, propertyValue) >= 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is disallowed: " + propertyValue);
+ }
+ if (mAllowed != null && Arrays.binarySearch(mAllowed, propertyValue) < 0) {
+ throw new IllegalArgumentException
+ ("Value for \"" + mPropertyName + "\" is not allowed: " + propertyValue);
+ }
+ }
+
+ public void constrain(char propertyValue) {
+ constrain(Character.toString(propertyValue));
+ }
+
+ public void constrain(char[] propertyValue) {
+ if (propertyValue != null) {
+ constrain(new String(propertyValue));
+ }
+ }
+ }
+}
+
diff --git a/src/main/java/com/amazon/carbonado/constraint/package-info.java b/src/main/java/com/amazon/carbonado/constraint/package-info.java
new file mode 100644
index 0000000..517c27e
--- /dev/null
+++ b/src/main/java/com/amazon/carbonado/constraint/package-info.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2006 Amazon Technologies, Inc. or its affiliates.
+ * Amazon, Amazon.com and Carbonado are trademarks or registered trademarks
+ * of Amazon Technologies, Inc. or its affiliates. All rights reserved.
+ *
+ * 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.
+ */
+
+/**
+ * Contains annotations and implementations for supporting property constraints.
+ *
+ * @see com.amazon.carbonado.constraint.ConstraintDefinition
+ */
+package com.amazon.carbonado.constraint;