no.abmu.abmstatistikk.annualstatistic.service.AnswerHelper.java Source code

Java tutorial

Introduction

Here is the source code for no.abmu.abmstatistikk.annualstatistic.service.AnswerHelper.java

Source

/*$Id: AnswerHelper.java 9029 2008-03-13 23:11:51Z jens $*/
/*
 ****************************************************************************
 *                                                                          *
 *                   (c) Copyright 2005 ABM-utvikling                       *
 *                                                                          *
 * This program 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 2 of the License, or (at your   *
 * option) any later version.                                               *
 *                                                                          *
 * This program 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. http://www.gnu.org/licenses/gpl.html    *
 *                                                                          *
 ****************************************************************************
 */

package no.abmu.abmstatistikk.annualstatistic.service;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.Set;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import no.abmu.abmstatistikk.annualstatistic.domain.Answer;
import no.abmu.abmstatistikk.annualstatistic.domain.Field;
import no.abmu.abmstatistikk.annualstatistic.domain.FieldAnnualChangeConstraint;
import no.abmu.abmstatistikk.annualstatistic.domain.FieldGroupConstraint;
import no.abmu.abmstatistikk.annualstatistic.domain.FieldGroupConstraintMember;
import no.abmu.abmstatistikk.annualstatistic.domain.FieldType;
import no.abmu.abmstatistikk.annualstatistic.domain.FieldTypeConstraint;
import no.abmu.abmstatistikk.annualstatistic.domain.Report;
import no.abmu.abmstatistikk.annualstatistic.domain.Schema;

/**
 * AnswerHelper.
 *
 * This class is a helper-class for the auto-generated Answer class.
 * Use this helper to add functionallity and ease the use of Answer objects.
 *
 * @author: Henning Kulander hennikul@linpro.no
 * @author $Author: jens $
 * @version $Rev: 9029 $
 * @date $Date: 2008-03-14 00:11:51 +0100 (Fri, 14 Mar 2008) $
 * @copyright ABM-Utvikling
 * @since 2005-01-07 (Rev 459)
 */
public class AnswerHelper {
    private static final Log logger = (Log) LogFactory.getLog(AnswerHelper.class);

    private Answer answer;

    public AnswerHelper(Answer answer) {
        this.answer = answer;
    }

    /**
     * validate.
     * 
     * @return Errormessage from validation or null if no errors
     */
    public String validate() {
        String error = null;
        error = validateFieldType();
        if (error != null || answer.getValue() == null) {
            return error;
        }
        error = validateFieldGroupConstraint();
        if (error != null) {
            return error;
        }
        return validateFieldAnnualChangeConstraint();
    }

    /**
     * validateFieldType.
     * 
     * @return The errormessage property
     */
    public String validateFieldType() {
        Field field = answer.getField();
        if (field == null) {
            return "Answer not associated with a field in database!";
        }
        FieldType fieldType = field.getFieldType();
        if (fieldType == null) {
            return "Field has no type, can't validate...";
        }
        String dataType = fieldType.getDatatype();
        if (dataType == null) {
            return "Field has undefined dataType, can't validate...";
        }

        if (dataType.equals("TEXT")) {
            return validateText();
        } else if (dataType.equals("POSTNUMBER")) {
            return validatePostNumber();
        } else if (dataType.equals("PHONENUMBER")) {
            return validatePhoneNumber();
        } else if (dataType.equals("YEAR")) {
            return validateYear();
        } else if (dataType.equals("E-MAIL")) {
            return validateEmail();
        } else if (dataType.equals("URL")) {
            return validateURL();
        } else if (dataType.equals("YES_NO_CHOICE")) {
            return validateYesNoChoice();
        } else if (dataType.equals("YES_NO_PARTLY_CHOICE")) {
            return validateYesNoPartyChoice();
        } else if (dataType.equals("CHECKBOX")) {
            return validateCheckBox();
        } else if (dataType.equals("DIGIT_12")) {
            return validateDigit(12);
        } else if (dataType.equals("DIGIT_10")) {
            return validateDigit(10);
        } else if (dataType.equals("DIGIT_9")) {
            return validateDigit(9);
        } else if (dataType.equals("DIGIT_4")) {
            return validateDigit(4);
        } else if (dataType.equals("DIGIT_5")) {
            return validateDigit(5);
        } else if (dataType.equals("DIGIT_3")) {
            return validateDigit(3);
        } else if (dataType.equals("DIGIT_12_0")) {
            return validateDigit(12);
        } else if (dataType.equals("DIGIT_4_0")) {
            return validateDigit(4);
        } else if (dataType.equals("DIGIT_3_0")) {
            return validateDigit(3);
        } else if (dataType.equals("RADIO_GROUP")) {
            return validateRadioGroup();
        } else if (dataType.equals("FLOAT")) {
            return validateFloat(10, 2);
        } else if (dataType.equals("FLOAT_5_2")) {
            return validateFloat(5, 2);
        } else if (dataType.equals("FLOAT_4_2")) {
            return validateFloat(4, 2);
        } else if (dataType.equals("WEEKHOURS")) {
            return validateWeekHours();
        } else if (dataType.equals("YEARDAYS")) {
            return validateYearDays();
        } else if (dataType.equals("YEARDAYSINT")) {
            return validateYearDaysInteger();
        } else if (dataType.equals("YEARHOURS")) {
            return validateYearHours();
        } else if (dataType.equals("SELECTBOX")) {
            return validateSelectBox();
        } else if (dataType.equals("AUTOCALCULATE")) {
            return validateAutoCalculate();
        } else if (dataType.equals("DATE")) {
            return validateDate();
        } else {
            return "Don't know how to validate " + dataType;
        }

        /* If we get this far, everything is OK. Returning no error string */
    }

