wwutil.model.BuiltinFunc.java Source code

Java tutorial

Introduction

Here is the source code for wwutil.model.BuiltinFunc.java

Source

/******************************************************************************
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0.  If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 * 
 * Software distributed under the License is distributed on an "AS IS" basis, 
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for 
 * the specific language governing rights and limitations under the License.
 *
 * The Original Code is: Jsoda
 * The Initial Developer of the Original Code is: William Wong (williamw520@gmail.com)
 * Portions created by William Wong are Copyright (C) 2012 William Wong, All Rights Reserved.
 *
 ******************************************************************************/

package wwutil.model;

import java.io.*;
import java.net.*;
import java.util.*;
import java.util.regex.*;
import java.text.MessageFormat;
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.concurrent.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.beanutils.ConvertUtils;

import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.auth.AWSCredentials;

import wwutil.sys.BaseXUtil;
import wwutil.sys.ReflectUtil;
import wwutil.model.AnnotationRegistry;
import wwutil.model.AnnotationClassHandler;
import wwutil.model.AnnotationFieldHandler;
import wwutil.model.ValidationException;
import wwutil.model.MaskMatcher;
import wwutil.model.annotation.*;

/**
 * Built-in data generation and validation functions.
 */
public class BuiltinFunc {
    private static Pattern sEmailPattern = Pattern.compile(EmailMatch.regex);

    private static AnnotationRegistry sPreStore1Registry = new AnnotationRegistry();
    private static AnnotationRegistry sPreStore2Registry = new AnnotationRegistry();
    private static AnnotationRegistry sValidationRegistry = new AnnotationRegistry();
    private static AnnotationRegistry sPostLoadRegistry = new AnnotationRegistry();

