001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.validator; 018 019import java.io.Serializable; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.Locale; 023import java.util.Map; 024import java.util.Map.Entry; 025import java.util.Objects; 026 027import org.apache.commons.logging.Log; 028import org.apache.commons.logging.LogFactory; 029 030/** 031 * Holds a set of {@code Form}s stored associated with a {@link Locale} 032 * based on the country, language, and variant specified. Instances of this 033 * class are configured with a <formset> xml element. 034 */ 035public class FormSet implements Serializable { 036 037 private static final long serialVersionUID = -8936513232763306055L; 038 039 /** 040 * This is the type of {@code FormSet}s where no locale is specified. 041 */ 042 protected static final int GLOBAL_FORMSET = 1; 043 044 /** 045 * This is the type of {@code FormSet}s where only language locale is 046 * specified. 047 */ 048 protected static final int LANGUAGE_FORMSET = 2; 049 050 /** 051 * This is the type of {@code FormSet}s where only language and country 052 * locale are specified. 053 */ 054 protected static final int COUNTRY_FORMSET = 3; 055 056 /** 057 * This is the type of {@code FormSet}s where full locale has been set. 058 */ 059 protected static final int VARIANT_FORMSET = 4; 060 061 /** Logging */ 062 private transient Log log = LogFactory.getLog(FormSet.class); 063 064 /** 065 * Whether or not the this {@code FormSet} was processed for replacing 066 * variables in strings with their values. 067 */ 068 private boolean processed; 069 070 /** Language component of {@link Locale} (required). */ 071 private String language; 072 073 /** Country component of {@link Locale} (optional). */ 074 private String country; 075 076 /** Variant component of {@link Locale} (optional). */ 077 private String variant; 078 079 /** 080 * A {@link Map} of {@code Form}s using the name field of the 081 * {@code Form} as the key. 082 */ 083 private final Map<String, Form> forms = new HashMap<>(); 084 085 /** 086 * A {@link Map} of {@code Constant}s using the name field of the 087 * {@code Constant} as the key. 088 */ 089 private final Map<String, String> constants = new HashMap<>(); 090 091 /** 092 * Flag indicating if this formSet has been merged with its parent (higher 093 * rank in Locale hierarchy). 094 */ 095 private volatile boolean merged; 096 097 /** 098 * Constructs a new instance. 099 */ 100 public FormSet() { 101 // empty 102 } 103 104 /** 105 * Add a {@code Constant} to the locale level. 106 * 107 * @param name The constant name 108 * @param value The constant value 109 */ 110 public void addConstant(final String name, final String value) { 111 if (constants.containsKey(name)) { 112 getLog().error("Constant '" + name + "' already exists in FormSet[" + displayKey() + "] - ignoring."); 113 } else { 114 constants.put(name, value); 115 } 116 } 117 118 /** 119 * Add a {@code Form} to the {@code FormSet}. 120 * 121 * @param f The form 122 */ 123 public void addForm(final Form f) { 124 125 final String formName = f.getName(); 126 if (forms.containsKey(formName)) { 127 getLog().error("Form '" + formName + "' already exists in FormSet[" + displayKey() + "] - ignoring."); 128 129 } else { 130 forms.put(f.getName(), f); 131 } 132 133 } 134 135 /** 136 * Returns a string representation of the object's key. 137 * 138 * @return A string representation of the key 139 */ 140 public String displayKey() { 141 final StringBuilder results = new StringBuilder(); 142 if (language != null && !language.isEmpty()) { 143 results.append("language="); 144 results.append(language); 145 } 146 if (country != null && !country.isEmpty()) { 147 if (results.length() > 0) { 148 results.append(", "); 149 } 150 results.append("country="); 151 results.append(country); 152 } 153 if (variant != null && !variant.isEmpty()) { 154 if (results.length() > 0) { 155 results.append(", "); 156 } 157 results.append("variant="); 158 results.append(variant); 159 } 160 if (results.length() == 0) { 161 results.append("default"); 162 } 163 164 return results.toString(); 165 } 166 167 /** 168 * Gets the equivalent of the country component of {@link Locale}. 169 * 170 * @return The country value 171 */ 172 public String getCountry() { 173 return country; 174 } 175 176 /** 177 * Retrieve a {@code Form} based on the form name. 178 * 179 * @param formName The form name 180 * @return The form 181 */ 182 public Form getForm(final String formName) { 183 return forms.get(formName); 184 } 185 186 /** 187 * A {@link Map} of {@code Form}s is returned as an unmodifiable 188 * {@link Map} with the key based on the form name. 189 * 190 * @return The forms map 191 */ 192 public Map<String, Form> getForms() { 193 return Collections.unmodifiableMap(forms); 194 } 195 196 /** 197 * Gets the equivalent of the language component of {@link Locale}. 198 * 199 * @return The language value 200 */ 201 public String getLanguage() { 202 return language; 203 } 204 205 /** 206 * Accessor method for Log instance. 207 * 208 * The Log instance variable is transient and 209 * accessing it through this method ensures it 210 * is re-initialized when this instance is 211 * de-serialized. 212 * 213 * @return The Log instance. 214 */ 215 private Log getLog() { 216 if (log == null) { 217 log = LogFactory.getLog(FormSet.class); 218 } 219 return log; 220 } 221 222 /** 223 * Returns the type of {@code FormSet}:{@code GLOBAL_FORMSET}, 224 * {@code LANGUAGE_FORMSET},{@code COUNTRY_FORMSET} or {@code VARIANT_FORMSET}. 225 * 226 * @return The type value 227 * @throws NullPointerException if there is inconsistency in the locale 228 * definition (not sure about this) 229 * @since 1.2.0 230 */ 231 protected int getType() { 232 final String myLanguage = getLanguage(); 233 final String myCountry = getCountry(); 234 if (getVariant() != null) { 235 Objects.requireNonNull(myLanguage, "When variant is specified, country and language must be specified."); 236 Objects.requireNonNull(myCountry, "When variant is specified, country and language must be specified."); 237 return VARIANT_FORMSET; 238 } 239 if (myCountry != null) { 240 Objects.requireNonNull(myLanguage, "When country is specified, language must be specified."); 241 return COUNTRY_FORMSET; 242 } 243 if (myLanguage != null) { 244 return LANGUAGE_FORMSET; 245 } 246 return GLOBAL_FORMSET; 247 } 248 249 /** 250 * Gets the equivalent of the variant component of {@link Locale}. 251 * 252 * @return The variant value 253 */ 254 public String getVariant() { 255 return variant; 256 } 257 258 /** 259 * Has this formSet been merged? 260 * 261 * @return true if it has been merged 262 * @since 1.2.0 263 */ 264 protected boolean isMerged() { 265 return merged; 266 } 267 268 /** 269 * Whether or not the this {@code FormSet} was processed for replacing 270 * variables in strings with their values. 271 * 272 * @return The processed value 273 */ 274 public boolean isProcessed() { 275 return processed; 276 } 277 278 /** 279 * Merges the given {@code FormSet} into this one. If any of {@code depends} 280 * s {@code Forms} are not in this {@code FormSet} then, include 281 * them, else merge both {@code Forms}. Theoretically we should only 282 * merge a "parent" formSet. 283 * 284 * @param depends FormSet to be merged 285 * @since 1.2.0 286 */ 287 protected void merge(final FormSet depends) { 288 if (depends != null) { 289 final Map<String, Form> myForms = getForms(); 290 final Map<String, Form> dependsForms = depends.getForms(); 291 for (final Entry<String, Form> entry : dependsForms.entrySet()) { 292 final String key = entry.getKey(); 293 final Form form = myForms.get(key); 294 if (form != null) { // merge, but principal 'rules', don't overwrite 295 // anything 296 form.merge(entry.getValue()); 297 } else { // just add 298 addForm(entry.getValue()); 299 } 300 } 301 } 302 merged = true; 303 } 304 305 /** 306 * Processes all of the {@code Form}s. 307 * 308 * @param globalConstants Global constants 309 */ 310 synchronized void process(final Map<String, String> globalConstants) { 311 for (final Form f : forms.values()) { 312 f.process(globalConstants, constants, forms); 313 } 314 315 processed = true; 316 } 317 318 /** 319 * Sets the equivalent of the country component of {@link Locale}. 320 * 321 * @param country The new country value 322 */ 323 public void setCountry(final String country) { 324 this.country = country; 325 } 326 327 /** 328 * Sets the equivalent of the language component of {@link Locale}. 329 * 330 * @param language The new language value 331 */ 332 public void setLanguage(final String language) { 333 this.language = language; 334 } 335 336 /** 337 * Sets the equivalent of the variant component of {@link Locale}. 338 * 339 * @param variant The new variant value 340 */ 341 public void setVariant(final String variant) { 342 this.variant = variant; 343 } 344 345 /** 346 * Returns a string representation of the object. 347 * 348 * @return A string representation 349 */ 350 @Override 351 public String toString() { 352 final StringBuilder results = new StringBuilder(); 353 354 results.append("FormSet: language="); 355 results.append(language); 356 results.append(" country="); 357 results.append(country); 358 results.append(" variant="); 359 results.append(variant); 360 results.append("\n"); 361 362 for (final Object name : getForms().values()) { 363 results.append(" "); 364 results.append(name); 365 results.append("\n"); 366 } 367 368 return results.toString(); 369 } 370}