    /**
     * validateDate.
     * 
     * @return
     */
    private String validateDate() {
        String validateValue = answer.getValue();
        if (validateValue == null || validateValue.equals("")) {
            return null; /* Value not required */
        }

        Pattern floatPattern = Pattern.compile("(\\d\\d?)/(\\d\\d?)");
        Matcher floatMatch = floatPattern.matcher(validateValue);
        if (!floatMatch.matches()) {
            return "schema.error.date_unmatched";
        }
        String dayString = floatMatch.group(1);
        String monthString = floatMatch.group(2);

        int day = 0, month = 0;
        try {
            day = Integer.parseInt(dayString);
            month = Integer.parseInt(monthString);
        } catch (NumberFormatException e) {
            return "schema.error.date_unmatched";
        }

        if (month < 1 || month > 12) {
            return "schema.error.date_bad_month";
        }

        if (day < 1 || day > 31) {
            return "schema.error.date_bad_day";
        }

        return null;
    }

    /**
     * validateSelectBox.
     * 
     * @return
     */
    private String validateSelectBox() {
        String validateValue = answer.getValue();
        if (validateValue == null || validateValue.equals("")) {
            // No choice made
            return null;
        }

        Field field = answer.getField();
        if (field == null) {
            logger.error("No defined field for " + answer);
            return "No field defined here... Should not happen";
        }

        FieldType fieldType = field.getFieldType();
        String fieldDatatype = null;
        if (fieldType != null) {
            fieldDatatype = fieldType.getDatatype();
        }
        if (fieldDatatype == null) {
            logger.error("No fieldtype for field: " + field);
            return "Field has no type, this should never happen.";
        }

        if (!fieldDatatype.equals("SELECTBOX")) {
            logger.error("Trying to validate " + fieldDatatype + " as SELECTBOX");
            return "Trying to validate " + fieldDatatype + " as SELECTBOX";
        }

        Set fieldTypeConstraints = fieldType.getFieldTypeConstraints();
        Iterator fieldTypeConstraintsIterator = fieldTypeConstraints.iterator();
        while (fieldTypeConstraintsIterator.hasNext()) {
            FieldTypeConstraint fieldTypeConstraint = (FieldTypeConstraint) fieldTypeConstraintsIterator.next();
            String parameterValue = fieldTypeConstraint.getValue();
            if (parameterValue != null && parameterValue.equals(validateValue)) {
                return null;
            }
        }

        return "schema.error.selectbox_illegal_value";
    }

    /**
     * validateFloat.
     * 
     * @param beforeComma : Maximum amounts of digit before comma
     * @param afterComma : Maximum number of digits after comma
     * @return
     */
    private String validateFloat(int beforeComma, int afterComma) {
        String validateValue = answer.getValue();
        if (validateValue == null || validateValue.equals("")) {
            // Input is not required
            return null;
        }

        Pattern floatPattern = Pattern
                .compile("(\\d{1," + beforeComma + "})([,.](\\d{" + afterComma + "," + afterComma + "}))?");
        Matcher floatMatch = floatPattern.matcher(validateValue);
        if (!floatMatch.matches()) {
            return "schema.error.float_" + beforeComma + "_" + afterComma + "_unmatched";
        }
        String beforeCommaString = floatMatch.group(1);
        String afterCommaString = floatMatch.group(3);
        if (afterCommaString == null) {
            afterCommaString = "";
            for (int i = 0; i < afterComma; i++) {
                afterCommaString = afterCommaString + "0";
            }
        }
        answer.setValue(beforeCommaString + "," + afterCommaString);

        return validateFloat();
    }

