FormSet.java

  1. /*
  2.  * Licensed to the Apache Software Foundation (ASF) under one or more
  3.  * contributor license agreements.  See the NOTICE file distributed with
  4.  * this work for additional information regarding copyright ownership.
  5.  * The ASF licenses this file to You under the Apache License, Version 2.0
  6.  * (the "License"); you may not use this file except in compliance with
  7.  * the License.  You may obtain a copy of the License at
  8.  *
  9.  *      https://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.apache.commons.validator;

  18. import java.io.Serializable;
  19. import java.util.Collections;
  20. import java.util.HashMap;
  21. import java.util.Locale;
  22. import java.util.Map;
  23. import java.util.Map.Entry;
  24. import java.util.Objects;

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

  27. /**
  28.  * Holds a set of {@code Form}s stored associated with a {@link Locale}
  29.  * based on the country, language, and variant specified. Instances of this
  30.  * class are configured with a <formset> xml element.
  31.  */
  32. public class FormSet implements Serializable {

  33.     private static final long serialVersionUID = -8936513232763306055L;

  34.     /**
  35.      * This is the type of {@code FormSet}s where no locale is specified.
  36.      */
  37.     protected static final int GLOBAL_FORMSET = 1;

  38.     /**
  39.      * This is the type of {@code FormSet}s where only language locale is
  40.      * specified.
  41.      */
  42.     protected static final int LANGUAGE_FORMSET = 2;

  43.     /**
  44.      * This is the type of {@code FormSet}s where only language and country
  45.      * locale are specified.
  46.      */
  47.     protected static final int COUNTRY_FORMSET = 3;

  48.     /**
  49.      * This is the type of {@code FormSet}s where full locale has been set.
  50.      */
  51.     protected static final int VARIANT_FORMSET = 4;

  52.     /** Logging */
  53.     private transient Log log = LogFactory.getLog(FormSet.class);

  54.     /**
  55.      * Whether or not the this {@code FormSet} was processed for replacing
  56.      * variables in strings with their values.
  57.      */
  58.     private boolean processed;

  59.     /** Language component of {@link Locale} (required). */
  60.     private String language;

  61.     /** Country component of {@link Locale} (optional). */
  62.     private String country;

  63.     /** Variant component of {@link Locale} (optional). */
  64.     private String variant;

  65.     /**
  66.      * A {@link Map} of {@code Form}s using the name field of the
  67.      * {@code Form} as the key.
  68.      */
  69.     private final Map<String, Form> forms = new HashMap<>();

  70.     /**
  71.      * A {@link Map} of {@code Constant}s using the name field of the
  72.      * {@code Constant} as the key.
  73.      */
  74.     private final Map<String, String> constants = new HashMap<>();

  75.     /**
  76.      * Flag indicating if this formSet has been merged with its parent (higher
  77.      * rank in Locale hierarchy).
  78.      */
  79.     private volatile boolean merged;

  80.     /**
  81.      * Constructs a new instance.
  82.      */
  83.     public FormSet() {
  84.         // empty
  85.     }

  86.     /**
  87.      * Add a {@code Constant} to the locale level.
  88.      *
  89.      * @param name   The constant name
  90.      * @param value  The constant value
  91.      */
  92.     public void addConstant(final String name, final String value) {
  93.         if (constants.containsKey(name)) {
  94.             getLog().error("Constant '" + name + "' already exists in FormSet[" + displayKey() + "] - ignoring.");
  95.         } else {
  96.             constants.put(name, value);
  97.         }
  98.     }

  99.     /**
  100.      * Add a {@code Form} to the {@code FormSet}.
  101.      *
  102.      * @param f  The form
  103.      */
  104.     public void addForm(final Form f) {

  105.         final String formName = f.getName();
  106.         if (forms.containsKey(formName)) {
  107.             getLog().error("Form '" + formName + "' already exists in FormSet[" + displayKey() + "] - ignoring.");

  108.         } else {
  109.             forms.put(f.getName(), f);
  110.         }

  111.     }

  112.     /**
  113.      * Returns a string representation of the object's key.
  114.      *
  115.      * @return   A string representation of the key
  116.      */
  117.     public String displayKey() {
  118.         final StringBuilder results = new StringBuilder();
  119.         if (language != null && !language.isEmpty()) {
  120.             results.append("language=");
  121.             results.append(language);
  122.         }
  123.         if (country != null && !country.isEmpty()) {
  124.             if (results.length() > 0) {
  125.                 results.append(", ");
  126.             }
  127.             results.append("country=");
  128.             results.append(country);
  129.         }
  130.         if (variant != null && !variant.isEmpty()) {
  131.             if (results.length() > 0) {
  132.                 results.append(", ");
  133.             }
  134.             results.append("variant=");
  135.             results.append(variant);
  136.         }
  137.         if (results.length() == 0) {
  138.             results.append("default");
  139.         }

  140.         return results.toString();
  141.     }

  142.     /**
  143.      * Gets the equivalent of the country component of {@link Locale}.
  144.      *
  145.      * @return   The country value
  146.      */
  147.     public String getCountry() {
  148.         return country;
  149.     }

  150.     /**
  151.      * Retrieve a {@code Form} based on the form name.
  152.      *
  153.      * @param formName  The form name
  154.      * @return          The form
  155.      */
  156.     public Form getForm(final String formName) {
  157.         return forms.get(formName);
  158.     }

  159.     /**
  160.      * A {@link Map} of {@code Form}s is returned as an unmodifiable
  161.      * {@link Map} with the key based on the form name.
  162.      *
  163.      * @return   The forms map
  164.      */
  165.     public Map<String, Form> getForms() {
  166.         return Collections.unmodifiableMap(forms);
  167.     }

  168.     /**
  169.      * Gets the equivalent of the language component of {@link Locale}.
  170.      *
  171.      * @return   The language value
  172.      */
  173.     public String getLanguage() {
  174.         return language;
  175.     }

  176.     /**
  177.      * Accessor method for Log instance.
  178.      *
  179.      * The Log instance variable is transient and
  180.      * accessing it through this method ensures it
  181.      * is re-initialized when this instance is
  182.      * de-serialized.
  183.      *
  184.      * @return The Log instance.
  185.      */
  186.     private Log getLog() {
  187.         if (log == null) {
  188.             log = LogFactory.getLog(FormSet.class);
  189.         }
  190.         return log;
  191.     }

  192.     /**
  193.      * Returns the type of {@code FormSet}:{@code GLOBAL_FORMSET},
  194.      * {@code LANGUAGE_FORMSET},{@code COUNTRY_FORMSET} or {@code VARIANT_FORMSET}.
  195.      *
  196.      * @return                       The type value
  197.      * @throws NullPointerException  if there is inconsistency in the locale
  198.      *      definition (not sure about this)
  199.      * @since 1.2.0
  200.      */
  201.     protected int getType() {
  202.         final String myLanguage = getLanguage();
  203.         final String myCountry = getCountry();
  204.         if (getVariant() != null) {
  205.             Objects.requireNonNull(myLanguage, "When variant is specified, country and language must be specified.");
  206.             Objects.requireNonNull(myCountry, "When variant is specified, country and language must be specified.");
  207.             return VARIANT_FORMSET;
  208.         }
  209.         if (myCountry != null) {
  210.             Objects.requireNonNull(myLanguage, "When country is specified, language must be specified.");
  211.             return COUNTRY_FORMSET;
  212.         }
  213.         if (myLanguage != null) {
  214.             return LANGUAGE_FORMSET;
  215.         }
  216.         return GLOBAL_FORMSET;
  217.     }

  218.     /**
  219.      * Gets the equivalent of the variant component of {@link Locale}.
  220.      *
  221.      * @return   The variant value
  222.      */
  223.     public String getVariant() {
  224.         return variant;
  225.     }

  226.     /**
  227.      * Has this formSet been merged?
  228.      *
  229.      * @return   true if it has been merged
  230.      * @since 1.2.0
  231.      */
  232.     protected boolean isMerged() {
  233.         return merged;
  234.     }

  235.     /**
  236.      * Whether or not the this {@code FormSet} was processed for replacing
  237.      * variables in strings with their values.
  238.      *
  239.      * @return   The processed value
  240.      */
  241.     public boolean isProcessed() {
  242.         return processed;
  243.     }

  244.     /**
  245.      * Merges the given {@code FormSet} into this one. If any of {@code depends}
  246.      * s {@code Forms} are not in this {@code FormSet} then, include
  247.      * them, else merge both {@code Forms}. Theoretically we should only
  248.      * merge a "parent" formSet.
  249.      *
  250.      * @param depends  FormSet to be merged
  251.      * @since 1.2.0
  252.      */
  253.     protected void merge(final FormSet depends) {
  254.         if (depends != null) {
  255.             final Map<String, Form> myForms = getForms();
  256.             final Map<String, Form> dependsForms = depends.getForms();
  257.             for (final Entry<String, Form> entry : dependsForms.entrySet()) {
  258.                 final String key = entry.getKey();
  259.                 final Form form = myForms.get(key);
  260.                 if (form != null) { // merge, but principal 'rules', don't overwrite
  261.                     // anything
  262.                     form.merge(entry.getValue());
  263.                 } else { // just add
  264.                     addForm(entry.getValue());
  265.                 }
  266.             }
  267.         }
  268.         merged = true;
  269.     }

  270.     /**
  271.      * Processes all of the {@code Form}s.
  272.      *
  273.      * @param globalConstants  Global constants
  274.      */
  275.     synchronized void process(final Map<String, String> globalConstants) {
  276.         for (final Form f : forms.values()) {
  277.             f.process(globalConstants, constants, forms);
  278.         }

  279.         processed = true;
  280.     }

  281.     /**
  282.      * Sets the equivalent of the country component of {@link Locale}.
  283.      *
  284.      * @param country  The new country value
  285.      */
  286.     public void setCountry(final String country) {
  287.         this.country = country;
  288.     }

  289.     /**
  290.      * Sets the equivalent of the language component of {@link Locale}.
  291.      *
  292.      * @param language  The new language value
  293.      */
  294.     public void setLanguage(final String language) {
  295.         this.language = language;
  296.     }

  297.     /**
  298.      * Sets the equivalent of the variant component of {@link Locale}.
  299.      *
  300.      * @param variant  The new variant value
  301.      */
  302.     public void setVariant(final String variant) {
  303.         this.variant = variant;
  304.     }

  305.     /**
  306.      * Returns a string representation of the object.
  307.      *
  308.      * @return   A string representation
  309.      */
  310.     @Override
  311.     public String toString() {
  312.         final StringBuilder results = new StringBuilder();

  313.         results.append("FormSet: language=");
  314.         results.append(language);
  315.         results.append("  country=");
  316.         results.append(country);
  317.         results.append("  variant=");
  318.         results.append(variant);
  319.         results.append("\n");

  320.         for (final Object name : getForms().values()) {
  321.             results.append("   ");
  322.             results.append(name);
  323.             results.append("\n");
  324.         }

  325.         return results.toString();
  326.     }
  327. }