MessageInterpolator.java

  1. package com.gh.mygreen.xlsmapper.localization;

  2. import java.util.Formatter;
  3. import java.util.LinkedHashMap;
  4. import java.util.LinkedList;
  5. import java.util.Map;
  6. import java.util.Objects;
  7. import java.util.Optional;

  8. import org.slf4j.Logger;
  9. import org.slf4j.LoggerFactory;

  10. import com.gh.mygreen.xlsmapper.expression.ExpressionEvaluationException;
  11. import com.gh.mygreen.xlsmapper.expression.ExpressionLanguage;
  12. import com.gh.mygreen.xlsmapper.expression.ExpressionLanguageJEXLImpl;
  13. import com.gh.mygreen.xlsmapper.util.ArgUtils;
  14. import com.gh.mygreen.xlsmapper.util.StackUtils;
  15. import com.gh.mygreen.xlsmapper.util.Utils;


  16. /**
  17.  * 名前付き変数のメッセージをフォーマットするクラス。
  18.  * <p><code>{...}</code>の場合、変数を単純に置換する。</p>
  19.  * <p><code>${...}</code>の場合、EL式を利用し処理する。</p>
  20.  * <p>文字'$', '{', '}'は特殊文字のため、<code>\</code>でエスケープを行う。</p>
  21.  * <p>ELのパーサは、{@link ExpressionLanguage}の実装クラスで切り替え可能。</p>
  22.  * <p>{@link MessageResolver}を指定した場合、メッセージ中の変数<code>{...}</code>をメッセージ定義コードとして解決する。
  23.  *   <br>ただし、メッセージ変数で指定されている変数が優先される。
  24.  * </p>
  25.  *
  26.  * @since 2.0
  27.  * @author T.TSUCHIE
  28.  *
  29.  */
  30. public class MessageInterpolator {
  31.    
  32.     private static final Logger logger = LoggerFactory.getLogger(MessageInterpolator.class);
  33.    
  34.     private ExpressionLanguage expressionLanguage;
  35.    
  36.     /**
  37.      * デフォルトのコンストラクタ
  38.      * <p>式言語の処理実装として、JEXLの{@link ExpressionLanguageJEXLImpl} が設定されます。
  39.      *
  40.      */
  41.     public MessageInterpolator() {
  42.         this.expressionLanguage = new ExpressionLanguageJEXLImpl();
  43.        
  44.     }
  45.    
  46.     /**
  47.      * 再帰処理の最大回数
  48.      */
  49.     private int maxRecursiveDepth = 5;
  50.    
  51.     /**
  52.      * 式言語の実装を指定するコンストラクタ
  53.      * @param expressionLanguage
  54.      */
  55.     public MessageInterpolator(final ExpressionLanguage expressionLanguage) {
  56.         ArgUtils.notNull(expressionLanguage, "expressionLanguage");
  57.         this.expressionLanguage = expressionLanguage;
  58.     }
  59.    
  60.     /**
  61.      * メッセージを引数varsで指定した変数で補完する。
  62.      *
  63.      * @param message 対象のメッセージ。
  64.      * @param vars メッセージ中の変数に対する値のマップ。
  65.      * @return 補完したメッセージ。
  66.      */
  67.     public String interpolate(final String message, final Map<String, ?> vars) {
  68.         return interpolate(message, vars, false);
  69.     }
  70.    
  71.     /**
  72.      * メッセージを引数varsで指定した変数で補完する。
  73.      *
  74.      * @param message 対象のメッセージ。
  75.      * @param vars メッセージ中の変数に対する値のマップ。
  76.      * @param recursive 変換したメッセージに対しても再帰的に処理するかどうか
  77.      * @return 補完したメッセージ。
  78.      */
  79.     public String interpolate(final String message, final Map<String, ?> vars, boolean recursive) {
  80.         return parse(message, vars, recursive, 0, null);
  81.     }
  82.    
  83.     /**
  84.      * メッセージを引数varsで指定した変数で補完する。
  85.      * <p>{@link MessageResolver}を指定した場合、メッセージ中の変数をメッセージコードとして解決します。
  86.      *
  87.      * @param message 対象のメッセージ。
  88.      * @param vars メッセージ中の変数に対する値のマップ。
  89.      * @param recursive 変換したメッセージに対しても再帰的に処理するかどうか
  90.      * @param messageResolver メッセージを解決するクラス。nullの場合、指定しないと同じ意味になります。
  91.      * @return 補完したメッセージ。
  92.      */
  93.     public String interpolate(final String message, final Map<String, ?> vars, boolean recursive,
  94.             final MessageResolver messageResolver) {
  95.         return parse(message, vars, recursive, 0, messageResolver);
  96.     }
  97.    
  98.     /**
  99.      * メッセージをパースし、変数に値を差し込み、EL式を評価する。
  100.      * @param message 対象のメッセージ。
  101.      * @param vars メッセージ中の変数に対する値のマップ。
  102.      * @param recursive 変換したメッセージに対しても再帰的に処理するかどうか。
  103.      * @param currentRecursiveDepth 現在の再帰処理回数。
  104.      * @param messageResolver メッセージを解決するクラス。nullの場合、指定しないと同じ意味になります。
  105.      * @return 補完したメッセージ。
  106.      */
  107.     protected String parse(final String message, final Map<String, ?> vars, boolean recursive, final int currentRecursiveDepth,
  108.             final MessageResolver messageResolver) {
  109.        
  110.         // 評価したメッセージを格納するバッファ。
  111.         final StringBuilder sb = new StringBuilder(message.length());
  112.        
  113.         /*
  114.          * 変数とEL式を解析する際に使用する、スタック変数。
  115.          * 式の開始が現れたらスタックに積み、式の終了が現れたらスタックから全てを取り出す。
  116.          * スタックに積まれるのは、1つ文の変数またはEL式。
  117.          */
  118.         final LinkedList<String> stack = new LinkedList<String>();
  119.        
  120.         final int length = message.length();
  121.        
  122.         for(int i=0; i < length; i++) {
  123.             final char c = message.charAt(i);
  124.            
  125.             if(StackUtils.equalsTopElement(stack, "\\")) {
  126.                 // 直前の文字がエスケープ文字の場合、エスケープ文字として結合する。
  127.                 String escapedChar = StackUtils.popup(stack) + c;
  128.                
  129.                 if(!stack.isEmpty()) {
  130.                     // 取り出した後もスタックがある場合は、式の途中であるため、再度スタックに積む。
  131.                     stack.push(escapedChar);
  132.                    
  133.                 } else {
  134.                     // 取り出した後にスタックがない場合は、エスケープを解除して通常の文字として積む。
  135.                     sb.append(c);
  136.                    
  137.                 }
  138.                
  139.             } else if(c == '\\') {
  140.                 // エスケープ文字の場合はスタックに積む。
  141.                 stack.push(String.valueOf(c));
  142.                
  143.             } else if(c == '$') {
  144.                 stack.push(String.valueOf(c));
  145.                
  146.             } else if(c == '{') {
  147.                
  148.                 if(!stack.isEmpty() && !StackUtils.equalsAnyBottomElement(stack, new String[]{"$", "{"})) {
  149.                     // スタックの先頭が式の開始形式でない場合
  150.                     throw new MessageParseException(message, "expression not start with '{' or '$'");
  151.                    
  152.                 } else {
  153.                     stack.push(String.valueOf(c));
  154.                 }
  155.                
  156.                
  157.             } else if(c == '}') {
  158.                
  159.                 if(StackUtils.equalsAnyBottomElement(stack, new String[]{"{", "$"})) {
  160.                     // 式の終わりの場合は、式を取り出し評価する。
  161.                     String expression = StackUtils.popupAndConcat(stack) + c;
  162.                    
  163.                     // エスケープを解除する
  164.                     expression = Utils.removeEscapeChar(expression, '\\');
  165.                    
  166.                     String result = evaluate(expression, vars, recursive, currentRecursiveDepth, messageResolver);
  167.                     sb.append(result);
  168.                    
  169.                 } else {
  170.                     sb.append(c);
  171.                    
  172.                 }
  173.                
  174.             } else {
  175.                
  176.                 if(stack.isEmpty()) {
  177.                     sb.append(c);
  178.                    
  179.                 } else {
  180.                     stack.push(String.valueOf(c));
  181.                 }
  182.                
  183.             }
  184.            
  185.         }
  186.        
  187.         if(!stack.isEmpty()) {
  188.             String val = StackUtils.popupAndConcat(stack);
  189.             val = Utils.removeEscapeChar(val, '\\');
  190.             sb.append(val);
  191.         }
  192.        
  193.         return sb.toString();
  194.     }
  195.    
  196.     private String evaluate(final String expression, final Map<String, ?> values, final boolean recursive,
  197.             final int currentRecursiveDepth, final MessageResolver messageResolver) {
  198.        
  199.         if(expression.startsWith("{")) {
  200.             // 変数の置換の場合
  201.             final String varName = expression.substring(1, expression.length()-1);
  202.            
  203.             if(values.containsKey(varName)) {
  204.                 // 該当するキーが存在する場合(再帰評価は行わない)
  205.                 final Object value = values.get(varName);
  206.                 final String eval = (value == null) ? "" : value.toString();
  207.                 return eval;
  208.                
  209.             } else if(messageResolver != null) {
  210.                 // メッセージコードをとして解決をする。
  211.                 final Optional<String> eval = messageResolver.getMessage(varName);
  212.                 if(!eval.isPresent()) {
  213.                     // 該当するキーが存在しない場合は、値をそのまま返す。
  214.                     return String.format("{%s}", varName);
  215.                 }
  216.                
  217.                 if(recursivable(recursive, maxRecursiveDepth, currentRecursiveDepth, eval.get())) {
  218.                     return parse(eval.get(), values, recursive, currentRecursiveDepth + 1, messageResolver);
  219.                 } else {
  220.                     return eval.get();
  221.                 }
  222.                
  223.             } else {
  224.                 // 該当するキーが存在しない場合は、値をそのまま返す。
  225.                 return expression.toString();
  226.             }
  227.            
  228.         } else if(expression.startsWith("${")) {
  229.             // EL式を評価する(再帰評価は行わない)
  230.             final String expr = expression.substring(2, expression.length()-1);
  231.             final String eval = evaluateExpression(expr, values);
  232.             return eval;
  233.            
  234.         }
  235.        
  236.         throw new MessageParseException(expression, "not support expression.");
  237.        
  238.     }
  239.    
  240.     /**
  241.      * 現在の再帰回数が最大回数に達しているかどうか。
  242.      *
  243.      * @param recursive 再帰的に処理するかどうか。
  244.      * @param maxRecursion 最大再帰回数
  245.      * @param currentDepth 再帰回数
  246.      * @param expression 再帰対象のメッセージ
  247.      * @return 最大再帰回数を超えていなければfalseを返す。
  248.      */
  249.     private boolean recursivable(final boolean recursive, final int maxRecursion, final int currentDepth,
  250.             String message) {

  251.         if(!recursive) {
  252.             return false;
  253.         }

  254.         if(maxRecursion <= 0) {
  255.             // 再帰回数の制限なし。
  256.             return true;
  257.         }

  258.         if(currentDepth <= maxRecursion) {
  259.             return true;
  260.         }

  261.         logger.warn("Over recursive depth : currentDepth={}, maxDepth={}, message={}.", currentDepth, maxRecursion, message);

  262.         return false;

  263.     }
  264.    
  265.     /**
  266.      * EL式を評価する。
  267.      * @param expression EL式
  268.      * @param values EL式中の変数。
  269.      * @return 評価した式。
  270.      * @throws ExpressionEvaluationException
  271.      */
  272.     protected String evaluateExpression(final String expression, final Map<String, ?> values) throws ExpressionEvaluationException {
  273.        
  274.         final Map<String, Object> context = new LinkedHashMap<String, Object>();
  275.         context.putAll(values);
  276.        
  277.         // フォーマッターの追加
  278.         context.computeIfAbsent("formatter", key -> new Formatter());
  279.        
  280.         /*
  281.          * 以下のケースの時、評価値はnullが返されるため、空文字に変換する。
  282.          * ・JEXLで存在しない変数名のとき。
  283.          * ・ELインジェクション対象の式のとき
  284.          */
  285.         String evalValue = Objects.toString(expressionLanguage.evaluate(expression, context), "");
  286.         if(logger.isTraceEnabled()) {
  287.             logger.trace("evaluate expression language: expression='{}' ===> value='{}'", expression, evalValue);
  288.         }
  289.        
  290.         return evalValue;
  291.     }
  292.    
  293.     /**
  294.      * EL式を解析する実装クラスを取得する。
  295.      * @return
  296.      */
  297.     public ExpressionLanguage getExpressionLanguage() {
  298.         return expressionLanguage;
  299.     }
  300.    
  301.     /**
  302.      * EL式を解析する実装クラスを設定する。
  303.      * @param expressionLanguage EL式の解析するクラスの実装。
  304.      */
  305.     public void setExpressionLanguage(ExpressionLanguage expressionLanguage) {
  306.         this.expressionLanguage = expressionLanguage;
  307.     }
  308.    
  309.     /**
  310.      * 評価した変数やEL式を再帰的に処するときの最大回数を取得します。
  311.      *
  312.      * @since 2.3
  313.      * @return 再帰的に処するときの最大回数。
  314.      */
  315.     public int getMaxRecursiveDepth() {
  316.         return maxRecursiveDepth;
  317.     }
  318.    
  319.     /**
  320.      * 評価した変数やEL式を再帰的に処するときの最大回数を設定します。
  321.      *
  322.      * @since 2.3
  323.      * @param maxRecursiveDepth 再帰的に処するときの最大回数。{@literal -1} のとき制限はありません。
  324.      */
  325.     public void setMaxRecursiveDepth(int maxRecursiveDepth) {
  326.         this.maxRecursiveDepth = maxRecursiveDepth;
  327.     }
  328.    
  329. }