    /**
     * validateYearHours.
     * 
     * @return
     */
    private String validateYearHours() {
        String error = validateDigit(0);
        if (error != null) {
            return error;
        }
        String valueString = answer.getValue();
        if (valueString == null || valueString.equals("")) {
            return null; /* Allow for no input */
        }

        /* Find year of schema and use calendar to find number of days of 
         * that year.
         */
        Report report = answer.getReport();
        if (report == null) {
            logger.error("Answer not bound to a report: " + answer);
            return "No report for answer! This should never happen.";
        }

        Schema schema = report.getSchema();
        if (schema == null) {
            logger.error("Report not bound to a schema: " + report);
            return "No schema for report! This should never happen.";
        }

        SchemaHelper schemaHelper = new SchemaHelper(schema);
        int year = schemaHelper.getYear();

        Calendar calendar = new GregorianCalendar(year, 1, 1);
        int daysOfYear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR);

        int value = Integer.parseInt(valueString);
        if (value > daysOfYear * 24 || value < 0) {
            return "schema.error.invalid_number_of_hours_pr_year";
        }
        return null;
    }

    private String validateYearDaysInteger() {
        String error = validateDigit(10);
        if (error != null) {
            return error;
        }

        return validateYearDaysCommon();
    }

    /**
     * validateYearDays.
     * 
     * @return error if day not between 0 and maximum number of days for 
     * the year given by schema. null if validation passes
     */
    private String validateYearDays() {
        String error = validateFloat(3, 1);
        if (error != null) {
            return error;
        }

        return validateYearDaysCommon();
    }

    private String validateYearDaysCommon() {
        String valueString = answer.getValue();
        if (valueString == null || valueString.equals("")) {
            return null; /* Allow for no input */
        }

        /* Find year of schema and use calendar to find number of days of 
         * that year.
         */
        Report report = answer.getReport();
        if (report == null) {
            logger.error("Answer not bound to a report: " + answer);
            return "No report for answer! This should never happen.";
        }

        Schema schema = report.getSchema();
        if (schema == null) {
            logger.error("Report not bound to a schema: " + report);
            return "No schema for report! This should never happen.";
        }

        SchemaHelper schemaHelper = new SchemaHelper(schema);
        int year = schemaHelper.getYear();

        Calendar calendar = new GregorianCalendar(year, 1, 1);
        int daysOfYyear = calendar.getActualMaximum(Calendar.DAY_OF_YEAR);

        valueString = valueString.replace(',', '.');
        float value = Float.parseFloat(valueString);
        if (value > daysOfYyear || value < 0) {
            return "schema.error.invalid_number_of_days_pr_year";
        }
        return null;
    }

    /**
     * validateWeekHours.
     * 
     * @return error if hours not between 0 and 7*24, null if validation passes
     */
    private String validateWeekHours() {
        String error = validateFloat(3, 1);
        if (error != null) {
            return error;
        }
        String valueString = answer.getValue();
        if (valueString == null || valueString.equals("")) {
            return null; /* Allow for no input */
        }
        valueString = valueString.replace(',', '.');
        float value = Float.parseFloat(valueString);
        if (value > 7 * 24 || value < 0) {
            return "schema.error.invalid_number_of_hours_pr_week";
        }
        return null;
    }

    /**
     * validateAutoCalculate.
     * 
     * @return
     */
    private String validateAutoCalculate() {
        // No need to validate automatic values, they are not editable.
        return null;
    }

    /**
     * validateFloat.
     * 
     * @return
     */
    private String validateFloat() {
        String validateValue = answer.getValue();

        if (validateValue == null || validateValue.equals("")) {
            // Input is not required
            return null;
        }

        validateValue = validateValue.replace(',', '.');
        float value;
        try {
            value = Float.parseFloat(validateValue);
        } catch (NumberFormatException e) {
            return "schema.error.float_invalid";
        }

        logger.debug("Validation of answer with value " + value + " succeded.");
        return null;
    }

    /**
     * validateRadioGroup.
     * 
     * @return
     */
    private String validateRadioGroup() {
        // TODO Auto-generated method stub
        return "Validation of Radio Groups not implemented yet";
    }

    /**
     * validateDigit.
     * 
     * @param max_digits : Maximum number of digits
     * @return error string or null if validation succedes
     */
    private String validateDigit(int maxDigits) {
        String validateValue = answer.getValue();

        if (validateValue == null || validateValue.equals("")) {
            // It is not mandatory to enter a value, return success
            return null;
        }
        // Check if length is longer than allowed:
        if (maxDigits > 0 && validateValue.length() > maxDigits) {
            return "schema.error.digit_too_long_" + maxDigits;
        }

        // Check if input is a valid digit:
        long value = 0;
        try {
            value = Long.parseLong(validateValue);
        } catch (NumberFormatException e) {
            return "schema.error.digit_invalid";
        }

        logger.debug("Validation of answer with value " + value + " succeded.");
        return null;
    }

    /**
     * validateCheckBox.
     * 
     * @return
     */
    private String validateCheckBox() {
        String validateValue = answer.getValue();

        if (validateValue == null || validateValue.equals("Unchecked") // Unchecked 
                || validateValue.equals("true")) { // Checked
            return null;
        }
        logger.error("Validation of a checkbox with value " + validateValue + " failed. This should never happen!");
        return "schema.error.bad_checkbox_value";
    }

    /**
     * validateYesNoParty_Choice.
     * 
     * @return
     */
    private String validateYesNoPartyChoice() {
        String validateValue = answer.getValue();

        if (validateValue == null) {
            return "schema.error.choice_required";
        }

        if (validateValue.equals("partly")) {
            return null;
        }

        return validateYesNoChoice();
    }

    /**
     * validateYesNo_Choice.
     * 
     * @return
     */
    private String validateYesNoChoice() {
        String validateValue = answer.getValue();

        if (validateValue == null) {
            return "schema.error.choice_required";
        }

        if (validateValue.equals("yes") || validateValue.equals("true") // Yes 
                || validateValue.equals("no") || validateValue.equals("false")) { // No
            return null;
        }
        logger.error(
                "Validation of a yes/no input failed with value " + validateValue + " this should never happen!");
        return "schema.error.bad_yes_no_value";
    }

    /**
     * validateURL.
     * 
     * @return
     */
    private String validateURL() {
        // TODO Auto-generated method stub
        return "Validation of URLs not implemented yet";
    }

    /**
     * validateEmail.
     * 
     * @return
     */
    private String validateEmail() {
        // TODO Auto-generated method stub
        return "Validation of e-mail addresses not implemented yet";
    }

    /**
     * validateYear.
     * 
     * @return
     */
    private String validateYear() {
        // TODO Auto-generated method stub
        return "Validation of years not implemented yet";
    }

    /**
     * validatePhoneNumber.
     * 
     * @return
     */
    private String validatePhoneNumber() {
        // TODO Auto-generated method stub
        return "Validation of phone-numbers not implemented yet";
    }

    /**
     * validatePostNumber.
     * 
     * @return
     */
    private String validatePostNumber() {
        // TODO Auto-generated method stub
        return "Validation of Post-numbers not implemented yet";
    }

    /**
     * validateText.
     * 
     * @return
     */
    private String validateText() {
        /* Free text input, check length... */
        String stringValue = answer.getValue();

        if (stringValue != null) {
            /* Remove leading spaces */
            stringValue = stringValue.replaceAll("^(\\s|\\n)+", "");
            /* Remove trailing spaces */
            stringValue = stringValue.replaceAll("(\\s|\\n)+$", "");
            answer.setValue(stringValue);
        }

        if (stringValue == null || stringValue.length() < 4096) {
            return null;
        } else {
            return "schema.error.text_too_long";
        }
    }

    public String validateSortedGroupConstraint(FieldGroupConstraint groupConstraint,
            FieldGroupConstraintMember groupMembership, Field field, ReportHelper reportHelper, Report report) {
        logger.debug("Checking sorted constraint for " + field.getName());
        String stringValue = answer.getValue();
        logger.debug("Field has value " + stringValue);
        FieldGroupConstraintHelper constraintHelper = new FieldGroupConstraintHelper(groupConstraint);
        int orderInGroup = groupMembership.getComp_id().getOrderingroupconstraint().intValue();
        int lastMemberNumber = constraintHelper.lastMemberNumber();
        if (orderInGroup > lastMemberNumber) {
            logger.error("Group member out of bounds! " + "Should not happen.");
            return "Group member out of bounds! Should not happen!";
        } else if (orderInGroup == lastMemberNumber) {
            /* If any of the other fields has a value, this field must
             * also have a value. Check for other values if this field
             * is empty */
            if (stringValue != null && !stringValue.equals("")) {
                return null; /* Value is not empty, no check needed */
            }
            String previousStringValue = null;
            int previousOrderInGroup = orderInGroup - 1;
            while (previousOrderInGroup >= 0 && (previousStringValue == null || previousStringValue.equals(""))) {
                String previousFieldName = constraintHelper.getFieldNameByOrderInGroup(previousOrderInGroup);
                if (previousFieldName == null) {
                    logger.error("No field with " + "order " + previousOrderInGroup + " in FieldGroupConstraint"
                            + groupConstraint);
                    return "Previous field in group constraint not " + "found. This should never happen.";
                }
                Answer previousAnswer = reportHelper.getAnswerByFieldName(previousFieldName);
                if (previousAnswer == null) {
                    logger.error(
                            "Report does not containt answer for " + "field " + previousFieldName + ". :" + report);
                    return "No answer object for next member in group " + "this should never happen";
                }
                previousStringValue = previousAnswer.getValue();
                previousOrderInGroup--;
            }
            if (previousStringValue != null && !previousStringValue.equals("")) {
                /* Last required since some other field has value */
                return "schema.error.sorted_field_required";
            } else {
                return null;
            }
        } else {
            /* If the field has a value, check if all fields bigger also
             * has a value, and that the value is higher */
            if (stringValue == null || stringValue.equals("")) {
                return null; /* Don't compare empty values */
            }

            int nextOrderInGroup = orderInGroup;
            String nextStringValue;

            do {
                nextOrderInGroup++;
                String nextFieldName = constraintHelper.getFieldNameByOrderInGroup(nextOrderInGroup);
                if (nextFieldName == null) {
                    logger.error("No field with order " + nextOrderInGroup + " in FieldGroupConstraint"
                            + groupConstraint);
                    return "Next field in group constraint not found. " + "This should never happen.";
                }
                Answer nextAnswer = reportHelper.getAnswerByFieldName(nextFieldName);
                if (nextAnswer == null) {
                    logger.error(
                            "Report does not containt answer for " + "field " + nextFieldName + ". :" + report);
                    return "No answer object for next member in group " + "this should never happen";
                }
                nextStringValue = nextAnswer.getValue();
            } while (nextOrderInGroup < lastMemberNumber
                    && (nextStringValue == null || nextStringValue.equals("")));

            if (nextStringValue == null || nextStringValue.equals("")) {
                /* All fields above were empty */
                return "schema.error.sorted_missing_field_to_compare_with";
            }
            logger.debug("Compare with " + nextStringValue);

            float value;
            try {
                stringValue = stringValue.replace(',', '.');
                value = Float.parseFloat(stringValue);
            } catch (NumberFormatException e) {
                /* Should have been caught in digit validation, but 
                 * we have the extra test here just in case */
                return "schema.error.float_invalid";
            }

            float nextValue;
            try {
                nextStringValue = nextStringValue.replace(',', '.');
                nextValue = Float.parseFloat(nextStringValue);
            } catch (NumberFormatException e) {
                /* Other field fails validation. The validation for this
                 * field shoul also reflect this. */
                return "schema.error.sorted_unable_to_compare";
            }

            if (value <= nextValue) {
                return null;
            } else {

                return "schema.error.sorted_too_big";
            }
        }

    }

    public String validateExcludesGroupConstraint(FieldGroupConstraint groupConstraint,
            FieldGroupConstraintMember groupMembership, Field field, ReportHelper reportHelper, Report report) {
        /* Check if this answer is the first in the group */
        FieldGroupConstraintHelper constraintHelper = new FieldGroupConstraintHelper(groupConstraint);
        int orderInGroup = groupMembership.getComp_id().getOrderingroupconstraint().intValue();
        /* If it is, allways return null */
        if (orderInGroup == 0) {
            return null;
        }
        /* If not return true if empty or null */
        String stringValue = answer.getValue();
        if (stringValue == null || stringValue.equals("")) {
            return null;
        }

        /* If not empty or null find the first answer */
        String firstFieldName = constraintHelper.getFieldNameByOrderInGroup(0);
        Answer firstAnswer = reportHelper.getAnswerByFieldName(firstFieldName);
        String firstAnswerValue = firstAnswer.getValue();

        /* Compare first answer to FieldGroupConstraint.parameter */
        /* If not equal, return true */
        if (firstAnswerValue == null || !firstAnswerValue.equals(groupConstraint.getParameter())) {
            return null;
        }

        /* Check if this is a Yes/No or Yes/No/Partly field */
        FieldType fieldType = field.getFieldType();
        if (fieldType == null) {
            logger.error("Field " + field.getName() + " does not containt " + "a FieldType:" + report);
            return "No FieldType for field, this should never happen";
        }
        String dataType = fieldType.getDatatype();
        if (dataType == null) {
            logger.error("Field " + field.getName() + " does not containt " + "a DataType:" + report);
            return "No DataType for field, this should never happen";
        }
        if (dataType.equals("YES_NO_CHOICE") || dataType.equals("YES_NO_PARTLY_CHOICE")) {
            /* If it is, return true if choice is no */
            if (stringValue.equals("no") || stringValue.equals("false")) {
                return null;
            }
        }

        return "schema.error." + report.getSchema().getShortname() + ".excluded_by_field_" + firstFieldName;

    }

    public String validateMinMaxGroupConstraint(FieldGroupConstraint groupConstraint,
            FieldGroupConstraintMember groupMembership, Field field, ReportHelper reportHelper, Report report) {
        /* Find the parameters for this MINMAX group */
        logger.debug("Checking MINMAX requirements for field " + answer.getField().getName());
        String parameter = groupConstraint.getParameter();
        Pattern pattern = Pattern.compile("\\((\\d+),(\\d+),(\\w+)\\)");
        Matcher matcher = pattern.matcher(parameter);
        if (!matcher.matches()) {
            logger.error("Bad MINMAX parameter " + parameter);
            return "Bad MINMAX parameter for this field. " + "This should never happen!";
        }
        String maxString = matcher.group(1);
        int max = Integer.parseInt(maxString);
        String minString = matcher.group(2);
        int min = Integer.parseInt(minString);
        String value = matcher.group(3);
        String stringValue = answer.getValue();

        /* Count how many has this value. */
        int count = 0;
        Set members = groupConstraint.getFieldGroupConstraintMembers();
        Iterator memberIterator = members.iterator();
        while (memberIterator.hasNext()) {
            FieldGroupConstraintMember member = (FieldGroupConstraintMember) memberIterator.next();
            Field memberField = member.getField();
            String memberFieldName = memberField.getName();
            Answer memberAnswer = reportHelper.getAnswerByFieldName(memberFieldName);
            String memberValue = memberAnswer.getValue();
            if (memberValue != null && memberValue.equals(value)) {
                count++;
            }
        }

        /* Check if this fields value is the value of the group, */
        if (stringValue == null || !stringValue.equals(value)) {
            /* If it is not, return error if count less than min */
            if (count < min) {
                return "schema.error.maxmin_" + value + "_lower_than_" + minString;
            } else {
                return null;
            }
        } else {
            /* If count is more than max, return error */
            if (count > max) {
                return "schema.error.maxmin_" + value + "_higher_than_" + maxString;
            } else {
                return null;
            }
        }

    }

    /**
     * validateFieldGroupConstraint.
     * 
     * @return null if no errors, error if sorting is wrong.
     */
    public String validateFieldGroupConstraint() {
        Field field = answer.getField();
        if (field == null) {
            logger.error("No defined field for " + answer);
            return "No field defined here... Should not happen";
        }
        Report report = answer.getReport();
        if (report == null) {
            logger.error("Answer not bound to a report: " + answer);
            return "No report for answer! This should never happen.";
        }
        ReportHelper reportHelper = new ReportHelper(report);
        Set fieldGroupMemberships = field.getFieldGroupConstraintMembers();
        if (fieldGroupMemberships.isEmpty()) {
            return null; /* Not in any FieldGroupConstraints, nothing to check*/
        }
        /* Walk through all fieldGroupConstraintMemberships and validate 
         * according to type. */
        Iterator fieldGroupMembershipsIterator = fieldGroupMemberships.iterator();
        String error;
        while (fieldGroupMembershipsIterator.hasNext()) {
            FieldGroupConstraintMember groupMembership = (FieldGroupConstraintMember) fieldGroupMembershipsIterator
                    .next();
            FieldGroupConstraint groupConstraint = groupMembership.getFieldGroupConstraint();
            String groupConstraintType = groupConstraint.getType();
            /* Validate SORTED Group constraints
             * Member with orderInGroup == 0 should have highest value.
             * Other members are in ascending order.
             * If a field has a value, all values after (ie. higher 
             * orderInGroup) should also have values. Other members may be 
             * absent.
             */
            if (groupConstraintType.equals("SORTED")) {
                error = validateSortedGroupConstraint(groupConstraint, groupMembership, field, reportHelper,
                        report);
            } else if (groupConstraintType.equals("EXCLUDES")) {
                error = validateExcludesGroupConstraint(groupConstraint, groupMembership, field, reportHelper,
                        report);
            } else if (groupConstraintType.equals("MINMAX")) {
                error = validateMinMaxGroupConstraint(groupConstraint, groupMembership, field, reportHelper,
                        report);
                /* AUTOCALCULATE-groups should not be editable and is not validated 
                 */
            } else if (groupConstraintType.equals("CALCULATION")) {
                error = null;
            } else {
                return "validation not implemented for GroupConstraintType: " + groupConstraintType;
            }
            if (error != null) {
                return error;
            }
        }
        return null;
    }

    /**
     * validateFieldAnnualChangeConstraint.
     * 
     * @return null if no errors, error if difference between this years 
     *         value and last years value is to big.
     */
    public String validateFieldAnnualChangeConstraint() {

        Field field = answer.getField();
        if (field == null) {
            logger.error("No defined field for " + answer);
            return "No field defined here... Should not happen";
        }
        FieldAnnualChangeConstraint annualChangeConstraint = field.getFieldAnnualChangeConstraintByFieldid();
        if (annualChangeConstraint == null) {
            if (logger.isDebugEnabled()) {
                logger.debug("No annual constraing on field " + field.getName());
            }
            return null;
        } else {
            if (logger.isDebugEnabled()) {
                logger.info("Start schecking annualConstraing for field " + field.getName());
            }
            FieldType fieldType = field.getFieldType();
            String fieldDatatype = null;
            if (fieldType != null) {
                fieldDatatype = fieldType.getDatatype();
            }
            if (fieldDatatype == null) {
                logger.error("No fieldtype for field: " + field);
                return "Field has no type, this should never happen.";
            }
            if (!fieldDatatype.startsWith("DIGIT") && !fieldDatatype.startsWith("FLOAT")
                    && !fieldDatatype.startsWith("WEEKHOURS")) {
                logger.error("Cannot validate annual change constraint for " + "datatype " + fieldDatatype);
                return "Unhandled dataType: " + fieldDatatype;
            }
            Field lastYearField = annualChangeConstraint.getFieldByLastyearfieldid();
            if (lastYearField == null) {
                logger.error("No last year field defined in Change Constraint" + annualChangeConstraint);
                return "No last year field defined in annual change constraint" + ". This should never happen.";
            }
            String lastYearFieldName = lastYearField.getName();
            Report report = answer.getReport();
            if (report == null) {
                logger.error("Answer not bound to a report: " + answer);
                return "No report for answer! This should never happen.";
            }
            Report lastYearReport = report.getReport();
            if (lastYearReport == null) {
                if (logger.isDebugEnabled()) {
                    logger.debug("No report to check values against");
                }
                return null; /* No report to check values against */
            }
            ReportHelper lastYearReportHelper = new ReportHelper(lastYearReport);
            Answer lastYearAnswer = lastYearReportHelper.getAnswerByFieldName(lastYearFieldName);

            String lastYearValueString = null;
            if (lastYearAnswer != null) {
                lastYearValueString = lastYearAnswer.getValue();
            }
            if (lastYearValueString == null || lastYearValueString.equals("")) {
                /* If last year value does not exist, no error has occured */
                return null;
            }
            String thisYearValueString = answer.getValue();
            if (thisYearValueString == null || thisYearValueString.equals("")) {
                /* If this years value is not present, don't perform any tests*/
                return null;
            }

            float thisYearValue;
            try {
                thisYearValueString = thisYearValueString.replace(',', '.');
                thisYearValue = Float.valueOf(thisYearValueString).floatValue();
            } catch (NumberFormatException e) {
                return "schema.error.float_invalid";
            }

            float lastYearValue;
            try {
                lastYearValueString = lastYearValueString.replace(',', '.');
                lastYearValue = Float.valueOf(lastYearValueString).floatValue();
            } catch (NumberFormatException e) {
                logger.error("Bad value in last years report: " + lastYearReport);
                logger.error("Value was: " + lastYearValueString);
                return "Bad value in last years report: " + lastYearValueString;
            }

            float change = java.lang.Math.abs(lastYearValue - thisYearValue);

            int minChange = annualChangeConstraint.getLowchangelimit();
            int highChange = annualChangeConstraint.getHighchangelimit();
            int percent = annualChangeConstraint.getPercentchangelimit();

            if (logger.isDebugEnabled()) {
                logger.debug("Last year value '" + lastYearValue + "' this year value '" + thisYearValue
                        + "' change '" + change + "' min change '" + minChange + "' max change '" + highChange
                        + "' percent '" + percent + "%");
            }

            /* Now we are ready to do the tests... */
            String comment = answer.getComment();
            if (comment != null && !comment.equals("")) {
                if (logger.isDebugEnabled()) {
                    logger.debug("User has commented the change");
                }
                return null; /* User has commented the change */
            }
            if (change > highChange) {
                /* If change is more than maxChange, warn user */
                //TODO: Find a way to give user a warning...
                if (logger.isDebugEnabled()) {
                    logger.debug(
                            "Change from " + lastYearValue + " to " + thisYearValue + " more than " + highChange);
                }
                return "schema.error.change_too_big";
            } else if (change > minChange && change / lastYearValue > (float) percent / 100) {
                /* If change is more than minChange and less than highChange 
                 * and the relative change is more than percent, issue a warning
                 */
                //TODO: Find a way to give user a warning...
                if (logger.isDebugEnabled()) {
                    logger.debug("Change from " + lastYearValue + " to " + thisYearValue + " more than " + percent
                            + "%. (" + (change / lastYearValue) * 100 + "%).");
                }
                return "schema.error.change_too_big_percent";
            }
            /* If change is less than minChange, no error has occured */
            /* If change is more than minChange, less than highChange and
             * relative change is less than percent, no error has occured
             */
            return null;

        }
    }

    public void autoCalculate() {
        float sum = 0;
        Field field = answer.getField();
        if (field == null) {
            logger.error("No field for answer: " + answer);
        }
        if (logger.isDebugEnabled()) {
            logger.debug("Calculating sum for field " + field.getName());
        }
        FieldType fieldType = field.getFieldType();
        if (!fieldType.getDatatype().equals("AUTOCALCULATE")) {
            logger.error("Trying to autocalculate an answer of type " + fieldType.getDatatype() + " - " + answer);
            return;
        }

        String oldValue = answer.getValue();
        if (oldValue != null && (!oldValue.equals("") || oldValue.equals("CALCULATING"))) {
            /* This sum has allready been calculated */
            return; /* Early cutoff in recursive sums */
        }
        answer.setValue("CALCULATING");

        Report report = answer.getReport();
        if (report == null) {
            logger.error("Answer not bound to a report: " + answer);
        }
        ReportHelper reportHelper = new ReportHelper(report);
        Set fieldGroupMemberships = field.getFieldGroupConstraintMembers();
        if (fieldGroupMemberships.isEmpty()) {
            logger.error("Trying to autocalculate answer for field " + field.getName() + " with no Fieldgroup "
                    + "membership " + answer);
        }

        Iterator fieldGroupMembershipsIterator = fieldGroupMemberships.iterator();
        while (fieldGroupMembershipsIterator.hasNext()) {
            FieldGroupConstraintMember groupMembership = (FieldGroupConstraintMember) fieldGroupMembershipsIterator
                    .next();
            FieldGroupConstraint groupConstraint = groupMembership.getFieldGroupConstraint();
            String groupConstraintType = groupConstraint.getType();
            if (groupConstraintType.equals("CALCULATION")) {
                FieldGroupConstraintHelper constraintHelper = new FieldGroupConstraintHelper(groupConstraint);
                int orderInGroup = groupMembership.getComp_id().getOrderingroupconstraint().intValue();
                if (orderInGroup == 0) {
                    int lastMemberNumber = constraintHelper.lastMemberNumber();
                    String sign = groupConstraint.getParameter();
                    if (sign == null
                            || (!sign.equals("+") && !sign.equals("-") && !sign.equals("*") && !sign.equals("/"))) {
                        logger.error("Don't know how to calculate with '" + sign + "'.");
                        return;
                    }
                    for (int i = 1; i <= lastMemberNumber; i++) {
                        String sumMemberFieldName = constraintHelper.getFieldNameByOrderInGroup(i);
                        Answer sumMember = reportHelper.getAnswerByFieldName(sumMemberFieldName);
                        if (sumMember == null) {
                            logger.error("Report for schema " + report.getSchema().getShortname()
                                    + " has no field with name " + sumMemberFieldName + ".");
                            logger.error("Cannot calculate sum for field " + field.getName());
                            return;
                        }

                        /* If field is autocalculated, calculate it first */
                        Field sumMemberField = sumMember.getField();
                        if (sumMemberField == null) {
                            logger.error("Member in sum has no field: " + sumMember);
                            return;
                        }
                        FieldType sumMemberFieldType = sumMemberField.getFieldType();
                        if (sumMemberFieldType == null) {
                            logger.error("Member in sum has noe fieldtype: " + answer);
                            return;
                        }
                        if (sumMemberFieldType.getDatatype().equals("AUTOCALCULATE")) {
                            AnswerHelper sumMemberHelper = new AnswerHelper(sumMember);
                            String calculateValue = sumMember.getValue();
                            if (calculateValue != null && calculateValue.equals("CALCULATING")) {
                                logger.error("Calculation loop detected when " + "calculating sum for field "
                                        + field.getName() + " in report for schema "
                                        + report.getSchema().getShortname());
                                return;
                            }
                            sumMemberHelper.autoCalculate();
                        }

                        float sumMemberValue = 0;
                        String value = sumMember.getValue();
                        if (value == null || value.equals("")) {
                            continue;
                        }
                        try {
                            sumMemberValue = Float.parseFloat(value);
                        } catch (NumberFormatException e) {
                            /* Badly formated number. Should not happen, return
                             * with answer.value == null.
                             */
                            answer.setValue("ERROR");
                            return;
                        }
                        if (i == 1) { /* Start with sum equals to first member*/
                            sum = sumMemberValue;
                        } else if (sign.equals("+")) {
                            sum += sumMemberValue;
                        } else if (sign.equals("-")) {
                            sum -= sumMemberValue;
                        } else if (sign.equals("*")) {
                            sum *= sumMemberValue;
                        } else if (sign.equals("/")) {
                            sum /= sumMemberValue;
                        } else {
                            logger.error("Bad calculation! " + "Should never reach this point");
                            return;
                        }
                    }

                    if (logger.isDebugEnabled()) {
                        logger.debug("Calculated sum for field " + answer.getField().getName() + " with value "
                                + Float.toString(sum));
                    }

                    answer.setValue(Float.toString(sum));
                    return;
                }
            }
        }
        logger.error("Trying to autocalculate answer with no CALCULATION " + "fieldgroups where orderInGroup is 0: "
                + answer);
    }

}