001/* 002 * Copyright 2012 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.runtime.i18n; 017 018import java.util.List; 019import java.util.Map; 020import java.util.Optional; 021 022import org.apache.avalon.framework.configuration.Configuration; 023import org.apache.avalon.framework.configuration.ConfigurationException; 024import org.apache.cocoon.transformation.I18nTransformer; 025import org.apache.cocoon.xml.AttributesImpl; 026import org.apache.commons.lang3.builder.EqualsBuilder; 027import org.apache.commons.lang3.builder.HashCodeBuilder; 028import org.xml.sax.ContentHandler; 029import org.xml.sax.SAXException; 030 031/** 032 * This class wraps a text that <i>may</i> be internationalized. 033 */ 034public final class I18nizableText implements I18nizable, I18nizableTextParameter 035{ 036 private final boolean _i18n; 037 private String _directLabel; 038 private String _key; 039 private String _catalogue; 040 private List<String> _parameters; 041 private Map<String, I18nizableTextParameter> _parameterMap; 042 private String _catalogueLocation; 043 private String _catalogueBundleName; 044 045 /** 046 * Create a simple international text 047 * @param label The text. Cannot be null. 048 */ 049 public I18nizableText(String label) 050 { 051 _i18n = false; 052 _directLabel = label; 053 } 054 055 /** 056 * Create an i18nized text 057 * @param catalogue the catalogue where the key is defined. Can be null. Can be overloaded by the catalogue in the key. 058 * @param key the key of the text. Cannot be null. May include the catalogue using the character ':' as separator. CATALOG:KEY. 059 */ 060 public I18nizableText(String catalogue, String key) 061 { 062 this(catalogue, key, (List<String>) null); 063 } 064 065 /** 066 * Create an i18nized text with ordered parameters. 067 * @param catalogue the catalogue where the key is defined. Can be null. Can be overloaded by the catalogue in the key. 068 * @param key the key of the text. Cannot be null. May include the catalogue using the character ':' as separator. CATALOG:KEY. 069 * @param parameters the parameters of the key if any. Can be null. 070 */ 071 public I18nizableText(String catalogue, String key, List<String> parameters) 072 { 073 _i18n = true; 074 075 String i18nKey = key.substring(key.indexOf(":") + 1); 076 String i18nCatalogue = i18nKey.length() == key.length() ? catalogue : key.substring(0, key.length() - i18nKey.length() - 1); 077 078 _catalogue = i18nCatalogue; 079 _key = i18nKey; 080 _parameters = parameters; 081 _parameterMap = null; 082 } 083 084 /** 085 * Create an i18nized text with named parameters. 086 * @param catalogue the catalogue where the key is defined. Can be null. Can be overloaded by the catalogue in the key. 087 * @param key the key of the text. Cannot be null. May include the catalogue using the character ':' as separator. CATALOG:KEY. 088 * @param parameters the named parameters of the message, as a Map of name -> value. Value can itself be an i18n key but must not have parameters. 089 */ 090 public I18nizableText(String catalogue, String key, Map<String, I18nizableTextParameter> parameters) 091 { 092 _i18n = true; 093 094 String i18nKey = key.substring(key.indexOf(":") + 1); 095 String i18nCatalogue = i18nKey.length() == key.length() ? catalogue : key.substring(0, key.length() - i18nKey.length() - 1); 096 097 _catalogue = i18nCatalogue; 098 _key = i18nKey; 099 _parameterMap = parameters; 100 _parameters = null; 101 } 102 103 /** 104 * Create an i18nized text. 105 * Use this constructor only when the catalogue is an external catalogue, not managed by Ametys application 106 * @param catalogueLocation the file location URI of the catalogue where the key is defined. 107 * @param catalogueFilename the catalogue bundle name such as 'messages' 108 * @param key the key of the text. Cannot be null. 109 */ 110 public I18nizableText(String catalogueLocation, String catalogueFilename, String key) 111 { 112 this(catalogueLocation, catalogueFilename, key, (List<String>) null); 113 } 114 115 /** 116 * Create an i18nized text with ordered parameters. 117 * Use this constructor only when the catalogue is an external catalogue, not managed by Ametys application 118 * @param catalogueLocation the file location URI of the catalogue where the key is defined. 119 * @param catalogueFilename the catalogue bundle name such as 'messages' 120 * @param key the key of the text. Cannot be null. 121 * @param parameters the parameters of the key if any. Can be null. 122 */ 123 public I18nizableText(String catalogueLocation, String catalogueFilename, String key, List<String> parameters) 124 { 125 _i18n = true; 126 127 _catalogueLocation = catalogueLocation; 128 _catalogueBundleName = catalogueFilename; 129 _catalogue = null; 130 _key = key; 131 _parameters = parameters; 132 _parameterMap = null; 133 } 134 135 /** 136 * Create an i18nized text with named parameters. 137 * @param catalogueLocation the file location URI of the catalogue where the key is defined. 138 * @param catalogueFilename the catalogue bundle name such as 'messages' 139 * @param key the key of the text. Cannot be null. 140 * @param parameters the named parameters of the message, as a Map of name -> value. Value can itself be an i18n key but must not have parameters. 141 */ 142 public I18nizableText(String catalogueLocation, String catalogueFilename, String key, Map<String, I18nizableTextParameter> parameters) 143 { 144 _i18n = true; 145 146 _catalogueLocation = catalogueLocation; 147 _catalogueBundleName = catalogueFilename; 148 _catalogue = null; 149 _key = key; 150 _parameterMap = parameters; 151 _parameters = null; 152 } 153 154 /** 155 * Determine whether the text is i18nized or a simple cross languages text. 156 * @return true if the text is i18nized and so defined by a catalogue, a key and optionally parameters.<br>false if the text is a simple label 157 */ 158 public boolean isI18n() 159 { 160 return _i18n; 161 } 162 163 /** 164 * Get the catalogue of the i18nized text. 165 * @return The catalogue where the key is defined 166 */ 167 public String getCatalogue() 168 { 169 if (_i18n) 170 { 171 return _catalogue; 172 } 173 else 174 { 175 throw new IllegalArgumentException("This text is not i18nized and so do not have catalogue. Use the 'isI18n' method to know whether a text is i18nized"); 176 } 177 } 178 179 /** 180 * Get the file location URI of the i18nized text. 181 * @return The catalogue location where the key is defined 182 */ 183 public String getLocation() 184 { 185 if (_i18n) 186 { 187 return _catalogueLocation; 188 } 189 else 190 { 191 throw new IllegalArgumentException("This text is not i18nized and so do not have location. Use the 'isI18n' method to know whether a text is i18nized"); 192 } 193 } 194 195 /** 196 * Get the files name of catalogue 197 * @return bundle name 198 */ 199 public String getBundleName() 200 { 201 if (_i18n) 202 { 203 return _catalogueBundleName; 204 } 205 else 206 { 207 throw new IllegalArgumentException("This text is not i18nized and so do not have location. Use the 'isI18n' method to know whether a text is i18nized"); 208 } 209 } 210 211 /** 212 * Get the key of the i18nized text. 213 * @return The key in the catalogue 214 */ 215 public String getKey() 216 { 217 if (_i18n) 218 { 219 return _key; 220 } 221 else 222 { 223 throw new IllegalArgumentException("This text is not i18nized and so do not have key. Use the 'isI18n' method to know whether a text is i18nized"); 224 } 225 } 226 227 /** 228 * Get the parameters of the key of the i18nized text. 229 * @return The list of parameters' values or null if there is no parameters 230 */ 231 public List<String> getParameters() 232 { 233 if (_i18n) 234 { 235 return _parameters; 236 } 237 else 238 { 239 throw new IllegalArgumentException("This text is not i18nized and so do not have parameters. Use the 'isI18n' method to know whether a text is i18nized"); 240 } 241 } 242 243 /** 244 * Get the parameters of the key of the i18nized text. 245 * @return The list of parameters' values or null if there is no parameters 246 */ 247 public Map<String, I18nizableTextParameter> getParameterMap() 248 { 249 if (_i18n) 250 { 251 return _parameterMap; 252 } 253 else 254 { 255 throw new IllegalArgumentException("This text is not i18nized and so do not have parameters. Use the 'isI18n' method to know whether a text is i18nized"); 256 } 257 } 258 259 /** 260 * Get the label if a text is not i18nized. 261 * @return The label 262 */ 263 public String getLabel() 264 { 265 if (!_i18n) 266 { 267 return _directLabel; 268 } 269 else 270 { 271 throw new IllegalArgumentException("This text is i18nized and so do not have label. Use the 'isI18n' method to know whether a text is i18nized"); 272 } 273 } 274 275 public void toSAX(ContentHandler handler) throws SAXException 276 { 277 if (isI18n()) 278 { 279 List<String> parameters = getParameters(); 280 Map<String, ? extends I18nizableTextParameter> parameterMap = getParameterMap(); 281 boolean hasParameter = parameters != null && parameters.size() > 0 || parameterMap != null && !parameterMap.isEmpty(); 282 283 handler.startPrefixMapping("i18n", I18nTransformer.I18N_NAMESPACE_URI); 284 285 if (hasParameter) 286 { 287 handler.startElement(I18nTransformer.I18N_NAMESPACE_URI, "translate", "i18n:translate", new AttributesImpl()); 288 } 289 290 AttributesImpl atts = new AttributesImpl(); 291 atts.addCDATAAttribute(I18nTransformer.I18N_NAMESPACE_URI, "key", "i18n:key", getKey()); 292 if (getCatalogue() != null) 293 { 294 atts.addCDATAAttribute(I18nTransformer.I18N_NAMESPACE_URI, "catalogue", "i18n:catalogue", getCatalogue()); 295 } 296 297 handler.startElement(I18nTransformer.I18N_NAMESPACE_URI, "text", "i18n:text", atts); 298 handler.endElement(I18nTransformer.I18N_NAMESPACE_URI, "text", "i18n:text"); 299 300 if (hasParameter) 301 { 302 if (parameters != null) 303 { 304 // Ordered parameters version. 305 for (String parameter : parameters) 306 { 307 if (parameter != null) 308 { 309 handler.startElement(I18nTransformer.I18N_NAMESPACE_URI, "param", "i18n:param", new AttributesImpl()); 310 handler.characters(parameter.toCharArray(), 0, parameter.length()); 311 handler.endElement(I18nTransformer.I18N_NAMESPACE_URI, "param", "i18n:param"); 312 } 313 } 314 } 315 else if (parameterMap != null) 316 { 317 // Named parameters version. 318 for (String parameterName : parameterMap.keySet()) 319 { 320 I18nizableTextParameter value = parameterMap.get(parameterName); 321 AttributesImpl attrs = new AttributesImpl(); 322 attrs.addCDATAAttribute("name", parameterName); 323 handler.startElement(I18nTransformer.I18N_NAMESPACE_URI, "param", "i18n:param", attrs); 324 value.toSAXAsParam(handler); 325 handler.endElement(I18nTransformer.I18N_NAMESPACE_URI, "param", "i18n:param"); 326 } 327 } 328 329 handler.endElement(I18nTransformer.I18N_NAMESPACE_URI, "translate", "i18n:translate"); 330 } 331 332 handler.endPrefixMapping("i18n"); 333 } 334 else 335 { 336 handler.characters(getLabel().toCharArray(), 0, getLabel().length()); 337 } 338 } 339 340 public void toSAXAsParam(ContentHandler handler) throws SAXException 341 { 342 if (isI18n()) 343 { 344 AttributesImpl atts = new AttributesImpl(); 345 if (getCatalogue() != null) 346 { 347 atts.addCDATAAttribute(I18nTransformer.I18N_NAMESPACE_URI, "catalogue", "i18n:catalogue", getCatalogue()); 348 } 349 350 handler.startElement(I18nTransformer.I18N_NAMESPACE_URI, "text", "i18n:text", atts); 351 handler.characters(_key.toCharArray(), 0, _key.length()); 352 handler.endElement(I18nTransformer.I18N_NAMESPACE_URI, "text", "i18n:text"); 353 } 354 else 355 { 356 handler.characters(getLabel().toCharArray(), 0, getLabel().length()); 357 } 358 } 359 360 @Override 361 public String toString() 362 { 363 String result = ""; 364 if (isI18n()) 365 { 366 result += getCatalogue() + ":" + getKey(); 367 List<String> parameters = getParameters(); 368 if (parameters != null) 369 { 370 result += "["; 371 boolean isFirst = true; 372 for (String parameter : parameters) 373 { 374 if (!isFirst) 375 { 376 result += "; param : " + parameter; 377 } 378 else 379 { 380 result += "param : " + parameter; 381 isFirst = false; 382 } 383 } 384 result += "]"; 385 } 386 } 387 else 388 { 389 result = getLabel(); 390 } 391 return result; 392 } 393 394 @Override 395 public int hashCode() 396 { 397 HashCodeBuilder hashCodeBuilder = new HashCodeBuilder(); 398 hashCodeBuilder.append(_i18n); 399 hashCodeBuilder.append(_key); 400 hashCodeBuilder.append(_catalogueLocation); 401 hashCodeBuilder.append(_catalogueBundleName); 402 hashCodeBuilder.append(_catalogue); 403 hashCodeBuilder.append(_directLabel); 404 405 if (_parameters == null) 406 { 407 hashCodeBuilder.append((Object) null); 408 } 409 else 410 { 411 hashCodeBuilder.append(_parameters.toArray(new String[_parameters.size()])); 412 } 413 414 hashCodeBuilder.append(_parameterMap); 415 416 return hashCodeBuilder.toHashCode(); 417 } 418 419 @Override 420 public boolean equals(Object obj) 421 { 422 if (obj == null) 423 { 424 return false; 425 } 426 427 if (!(obj instanceof I18nizableText)) 428 { 429 return false; 430 } 431 432 if (this == obj) 433 { 434 return true; 435 } 436 437 I18nizableText i18nObj = (I18nizableText) obj; 438 if (_i18n != i18nObj._i18n) 439 { 440 return false; 441 } 442 443 EqualsBuilder equalsBuilder = new EqualsBuilder(); 444 if (_i18n) 445 { 446 equalsBuilder.append(_key, i18nObj._key); 447 448 if (_catalogue == null) 449 { 450 equalsBuilder.append(_catalogueLocation, i18nObj._catalogueLocation); 451 equalsBuilder.append(_catalogueBundleName, i18nObj._catalogueBundleName); 452 } 453 else 454 { 455 equalsBuilder.append(_catalogue, i18nObj._catalogue); 456 } 457 458 if (_parameters == null) 459 { 460 equalsBuilder.append(_parameters, i18nObj._parameters); 461 } 462 else 463 { 464 String[] otherParameters = null; 465 466 if (i18nObj._parameters != null) 467 { 468 otherParameters = i18nObj._parameters.toArray(new String[i18nObj._parameters.size()]); 469 } 470 471 equalsBuilder.append(_parameters.toArray(new String[_parameters.size()]), 472 otherParameters); 473 } 474 475 equalsBuilder.append(_parameterMap, i18nObj.getParameterMap()); 476 } 477 else 478 { 479 equalsBuilder.append(_directLabel, i18nObj._directLabel); 480 } 481 482 return equalsBuilder.isEquals(); 483 } 484 485 /** 486 * Check if configuration is an i18nizable text 487 * @param config the config to check 488 * @return true if the config is an I18n 489 */ 490 public static boolean isI18n(Configuration config) 491 { 492 return config.getAttributeAsBoolean("i18n", false) || config.getAttribute("type", "").equals("i18n"); 493 } 494 495 /** 496 * Get an i18n text configuration (can be a key or a "direct" string). 497 * @param config The configuration to parse. 498 * @param catalogueLocation The i18n catalogue location URI 499 * @param catalogueFilename The i18n catalogue bundle name 500 * @param value The i18n text, can be a key or a "direct" string. 501 * @return The i18nizable text 502 */ 503 private static I18nizableText getI18nizableTextValue(Configuration config, String catalogueLocation, String catalogueFilename, String value) 504 { 505 return new I18nizableText(catalogueLocation, catalogueFilename, value); 506 } 507 508 /** 509 * Get an i18n text configuration (can be a key or a "direct" string). 510 * @param config The configuration to parse. 511 * @param defaultCatalogue The i18n catalogue to use when not specified. 512 * @param value The i18n text, can be a key or a "direct" string. 513 * @return The i18nizable text or null if the config is null 514 */ 515 public static I18nizableText getI18nizableTextValue(Configuration config, String defaultCatalogue, String value) 516 { 517 if (config != null) 518 { 519 if (I18nizableText.isI18n(config)) 520 { 521 String catalogue = config.getAttribute("catalogue", defaultCatalogue); 522 523 return new I18nizableText(catalogue, value); 524 } 525 else 526 { 527 return new I18nizableText(value); 528 } 529 } 530 else 531 { 532 return null; 533 } 534 } 535 536 /** 537 * Parse a i18n text configuration. 538 * @param config the configuration to use. 539 * @param catalogueLocation The i18n catalogue location URI 540 * @param catalogueFilename The i18n catalogue bundle name 541 * @param defaultValue The default value key in configuration 542 * @return the i18n text or null if the config is null 543 */ 544 public static I18nizableText parseI18nizableText(Configuration config, String catalogueLocation, String catalogueFilename, String defaultValue) 545 { 546 if (config != null) 547 { 548 String text = config.getValue(defaultValue); 549 return I18nizableText.getI18nizableTextValue(config, catalogueLocation, catalogueFilename, text); 550 } 551 else 552 { 553 return null; 554 } 555 } 556 557 /** 558 * Parse an optional i18n text configuration, with a default value. 559 * @param config the configuration to use. 560 * @param defaultCatalogue the i18n catalogue to use when not specified. 561 * @param defaultValue the default value key in configuration. 562 * @return the i18n text or null if the config is null 563 */ 564 public static I18nizableText parseI18nizableText(Configuration config, String defaultCatalogue, String defaultValue) 565 { 566 return Optional.ofNullable(config) 567 .map(cfg -> cfg.getValue(defaultValue)) 568 .map(text -> I18nizableText.getI18nizableTextValue(config, defaultCatalogue, text)) 569 .orElse(new I18nizableText(defaultValue)); 570 } 571 572 /** 573 * Parse an optional i18n text configuration, with a default i18n text value. 574 * @param config the configuration to use. 575 * @param defaultCatalogue the i18n catalogue to use when not specified. 576 * @param defaultValue the default i18n text value. 577 * @return the i18n text or null if the config is null 578 */ 579 public static I18nizableText parseI18nizableText(Configuration config, String defaultCatalogue, I18nizableText defaultValue) 580 { 581 return Optional.ofNullable(config) 582 .map(cfg -> cfg.getValue(null)) 583 .map(text -> I18nizableText.getI18nizableTextValue(config, defaultCatalogue, text)) 584 .orElse(defaultValue); 585 } 586 587 /** 588 * Parse a mandatory i18n text configuration, throwing an exception if empty. 589 * @param config the configuration to use. 590 * @param defaultCatalogue the i18n catalogue to use when not specified. 591 * @return the i18n text or null if the config is null 592 * @throws ConfigurationException if the configuration is not valid. 593 */ 594 public static I18nizableText parseI18nizableText(Configuration config, String defaultCatalogue) throws ConfigurationException 595 { 596 if (config != null) 597 { 598 String text = config.getValue(); 599 return I18nizableText.getI18nizableTextValue(config, defaultCatalogue, text); 600 } 601 else 602 { 603 return null; 604 } 605 } 606}