org.springmodules.validation.valang.javascript.ValangJavaScriptTranslator.java Source code

Java tutorial

Introduction

Here is the source code for org.springmodules.validation.valang.javascript.ValangJavaScriptTranslator.java

Source

/*
 * Copyright 2002-2005 the original author or authors.
 *
 * 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 org.springmodules.validation.valang.javascript;

import java.io.IOException;
import java.io.Writer;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

import org.apache.commons.collections.Predicate;
import org.apache.commons.collections.functors.AndPredicate;
import org.apache.commons.collections.functors.NotPredicate;
import org.apache.commons.collections.functors.OrPredicate;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.core.ReflectiveVisitorHelper;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springmodules.validation.valang.functions.AbstractFunction;
import org.springmodules.validation.valang.functions.AbstractMathFunction;
import org.springmodules.validation.valang.functions.AddFunction;
import org.springmodules.validation.valang.functions.BeanPropertyFunction;
import org.springmodules.validation.valang.functions.DateLiteralFunction;
import org.springmodules.validation.valang.functions.DivideFunction;
import org.springmodules.validation.valang.functions.Function;
import org.springmodules.validation.valang.functions.LengthOfFunction;
import org.springmodules.validation.valang.functions.LiteralFunction;
import org.springmodules.validation.valang.functions.LowerCaseFunction;
import org.springmodules.validation.valang.functions.MapEntryFunction;
import org.springmodules.validation.valang.functions.ModuloFunction;
import org.springmodules.validation.valang.functions.MultiplyFunction;
import org.springmodules.validation.valang.functions.NotFunction;
import org.springmodules.validation.valang.functions.SubtractFunction;
import org.springmodules.validation.valang.functions.TargetBeanFunction;
import org.springmodules.validation.valang.functions.UpperCaseFunction;
import org.springmodules.validation.valang.predicates.BasicValidationRule;
import org.springmodules.validation.valang.predicates.GenericTestPredicate;
import org.springmodules.validation.valang.predicates.Operator;
import org.springmodules.validation.valang.predicates.Operator.BetweenOperator;
import org.springmodules.validation.valang.predicates.Operator.EqualsOperator;
import org.springmodules.validation.valang.predicates.Operator.HasLengthOperator;
import org.springmodules.validation.valang.predicates.Operator.HasNoLengthOperator;
import org.springmodules.validation.valang.predicates.Operator.HasNoTextOperator;
import org.springmodules.validation.valang.predicates.Operator.HasTextOperator;
import org.springmodules.validation.valang.predicates.Operator.InOperator;
import org.springmodules.validation.valang.predicates.Operator.IsBlankOperator;
import org.springmodules.validation.valang.predicates.Operator.IsLowerCaseOperator;
import org.springmodules.validation.valang.predicates.Operator.IsNotBlankOperator;
import org.springmodules.validation.valang.predicates.Operator.IsNotLowerCaseOperator;
import org.springmodules.validation.valang.predicates.Operator.IsNotUpperCaseOperator;
import org.springmodules.validation.valang.predicates.Operator.IsNotWordOperator;
import org.springmodules.validation.valang.predicates.Operator.IsUpperCaseOperator;
import org.springmodules.validation.valang.predicates.Operator.IsWordOperator;
import org.springmodules.validation.valang.predicates.Operator.LessThanOperator;
import org.springmodules.validation.valang.predicates.Operator.LessThanOrEqualOperator;
import org.springmodules.validation.valang.predicates.Operator.MoreThanOperator;
import org.springmodules.validation.valang.predicates.Operator.MoreThanOrEqualOperator;
import org.springmodules.validation.valang.predicates.Operator.NotBetweenOperator;
import org.springmodules.validation.valang.predicates.Operator.NotEqualsOperator;
import org.springmodules.validation.valang.predicates.Operator.NotInOperator;
import org.springmodules.validation.valang.predicates.Operator.NotNullOperator;
import org.springmodules.validation.valang.predicates.Operator.NullOperator;

/**
 * Translates a collection of valang validation rules into a JavaScript statement that is capable of validating a HTML
 * form and writes this to a provided <code>Writer</code>. This class is <b>not</b> thread safe so it is recommended
 * that a new instance be created each time a translation is required.
 * <p/>
 * <p>
 * The generated JavaScript code is dependent on the code base found in the file "valang_codebase.js" having already
 * been loaded into the page where the validation will occur.
 * 
 * @author Oliver Hutchison
 */
public class ValangJavaScriptTranslator extends AbstractValangJavaScriptTranslator {

    private static final Log logger = LogFactory.getLog(ValangJavaScriptTranslator.class);

    private static final ReflectiveVisitorHelper reflectiveVisitorHelper = new ReflectiveVisitorHelper();

    public ValangJavaScriptTranslator() {
    }

