Package org.hibernate.validator.messageinterpolation

Source Code of org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator$LocalisedMessage

// $Id: ResourceBundleMessageInterpolator.java 19777 2010-06-21 13:35:31Z hardy.ferentschik $
/*
* JBoss, Home of Professional Open Source
* Copyright 2009, Red Hat, Inc. and/or its affiliates, and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* 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.hibernate.validator.messageinterpolation;

import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.WeakHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.validation.MessageInterpolator;

import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
import org.hibernate.validator.resourceloading.ResourceBundleLocator;

/**
* Resource bundle backed message interpolator.
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
* @author Gunnar Morling
*/
public class ResourceBundleMessageInterpolator implements MessageInterpolator {

  /**
   * The name of the default message bundle.
   */
  public static final String DEFAULT_VALIDATION_MESSAGES = "org.hibernate.validator.ValidationMessages";

  /**
   * The name of the user-provided message bundle as defined in the specification.
   */
  public static final String USER_VALIDATION_MESSAGES = "ValidationMessages";

  /**
   * Regular expression used to do message interpolation.
   */
  private static final Pattern MESSAGE_PARAMETER_PATTERN = Pattern.compile( "(\\{[^\\}]+?\\})" );

  /**
   * The default locale for the current user.
   */
  private final Locale defaultLocale;

  /**
   * Loads user-specified resource bundles.
   */
  private final ResourceBundleLocator userResourceBundleLocator;

  /**
   * Loads built-in resource bundles.
   */
  private final ResourceBundleLocator defaultResourceBundleLocator;

  /**
   * Step 1-3 of message interpolation can be cached. We do this in this map.
   */
  private final Map<LocalisedMessage, String> resolvedMessages = new WeakHashMap<LocalisedMessage, String>();

  /**
   * Flag indicating whether this interpolator should chance some of the interpolation steps.
   */
  private final boolean cacheMessages;

  public ResourceBundleMessageInterpolator() {
    this( null );
  }