    static {
        try {
            setupBuiltinPreStore1Handlers(sPreStore1Registry);
            setupBuiltinPreStore2Handlers(sPreStore2Registry);
            setupBuiltinValidationHandlers(sValidationRegistry);
            setupBuiltinPostLoadHandlers(sPostLoadRegistry);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    public static AnnotationRegistry clonePreStore1Registry() {
        return sPreStore1Registry.cloneRegistry();
    }

    public static AnnotationRegistry clonePreStore2Registry() {
        return sPreStore2Registry.cloneRegistry();
    }

    public static AnnotationRegistry cloneValidationRegistry() {
        return sValidationRegistry.cloneRegistry();
    }

    public static AnnotationRegistry clonePostLoadRegistry() {
        return sPostLoadRegistry.cloneRegistry();
    }

    ////////////////////////////////////////////////////////////////////////////
    // Stage 1 data handlers
    ////////////////////////////////////////////////////////////////////////////

    private static void setupBuiltinPreStore1Handlers(AnnotationRegistry registry) {

        registry.register(DefaultGUID.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @DefaultGUID field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object value = field.get(object);
                if (value == null || value.toString().length() == 0) {
                    boolean isShort = ReflectUtil.getAnnoValue(fieldAnnotation, "isShort", false);
                    String uuidStr = isShort ? BaseXUtil.uuid8() : BaseXUtil.uuid16();
                    field.set(object, uuidStr);
                }
            }
        });

        registry.register(ModifiedTime.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != java.util.Date.class)
                    throw new ValidationException(
                            "The @ModifiedTime field must be java.util.Date type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                field.set(object, new Date());
            }
        });

        registry.register(VersionLocking.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class)
                    throw new ValidationException(
                            "The @VersionLocking field must be int type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                ReflectUtil.incrementField(object, field, 1);
            }
        });

        registry.register(ToUpper.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @ToUpper field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String value = (String) field.get(object);
                if (value != null) {
                    field.set(object, value.toUpperCase());
                }
            }
        });

        registry.register(ToLower.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @ToLower field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String value = (String) field.get(object);
                if (value != null) {
                    field.set(object, value.toLowerCase());
                }
            }
        });

        registry.register(Trim.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @Trim field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String value = (String) field.get(object);
                if (value != null) {
                    field.set(object, value.trim());
                }
            }
        });

        registry.register(RemoveChar.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @RemoveChar field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                char charToRemove = ReflectUtil.getAnnoValue(fieldAnnotation, "charToRemove", ' ');
                String value = (String) field.get(object);
                if (value != null) {
                    field.set(object, StringUtils.remove(value, charToRemove));
                }
            }
        });

        registry.register(RemoveAlphaDigits.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @RemoveAlphaDigits field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                boolean removeDigits = ReflectUtil.getAnnoValue(fieldAnnotation, "removeDigits", false);
                String value = (String) field.get(object);
                if (value != null) {
                    if (removeDigits)
                        field.set(object, value.replaceAll("[\\d]", "")); // remove all digits
                    else
                        field.set(object, value.replaceAll("[^\\d]", "")); // remove all alphas (non-digits)
                }
            }
        });

        registry.register(MaxValue.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class)
                    throw new ValidationException(
                            "The @MaxValue field must be number type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object maxValueObj = ReflectUtil.getAnnoValue(fieldAnnotation, "value", (Object) null);
                double maxValue = ((Double) ConvertUtils.convert(maxValueObj, Double.class)).doubleValue();
                Object valueObj = field.get(object);
                double value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                value = (value > maxValue ? maxValue : value);
                field.set(object, ConvertUtils.convert(value, field.getType()));
            }
        });

        registry.register(MinValue.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class)
                    throw new ValidationException(
                            "The @MinValue field must be number type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object minValueObj = ReflectUtil.getAnnoValue(fieldAnnotation, "value", (Object) null);
                double minValue = ((Double) ConvertUtils.convert(minValueObj, Double.class)).doubleValue();
                Object valueObj = field.get(object);
                double value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                value = (value < minValue ? minValue : value);
                field.set(object, ConvertUtils.convert(value, field.getType()));
            }
        });

        registry.register(AbsValue.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class)
                    throw new ValidationException(
                            "The @AbsValue field must be number type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object valueObj = field.get(object);
                double value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                value = Math.abs(value);
                field.set(object, ConvertUtils.convert(value, field.getType()));
            }
        });

        registry.register(CeilValue.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class)
                    throw new ValidationException(
                            "The @CeilValue field must be number type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object valueObj = field.get(object);
                double value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                value = Math.ceil(value);
                field.set(object, ConvertUtils.convert(value, field.getType()));
            }
        });

        registry.register(FloorValue.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class)
                    throw new ValidationException(
                            "The @FloorValue field must be number type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object valueObj = field.get(object);
                double value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                value = Math.floor(value);
                field.set(object, ConvertUtils.convert(value, field.getType()));
            }
        });

    }

    ////////////////////////////////////////////////////////////////////////////
    // Stage 2 data handlers
    ////////////////////////////////////////////////////////////////////////////

    private static void setupBuiltinPreStore2Handlers(AnnotationRegistry registry) {

        registry.register(DefaultComposite.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @DefaultComposite field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                fillDefaultComposite(field, object, allFieldMap);
            }
        });

        registry.register(FormatMsg.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @FormatMsg field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                FormatMsg formatMsg = (FormatMsg) fieldAnnotation;
                if (formatMsg.onSave()) {
                    fillFormatMsg(formatMsg, field, object, allFieldMap);
                }
            }
        });

    }

    ////////////////////////////////////////////////////////////////////////////
    // Validation handlers
    ////////////////////////////////////////////////////////////////////////////

    private static void setupBuiltinValidationHandlers(AnnotationRegistry registry) {

        registry.register(Required.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                if (field.get(object) == null)
                    throw new ValidationException("@Required field cannot be null.  Field: " + field.getName());
            }
        });

        registry.register(MaxSize.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class
                        && field.getType() != String.class)
                    throw new ValidationException(
                            "The @MaxSize field must be number type or String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object annValueObj = ReflectUtil.getAnnoValue(fieldAnnotation, "value", (Object) null);
                double annValue = ((Double) ConvertUtils.convert(annValueObj, Double.class)).doubleValue();
                if (annValue != 0) {
                    Object valueObj = field.get(object);
                    double value;
                    if (valueObj instanceof String)
                        value = ((String) valueObj).length();
                    else
                        value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                    if (value > annValue)
                        throw new ValidationException("Field value " + valueObj + " exceeds MaxSize " + annValueObj
                                + ".  Field: " + field.getName());
                }
            }
        });

        registry.register(MinSize.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != Integer.class && field.getType() != int.class
                        && field.getType() != Long.class && field.getType() != long.class
                        && field.getType() != Short.class && field.getType() != short.class
                        && field.getType() != Float.class && field.getType() != float.class
                        && field.getType() != Double.class && field.getType() != double.class
                        && field.getType() != String.class)
                    throw new ValidationException(
                            "The @MinSize field must be number type or String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                Object annValueObj = ReflectUtil.getAnnoValue(fieldAnnotation, "value", (Object) null);
                double annValue = ((Double) ConvertUtils.convert(annValueObj, Double.class)).doubleValue();
                if (annValue != 0) {
                    Object valueObj = field.get(object);
                    double value;
                    if (valueObj instanceof String)
                        value = ((String) valueObj).length();
                    else
                        value = ((Double) ConvertUtils.convert(valueObj, Double.class)).doubleValue();
                    if (value < annValue)
                        throw new ValidationException("Field value " + valueObj + " is less than MinSize "
                                + annValueObj + ".  Field: " + field.getName());
                }
            }
        });

        registry.register(StartsWith.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @StartsWith field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String annValue = ReflectUtil.getAnnoValue(fieldAnnotation, "value", "");
                String value = (String) field.get(object);
                if (value != null && !value.startsWith(annValue))
                    throw new ValidationException("Field value " + value + " does not start with " + annValue
                            + ".  Field: " + field.getName());
            }
        });

        registry.register(EndsWith.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @EndsWith field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String annValue = ReflectUtil.getAnnoValue(fieldAnnotation, "value", "");
                String value = (String) field.get(object);
                if (value != null && !value.endsWith(annValue))
                    throw new ValidationException("Field value " + value + " does not end with " + annValue
                            + ".  Field: " + field.getName());
            }
        });

        registry.register(Contains.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @Contains field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String annValue = ReflectUtil.getAnnoValue(fieldAnnotation, "value", "");
                String value = (String) field.get(object);
                if (value != null && !value.contains(annValue))
                    throw new ValidationException("Field value " + value + " does not contain " + annValue
                            + ".  Field: " + field.getName());
            }
        });

        registry.register(NotContains.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @NotContains field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String annValue = ReflectUtil.getAnnoValue(fieldAnnotation, "value", "");
                String value = (String) field.get(object);
                if (value != null && value.contains(annValue))
                    throw new ValidationException(
                            "Field value " + value + " contains " + annValue + ".  Field: " + field.getName());
            }
        });

        registry.register(RegexMatch.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @RegexMatch field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String annValue = ReflectUtil.getAnnoValue(fieldAnnotation, "value", "");
                String value = (String) field.get(object);
                if (value != null && !Pattern.matches(annValue, value))
                    throw new ValidationException("Field value " + value + " does not match the regex " + annValue
                            + ".  Field: " + field.getName());
            }
        });

        registry.register(EmailMatch.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @EmailMatch field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String value = (String) field.get(object);
                if (value != null) {
                    if (!sEmailPattern.matcher(value.toUpperCase()).matches())
                        throw new ValidationException(
                                "Field value " + value + " is not an email.  Field: " + field.getName());
                }
            }
        });

        registry.register(MaskMatch.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @MaskMatch field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                char digitMask = ReflectUtil.getAnnoValue(fieldAnnotation, "digitMask", '#');
                char letterMask = ReflectUtil.getAnnoValue(fieldAnnotation, "letterMask", '@');
                char anyMask = ReflectUtil.getAnnoValue(fieldAnnotation, "anyMask", '*');
                String pattern = ReflectUtil.getAnnoValue(fieldAnnotation, "pattern", "");
                String value = (String) field.get(object);
                if (value != null) {
                    MaskMatcher matcher = new MaskMatcher(pattern, digitMask, letterMask, anyMask);
                    if (!matcher.matches(value))
                        throw new ValidationException("Field value " + value + " does not match the mask pattern "
                                + pattern + ".  Field: " + field.getName());
                }
            }
        });

        registry.register(OneOf.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @OneOf field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                String[] annValue = (String[]) ReflectUtil.getAnnoValue(fieldAnnotation, "choices", new String[0]);
                String value = (String) field.get(object);
                if (value != null) {
                    for (String choice : annValue) {
                        if (value.equals(choice))
                            return;
                    }
                    throw new ValidationException(
                            "Field value " + value + " is not one of the choices.  Field: " + field.getName());
                }
            }
        });

    }

    ////////////////////////////////////////////////////////////////////////////
    // Stage 2 data handlers
    ////////////////////////////////////////////////////////////////////////////

    private static void setupBuiltinPostLoadHandlers(AnnotationRegistry registry) {

        registry.register(FormatMsg.class, new AnnotationFieldHandler() {
            public void checkModel(Annotation fieldAnnotation, Field field, Map<String, Field> allFieldMap)
                    throws ValidationException {
                if (field.getType() != String.class)
                    throw new ValidationException(
                            "The @FormatMsg field must be String type.  Field: " + field.getName());
            }

            public void handle(Annotation fieldAnnotation, Object object, Field field,
                    Map<String, Field> allFieldMap) throws Exception {
                FormatMsg formatMsg = (FormatMsg) fieldAnnotation;
                if (formatMsg.onLoad()) {
                    fillFormatMsg(formatMsg, field, object, allFieldMap);
                }
            }
        });

    }

    private static void fillDefaultComposite(Field field, Object dataObj, Map<String, Field> allFieldMap)
            throws Exception {
        String[] fromFields = ReflectUtil.getAnnotationValue(field, DefaultComposite.class, "fromFields",
                String[].class, new String[0]);
        int[] substrLen = ReflectUtil.getAnnotationValue(field, DefaultComposite.class, "substrLen", int[].class,
                new int[0]);
        String separator = ReflectUtil.getAnnotationValue(field, DefaultComposite.class, "separator", "-");
        StringBuilder sb = new StringBuilder();

        for (int i = 0; i < fromFields.length; i++) {
            Field subpartField = allFieldMap.get(fromFields[i]);
            if (subpartField == null)
                throw new IllegalArgumentException(
                        fromFields[i] + " specified in the fromFields parameter of the @DefaultComposite field "
                                + field.getName() + " doesn't exist.");
            Object subpartValue = subpartField.get(dataObj);
            String subpartStr = subpartValue == null ? "" : subpartValue.toString();

            subpartStr = getSubpartMax(subpartStr, i, substrLen);

            if (subpartStr.length() > 0) {
                if (sb.length() > 0)
                    sb.append(separator);
                sb.append(subpartStr);
            }
        }

        field.set(dataObj, sb.toString());
    }

    private static String getSubpartMax(String fieldStr, int fieldPos, int[] substrLen) {
        if (substrLen == null || fieldPos >= substrLen.length || substrLen[fieldPos] == 0)
            return fieldStr;
        int len = substrLen[fieldPos] > fieldStr.length() ? fieldStr.length() : substrLen[fieldPos];
        return fieldStr.substring(0, len);
    }

    private static void fillFormatMsg(FormatMsg formatMsg, Field field, Object dataObj,
            Map<String, Field> allFieldMap) throws Exception {
        Object[] paramObjs = new Object[formatMsg.paramFields().length];
        for (int i = 0; i < formatMsg.paramFields().length; i++) {
            Field paramField = allFieldMap.get(formatMsg.paramFields()[i]);
            if (paramField == null)
                throw new IllegalArgumentException(formatMsg.paramFields()[i]
                        + " specified in the paramFields parameter of the @FormatMsg field " + field.getName()
                        + " doesn't exist.");
            paramObjs[i] = paramField.get(dataObj);
        }
        String msg = MessageFormat.format(formatMsg.format(), paramObjs);
        field.set(dataObj, msg);
    }

}