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