  public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator) {
    this( userResourceBundleLocator, true );
  }

  public ResourceBundleMessageInterpolator(ResourceBundleLocator userResourceBundleLocator, boolean cacheMessages) {

    defaultLocale = Locale.getDefault();

    if ( userResourceBundleLocator == null ) {
      this.userResourceBundleLocator = new PlatformResourceBundleLocator( USER_VALIDATION_MESSAGES );
    }
    else {
      this.userResourceBundleLocator = userResourceBundleLocator;
    }

    this.defaultResourceBundleLocator = new PlatformResourceBundleLocator( DEFAULT_VALIDATION_MESSAGES );
    this.cacheMessages = cacheMessages;
  }

  public String interpolate(String message, Context context) {
    // probably no need for caching, but it could be done by parameters since the map
    // is immutable and uniquely built per Validation definition, the comparison has to be based on == and not equals though
    return interpolateMessage( message, context.getConstraintDescriptor().getAttributes(), defaultLocale );
  }

  public String interpolate(String message, Context context, Locale locale) {
    return interpolateMessage( message, context.getConstraintDescriptor().getAttributes(), locale );
  }

  /**
   * Runs the message interpolation according to algorithm specified in JSR 303.
   * <br/>
   * Note:
   * <br/>
   * Look-ups in user bundles is recursive whereas look-ups in default bundle are not!
   *
   * @param message the message to interpolate
   * @param annotationParameters the parameters of the annotation for which to interpolate this message
   * @param locale the {@code Locale} to use for the resource bundle.
   *
   * @return the interpolated message.
   */
  private String interpolateMessage(String message, Map<String, Object> annotationParameters, Locale locale) {
    LocalisedMessage localisedMessage = new LocalisedMessage( message, locale );
    String resolvedMessage = null;

    if ( cacheMessages ) {
      resolvedMessage = resolvedMessages.get( localisedMessage );
    }

    // if the message is not already in the cache we have to run step 1-3 of the message resolution
    if ( resolvedMessage == null ) {
      ResourceBundle userResourceBundle = userResourceBundleLocator
          .getResourceBundle( locale );
      ResourceBundle defaultResourceBundle = defaultResourceBundleLocator
          .getResourceBundle( locale );

      String userBundleResolvedMessage;
      resolvedMessage = message;
      boolean evaluatedDefaultBundleOnce = false;
      do {
        // search the user bundle recursive (step1)
        userBundleResolvedMessage = replaceVariables(
            resolvedMessage, userResourceBundle, locale, true
        );

        // exit condition - we have at least tried to validate against the default bundle and there was no
        // further replacements
        if ( evaluatedDefaultBundleOnce
            && !hasReplacementTakenPlace( userBundleResolvedMessage, resolvedMessage ) ) {
          break;
        }

        // search the default bundle non recursive (step2)
        resolvedMessage = replaceVariables( userBundleResolvedMessage, defaultResourceBundle, locale, false );
        evaluatedDefaultBundleOnce = true;
        if ( cacheMessages ) {
          resolvedMessages.put( localisedMessage, resolvedMessage );
        }
      } while ( true );
    }

    // resolve annotation attributes (step 4)
    resolvedMessage = replaceAnnotationAttributes( resolvedMessage, annotationParameters );

    // last but not least we have to take care of escaped literals
    resolvedMessage = resolvedMessage.replace( "\\{", "{" );
    resolvedMessage = resolvedMessage.replace( "\\}", "}" );
    resolvedMessage = resolvedMessage.replace( "\\\\", "\\" );
    return resolvedMessage;
  }

  private boolean hasReplacementTakenPlace(String origMessage, String newMessage) {
    return !origMessage.equals( newMessage );
  }

  private String replaceVariables(String message, ResourceBundle bundle, Locale locale, boolean recurse) {
    Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher( message );
    StringBuffer sb = new StringBuffer();
    String resolvedParameterValue;
    while ( matcher.find() ) {
      String parameter = matcher.group( 1 );
      resolvedParameterValue = resolveParameter(
          parameter, bundle, locale, recurse
      );

      matcher.appendReplacement( sb, escapeMetaCharacters( resolvedParameterValue ) );
    }
    matcher.appendTail( sb );
    return sb.toString();
  }

  private String replaceAnnotationAttributes(String message, Map<String, Object> annotationParameters) {
    Matcher matcher = MESSAGE_PARAMETER_PATTERN.matcher( message );
    StringBuffer sb = new StringBuffer();
    while ( matcher.find() ) {
      String resolvedParameterValue;
      String parameter = matcher.group( 1 );
      Object variable = annotationParameters.get( removeCurlyBrace( parameter ) );
      if ( variable != null ) {
        resolvedParameterValue = escapeMetaCharacters( variable.toString() );
      }
      else {
        resolvedParameterValue = parameter;
      }
      matcher.appendReplacement( sb, resolvedParameterValue );
    }
    matcher.appendTail( sb );
    return sb.toString();
  }

  private String resolveParameter(String parameterName, ResourceBundle bundle, Locale locale, boolean recurse) {
    String parameterValue;
    try {
      if ( bundle != null ) {
        parameterValue = bundle.getString( removeCurlyBrace( parameterName ) );
        if ( recurse ) {
          parameterValue = replaceVariables( parameterValue, bundle, locale, recurse );
        }
      }
      else {
        parameterValue = parameterName;
      }
    }
    catch ( MissingResourceException e ) {
      // return parameter itself
      parameterValue = parameterName;
    }
    return parameterValue;
  }

  private String removeCurlyBrace(String parameter) {
    return parameter.substring( 1, parameter.length() - 1 );
  }

  /**
   * @param s The string in which to replace the meta characters '$' and '\'.
   *
   * @return A string where meta characters relevant for {@link Matcher#appendReplacement} are escaped.
   */
  private String escapeMetaCharacters(String s) {
    String escapedString = s.replace( "\\", "\\\\" );
    escapedString = escapedString.replace( "$", "\\$" );
    return escapedString;
  }

  private static class LocalisedMessage {
    private final String message;
    private final Locale locale;

    LocalisedMessage(String message, Locale locale) {
      this.message = message;
      this.locale = locale;
    }

    @Override
    public boolean equals(Object o) {
      if ( this == o ) {
        return true;
      }
      if ( o == null || getClass() != o.getClass() ) {
        return false;
      }

      LocalisedMessage that = ( LocalisedMessage ) o;

      if ( locale != null ? !locale.equals( that.locale ) : that.locale != null ) {
        return false;
      }
      if ( message != null ? !message.equals( that.message ) : that.message != null ) {
        return false;
      }

      return true;
    }

    @Override
    public int hashCode() {
      int result = message != null ? message.hashCode() : 0;
      result = 31 * result + ( locale != null ? locale.hashCode() : 0 );
      return result;
    }
  }
}
TOP

Related Classes of org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator$LocalisedMessage

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.