    /**
     * Translates the provided set of Valang <code>BasicValidationRule</code>s into JavaScript code capable of
     * validating a HTML form and outputs the translated code into the provided writer.
     * 
     * @param writer
     *            the writer to output the JavaScript code into
     * @param name
     *            the name of the command that is being validated
     * @param installSelfWithForm
     *            should the generated JavaScript attempt to install its self with the form on creation
     * @param rules
     *            the collection of <code>BasicValidationRule</code>s to translate
     * @param messageSource
     *            the message source accessor that will be used to resolve validation messages.
     */
    public void writeJavaScriptValangValidator(Writer writer, String name, boolean installSelfWithForm,
            Collection rules, MessageSourceAccessor messageSource) throws IOException {
        try {
            setWriter(writer);
            append("new ValangValidator(");
            appendJsString(name);
            append(',');
            append(Boolean.toString(installSelfWithForm));
            append(',');
            appendArrayValidators(rules, messageSource);
            append(')');
        } finally {
            clearWriter();
        }
    }

    public void writeJavaScriptPredicate(Writer writer, Predicate pred) throws IOException {
        try {
            setWriter(writer);
            appendValidationFunction(pred);

        } finally {
            clearWriter();
        }
    }

    protected void appendArrayValidators(Collection rules, MessageSourceAccessor messageSource) throws IOException {
        append("new Array(");
        for (Iterator i = rules.iterator(); i.hasNext();) {
            appendValidatorRule((BasicValidationRule) i.next(), messageSource);
            if (i.hasNext()) {
                append(',');
            }
        }
        append(')');
    }

    protected void appendValidatorRule(BasicValidationRule rule, MessageSourceAccessor messageSource)
            throws IOException {
        append("new ValangValidator.Rule('");
        append(rule.getField());
        append("','not implemented',");
        appendJsString(getErrorMessage(rule, messageSource));
        append(',');
        appendValidationFunction(rule.getPredicate());
        append(')');
    }

    protected void appendValidationFunction(Predicate p) throws IOException {
        append("function() {return ");
        doVisit(p);
        append('}');
    }

    protected void doVisit(Object value) throws IOException {
        reflectiveVisitorHelper.invokeVisit(this, value);
    }

    void visitNull() throws IOException {
        append("null");
    }

    void visit(Function f) throws IOException {
        if (logger.isWarnEnabled()) {
            logger.warn("Encountered unsupported custom function '" + f.getClass().getName() + "'");
        }
        append("this._throwError('don\\'t know how to handle custom function \\'");
        append(getNameForCustomFunction(f));
        append("\\'')");
    }

    void visit(AbstractFunction f) throws IOException {
        Function[] arguments = f.getArguments();
        append(getNameForCustomFunction(f));
        append('(');
        for (int i = 0; i < arguments.length; i++) {
            doVisit(arguments[i]);
            if (i < arguments.length - 1) {
                append(',');
            }
        }
        append(')');
    }

    protected String getNameForCustomFunction(Function f) {
        return "this." + ClassUtils.getShortName(f.getClass());
    }

    void visit(NotPredicate p) throws IOException {
        Assert.isTrue(p.getPredicates().length == 1);
        append("! (");
        doVisit(p.getPredicates()[0]);
        append(')');
    }

    void visit(AndPredicate p) throws IOException {
        String op = ") && ";
        for (int i = 0; i < p.getPredicates().length; i++) {
            Predicate innerP = p.getPredicates()[i];
            append('(');
            doVisit(innerP);
            if (i < p.getPredicates().length - 1) {
                append(op);
            } else {
                append(')');
            }
        }
    }

    void visit(OrPredicate p) throws IOException {
        String op = ") || ";
        for (int i = 0; i < p.getPredicates().length; i++) {
            Predicate innerP = p.getPredicates()[i];
            append('(');
            doVisit(innerP);
            if (i < p.getPredicates().length - 1) {
                append(op);
            } else {
                append(')');
            }
        }
    }

    void visit(GenericTestPredicate p) throws IOException {
        append(operatorToFunctionName(p.getOperator()));
        append("((");
        doVisit(p.getLeftFunction());
        append("), (");
        doVisit(p.getRightFunction());
        append("))");
    }

    protected String operatorToFunctionName(Operator operator) {
        if (operator instanceof EqualsOperator) {
            return "this.equals";
        } else if (operator instanceof NotEqualsOperator) {
            return "! this.equals";
        } else if (operator instanceof LessThanOperator) {
            return "this.lessThan";
        } else if (operator instanceof LessThanOrEqualOperator) {
            return "this.lessThanOrEquals";
        } else if (operator instanceof MoreThanOperator) {
            return "this.moreThan";
        } else if (operator instanceof MoreThanOrEqualOperator) {
            return "this.moreThanOrEquals";
        } else if (operator instanceof InOperator) {
            return "this.inFunc";
        } else if (operator instanceof NotInOperator) {
            return "! this.inFunc";
        } else if (operator instanceof BetweenOperator) {
            return "this.between";
        } else if (operator instanceof NotBetweenOperator) {
            return "! this.between";
        } else if (operator instanceof NullOperator) {
            return "this.nullFunc";
        } else if (operator instanceof NotNullOperator) {
            return "! this.nullFunc";
        } else if (operator instanceof HasTextOperator) {
            return "this.hasText";
        } else if (operator instanceof HasNoTextOperator) {
            return "! this.hasText";
        } else if (operator instanceof HasLengthOperator) {
            return "this.hasLength";
        } else if (operator instanceof HasNoLengthOperator) {
            return "! this.hasLength";
        } else if (operator instanceof IsBlankOperator) {
            return "this.isBlank";
        } else if (operator instanceof IsNotBlankOperator) {
            return "! this.isBlank";
        } else if (operator instanceof IsWordOperator) {
            return "this.isWord";
        } else if (operator instanceof IsNotWordOperator) {
            return "! this.isWord";
        } else if (operator instanceof IsUpperCaseOperator) {
            return "this.isUpper";
        } else if (operator instanceof IsNotUpperCaseOperator) {
            return "! this.isUpper";
        } else if (operator instanceof IsLowerCaseOperator) {
            return "this.isLower";
        } else if (operator instanceof IsNotLowerCaseOperator) {
            return "! this.isLower";
        } else {
            throw new UnsupportedOperationException(
                    "Unexpected operator type '" + operator.getClass().getName() + "'");
        }
    }

    void visit(TargetBeanFunction f) throws IOException {
        append("this.getTargetBean()");
    }

    void visit(BeanPropertyFunction f) throws IOException {
        append("this.getPropertyValue(");
        appendJsString(f.getField());
        append(')');
    }

    void visit(MapEntryFunction f) throws IOException {
        append("(");
        doVisit(f.getMapFunction());
        append("[");
        doVisit(f.getKeyFunction());
        append("])");
    }

    void visit(LiteralFunction f) throws IOException {
        Object literal = f.getResult(null);
        if (literal instanceof String) {
            appendJsString((String) literal);
        } else if (literal instanceof Number) {
            append(literal.toString());
        } else if (literal instanceof Boolean) {
            append(literal.toString());
        } else if (literal instanceof Function[]) {
            Function[] functions = (Function[]) literal;
            appeandLiteralArray(functions);
        } else if (literal instanceof Collection) {
            appeandLiteralArray((((Collection) literal).toArray()));
        } else {
            throw new UnsupportedOperationException("Unexpected literal type '" + literal.getClass() + "'");
        }
    }

    void appeandLiteralArray(Object[] functions) throws IOException {
        append("new Array(");
        for (int i = 0; i < functions.length; i++) {
            doVisit(functions[i]);
            if (i < functions.length - 1) {
                append(",");
            }
        }
        append(')');
    }

    void visit(DateLiteralFunction f) throws IOException {
        Calendar cal = Calendar.getInstance();
        cal.setTime((Date) f.getResult(null));
        append("new Date(");
        append(cal.get(Calendar.YEAR));
        append(", ");
        append(cal.get(Calendar.MONTH));
        append(", ");
        append(cal.get(Calendar.DATE));
        append(", ");
        append(cal.get(Calendar.HOUR_OF_DAY));
        append(", ");
        append(cal.get(Calendar.MINUTE));
        append(", ");
        append(cal.get(Calendar.SECOND));
        append(", ");
        append(cal.get(Calendar.MILLISECOND));
        append(')');
    }

    void visit(LengthOfFunction f) throws IOException {
        append("this.lengthOf(");
        doVisit(f.getArguments()[0]);
        append(')');
    }

    void visit(NotFunction f) throws IOException {
        append("! ");
        doVisit(f.getArguments()[0]);
    }

    void visit(UpperCaseFunction f) throws IOException {
        append("this.upperCase(");
        doVisit(f.getArguments()[0]);
        append(')');
    }

    void visit(LowerCaseFunction f) throws IOException {
        append("this.lowerCase(");
        doVisit(f.getArguments()[0]);
        append(')');
    }

    void visit(AbstractMathFunction f) throws IOException {
        append(mathToFunctionName(f));
        append("((");
        doVisit(f.getLeftFunction());
        append("),(");
        doVisit(f.getRightFunction());
        append("))");
    }

    protected String mathToFunctionName(AbstractMathFunction f) {
        if (f instanceof AddFunction) {
            return "this.add";
        } else if (f instanceof DivideFunction) {
            return "this.divide";
        } else if (f instanceof ModuloFunction) {
            return "this.modulo";
        } else if (f instanceof MultiplyFunction) {
            return "this.multiply";
        } else if (f instanceof SubtractFunction) {
            return "this.subtract";
        } else {
            throw new UnsupportedOperationException(
                    "Unexpected math function type '" + f.getClass().getName() + "'");
        }
    }
}