001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software GmbH & Co. KG, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.widgets; 029 030import org.opencms.main.CmsLog; 031import org.opencms.util.CmsStringUtil; 032 033import java.util.ArrayList; 034import java.util.Collections; 035import java.util.List; 036 037import org.apache.commons.logging.Log; 038 039/** 040 * An option of a select type widget.<p> 041 * 042 * If options are passed from XML content schema definitions as widget configuration options, 043 * the following syntax is used for defining the option values:<p> 044 * 045 * <code>value='{text}' default='{true|false}' option='{text}' help='{text}|{more option definitions}</code><p> 046 * 047 * For example:<p> 048 * 049 * <code>value='value1' default='true' option='option1' help='help1'|value='value2' option='option2' help='help2'</code><p> 050 * 051 * The elements <code>default</code>, <code>option</code> and <code>help</code> are all optional, only a 052 * <code>value</code> must be present in the input. 053 * There should be only one <code>default</code> set to <code>true</code> 054 * in the input, if more than one is detected, only the first <code>default</code> found is actually used. 055 * If no <code>option</code> is given, the value of <code>option</code> defaults to the value of the given <code>value</code>. 056 * If no <code>help</code> is given, the default is <code>null</code>.<p> 057 * 058 * Shortcut syntax options:<p> 059 * 060 * If you don't specify the <code>value</code> key, the value is assumed to start at the first position of an 061 * option definition. In this case the value must not be surrounded by the <code>'</code> chars. 062 * Example: <code>value='some value' default='true'</code> can also be written as <code>some value default='true'</code>.<p> 063 * 064 * Only if you use the short value definition as described above, a default value can be marked with a <code>*</code> 065 * at the end of the value definition. 066 * Example: <code>value='some value' default='true'</code> can also be written as <code>some value*</code>.<p> 067 * 068 * Only if you use the short value definition as described above, you can also append the <code>option</code> 069 * to the <code>value</code> using a <code>:</code>. In this case no <code>'</code> must surround the <code>option</code>. 070 * Please keep in mind that in this case the value 071 * itself can not longer contain a <code>:</code> char, since it would then be interpreted as a delimiter. 072 * Example: <code>value='some value' option='some option'</code> can also be written as <code>some value:some option</code>.<p> 073 * 074 * Any combinations of the above described shortcuts are allowed in the configuration option String. 075 * Here are some more examples of valid configuration option Strings:<p> 076 * 077 * <code>1*|2|3|4|5|6|7</code><br> 078 * <code>1 default='true'|2|3|4|5|6|7</code><br> 079 * <code>value='1' default='true'|value='2'|value='3'</code><br> 080 * <code>value='1'|2*|value='3'</code><br> 081 * <code>1*:option text|2|3|4</code><br> 082 * <code>1* option='option text' help='some'|2|3|4</code><p> 083 * 084 * Please note: If an entry in the configuration String is malformed, this error is silently ignored (but written 085 * to the log channel of this class at <code>INFO</code>level.<p> 086 * 087 * @since 6.0.0 088 */ 089public class CmsSelectWidgetOption { 090 091 /** Optional shortcut default marker. */ 092 private static final char DEFAULT_MARKER = '*'; 093 094 /** Delimiter between option sets. */ 095 private static final char INPUT_DELIMITER = '|'; 096 097 /** Key prefix for the 'default'. */ 098 private static final String KEY_DEFAULT = "default='"; 099 100 /** Key prefix for the 'help' text. */ 101 private static final String KEY_HELP = "help='"; 102 103 /** Key prefix for the 'option' text. */ 104 private static final String KEY_OPTION = "option='"; 105 106 /** Key prefix for the 'value'. */ 107 private static final String KEY_VALUE = "value='"; 108 109 /** The log object for this class. */ 110 private static final Log LOG = CmsLog.getLog(CmsSelectWidgetOption.class); 111 112 /** Optional shortcut option delimiter. */ 113 private static final char OPTION_DELIMITER = ':'; 114 115 /** Indicates if this is the default value of the selection. */ 116 private boolean m_default; 117 118 /** The hashcode of this object. */ 119 private int m_hashcode; 120 121 /** The (optional) help text of this select option. */ 122 private String m_help; 123 124 /** The (optional) display text of this select option. */ 125 private String m_option; 126 127 /** The value of this select option. */ 128 private String m_value; 129 130 /** 131 * Creates a new select option for the given value.<p> 132 * 133 * @param value the value of this select option 134 */ 135 public CmsSelectWidgetOption(String value) { 136 137 this(value, false, null, null); 138 } 139 140 /** 141 * Creates a new select option form the given values.<p> 142 * 143 * @param value the value of this select option 144 * @param isDefault indicates if this is the default value of the selection (default is <code>false</code>) 145 */ 146 public CmsSelectWidgetOption(String value, boolean isDefault) { 147 148 this(value, isDefault, null, null); 149 } 150 151 /** 152 * Creates a new select option form the given values.<p> 153 * 154 * @param value the value of this select option 155 * @param isDefault indicates if this is the default value of the selection (default is <code>false</code>) 156 * @param optionText the (optional) display text of this select option 157 */ 158 public CmsSelectWidgetOption(String value, boolean isDefault, String optionText) { 159 160 this(value, isDefault, optionText, null); 161 } 162 163 /** 164 * Creates a new select option form the given values.<p> 165 * 166 * @param value the value of this select option 167 * @param isDefault indicates if this is the default value of the selection (default is <code>false</code>) 168 * @param optionText the (optional) display text of this select option 169 * @param helpText the (optional) help text of this select option 170 */ 171 public CmsSelectWidgetOption(String value, boolean isDefault, String optionText, String helpText) { 172 173 m_default = isDefault; 174 m_value = value; 175 m_option = optionText; 176 m_help = helpText; 177 } 178 179 /** 180 * Returns a select widget configuration String created from the given list of select options.<p> 181 * 182 * If an element found in the given list is not of type 183 * <code>{@link CmsSelectWidgetOption}</code>, it is ignored.<p> 184 * 185 * @param options the list of select options to create the configuration String for 186 * 187 * @return a select widget configuration String created from the given list of select options 188 */ 189 public static String createConfigurationString(List<CmsSelectWidgetOption> options) { 190 191 if ((options == null) || (options.size() == 0)) { 192 return ""; 193 } 194 StringBuffer result = new StringBuffer(256); 195 boolean first = true; 196 for (int i = 0; i < options.size(); i++) { 197 CmsSelectWidgetOption o = options.get(i); 198 if (!first) { 199 result.append(CmsSelectWidgetOption.INPUT_DELIMITER); 200 } else { 201 first = false; 202 } 203 result.append(o.toString().replace("|", "\\|")); 204 } 205 return result.toString(); 206 } 207 208 /** 209 * Returns the default option from the given list of select options, 210 * or <code>null</code> in case there is no default option in the given list.<p> 211 * 212 * If an element found in the given list is not of type 213 * <code>{@link CmsSelectWidgetOption}</code>, this is ignored.<p> 214 * 215 * @param options the list of select options to get the default from 216 * 217 * @return the default option from the given list of select options, or <code>null</code> in case there is no default option 218 */ 219 public static CmsSelectWidgetOption getDefaultOption(List<CmsSelectWidgetOption> options) { 220 221 if ((options == null) || (options.size() == 0)) { 222 return null; 223 } 224 for (int i = 0; i < options.size(); i++) { 225 Object o = options.get(i); 226 if (o instanceof CmsSelectWidgetOption) { 227 CmsSelectWidgetOption option = (CmsSelectWidgetOption)o; 228 if (option.isDefault()) { 229 return option; 230 } 231 } 232 } 233 return null; 234 } 235 236 /** 237 * Returns a list of default options from the given list of select options.<p> 238 * 239 * If an element found in the given list is not of type 240 * <code>{@link CmsSelectWidgetOption}</code>, this is ignored.<p> 241 * 242 * @param options the list of select options to get the default from 243 * 244 * @return a list of <code>{@link CmsSelectWidgetOption}</code> objects 245 */ 246 public static List<CmsSelectWidgetOption> getDefaultOptions(List<CmsSelectWidgetOption> options) { 247 248 List<CmsSelectWidgetOption> defaults = new ArrayList<CmsSelectWidgetOption>(); 249 if ((options == null) || (options.size() == 0)) { 250 return defaults; 251 } 252 for (int i = 0; i < options.size(); i++) { 253 Object o = options.get(i); 254 if (o instanceof CmsSelectWidgetOption) { 255 CmsSelectWidgetOption option = (CmsSelectWidgetOption)o; 256 if (option.isDefault()) { 257 defaults.add(option); 258 } 259 } 260 } 261 return defaults; 262 } 263 264 /** 265 * Parses a widget configuration String for select option values.<p> 266 * 267 * If the input is <code>null</code> or empty, a <code>{@link Collections#EMPTY_LIST}</code> 268 * is returned.<p> 269 * 270 * Please note: No exception is thrown in case the input is malformed, all malformed entries are silently ignored.<p> 271 * 272 * @param input the widget input string to parse 273 * 274 * @return a List of <code>{@link CmsSelectWidgetOption}</code> elements 275 */ 276 public static List<CmsSelectWidgetOption> parseOptions(String input) { 277 278 if (CmsStringUtil.isEmptyOrWhitespaceOnly(input)) { 279 // default result for empty input 280 return Collections.emptyList(); 281 } 282 283 // cut along the delimiter 284 String[] parts = splitOptions(input); 285 List<CmsSelectWidgetOption> result = new ArrayList<CmsSelectWidgetOption>(parts.length); 286 287 // indicates if a default of 'true' was already set in this result list 288 boolean foundDefault = false; 289 290 for (int i = 0; i < parts.length; i++) { 291 292 String part = parts[i].trim(); 293 if (part.length() == 0) { 294 // skip empty parts 295 continue; 296 } 297 298 try { 299 part = part.replace("\\|", "|"); 300 String value = null; 301 String option = null; 302 String help = null; 303 boolean isDefault = false; 304 305 int posValue = part.indexOf(KEY_VALUE); 306 int posDefault = part.indexOf(KEY_DEFAULT); 307 int posOption = part.indexOf(KEY_OPTION); 308 int posHelp = part.indexOf(KEY_HELP); 309 310 boolean shortValue = false; 311 if (posValue < 0) { 312 // shortcut syntax, value key must be at first position 313 if ((posDefault == 0) || (posOption == 0) || (posHelp == 0)) { 314 // malformed part - no value given 315 throw new CmsWidgetException( 316 Messages.get().container(Messages.ERR_MALFORMED_SELECT_OPTIONS_1, input)); 317 } 318 posValue = 0; 319 shortValue = true; 320 } 321 322 // a 'value' must be always present 323 int end = part.length(); 324 // check where the 'value' ends 325 if (posHelp > posValue) { 326 end = posHelp; 327 } 328 if ((posDefault > posValue) && (posDefault < end)) { 329 end = posDefault; 330 } 331 if ((posOption > posValue) && (posOption < end)) { 332 end = posOption; 333 } 334 if (shortValue) { 335 // no explicit setting using the key, value must be at the first position 336 value = part.substring(0, end).trim(); 337 } else { 338 value = part.substring((posValue + KEY_VALUE.length()) - 1, end).trim(); 339 // cut of trailing ' 340 value = value.substring(1, value.length() - 1); 341 } 342 343 boolean shortOption = false; 344 // check if the option is appended using the ':' shortcut 345 if ((shortValue) && (posOption < 0)) { 346 int pos = value.indexOf(OPTION_DELIMITER); 347 if (pos >= 0) { 348 // shortcut syntax is used 349 posOption = pos; 350 shortOption = true; 351 value = value.substring(0, pos); 352 } 353 } 354 355 if (posDefault >= 0) { 356 // there was an explicit 'default' setting using the key, check where it ends 357 end = part.length(); 358 if (posHelp > posDefault) { 359 end = posHelp; 360 } 361 if ((posOption > posDefault) && (posOption < end)) { 362 end = posOption; 363 } 364 if ((posValue > posDefault) && (posValue < end)) { 365 end = posValue; 366 } 367 String sub = part.substring(posDefault + KEY_DEFAULT.length(), end).trim(); 368 // cut of trailing ' 369 sub = sub.substring(0, sub.length() - 1); 370 isDefault = Boolean.valueOf(sub).booleanValue(); 371 } else { 372 // check for shortcut syntax, value must end with a '*' 373 if ((value != null) && (value.charAt(value.length() - 1) == DEFAULT_MARKER)) { 374 isDefault = true; 375 value = value.substring(0, value.length() - 1); 376 } 377 } 378 379 if (posOption >= 0) { 380 // an 'option' setting is available, check where it ends 381 end = part.length(); 382 if (posHelp > posOption) { 383 end = posHelp; 384 } 385 if ((posDefault > posOption) && (posDefault < end)) { 386 end = posDefault; 387 } 388 if ((posValue > posOption) && (posValue < end)) { 389 end = posValue; 390 } 391 if (shortOption) { 392 // shortcut syntax used for option with ':' appended to value 393 option = part.substring(posOption + 1, end).trim(); 394 } else { 395 option = part.substring((posOption + KEY_OPTION.length()) - 1, end).trim(); 396 // cut of trailing ' 397 option = option.substring(1, option.length() - 1); 398 } 399 } 400 401 if (posHelp >= 0) { 402 // a 'help' setting is available, check where it ends 403 end = part.length(); 404 if (posOption > posHelp) { 405 end = posOption; 406 } 407 if ((posDefault > posHelp) && (posDefault < end)) { 408 end = posDefault; 409 } 410 if ((posValue > posHelp) && (posValue < end)) { 411 end = posValue; 412 } 413 help = part.substring((posHelp + KEY_HELP.length()) - 1, end).trim(); 414 // cut of trailing ' 415 help = help.substring(1, help.length() - 1); 416 } 417 418 // check if there was already a 'true' default, if so all other entries are 'false' 419 if (foundDefault) { 420 isDefault = false; 421 } else if (isDefault) { 422 foundDefault = true; 423 } 424 425 result.add(new CmsSelectWidgetOption(value, isDefault, option, help)); 426 427 } catch (Exception e) { 428 if (LOG.isInfoEnabled()) { 429 LOG.info(Messages.get().getBundle().key(Messages.ERR_MALFORMED_SELECT_OPTIONS_1, input)); 430 } 431 } 432 } 433 434 return result; 435 } 436 437 /** 438 * Splits the options string at every unescaped input delimiter, i.e., every unescaped "|". 439 * @param input the options string 440 * @return the array with the various options 441 */ 442 public static String[] splitOptions(String input) { 443 444 //Note that we use a regex matching all "|" characters not prefixed by "\" 445 //Since we define a regex for matching, the input delimiter "|" needs to be escaped, as well as "\", 446 //which is even double-escaped - one escaping is due to the String, one due to the regex. 447 return input.split("(?<!\\\\)\\" + INPUT_DELIMITER); 448 } 449 450 /** 451 * @see java.lang.Object#equals(java.lang.Object) 452 */ 453 @Override 454 public boolean equals(Object obj) { 455 456 if (!(obj instanceof CmsSelectWidgetOption)) { 457 return false; 458 } 459 CmsSelectWidgetOption other = (CmsSelectWidgetOption)obj; 460 if (m_default != other.m_default) { 461 return false; 462 } 463 if (m_value == null) { 464 if (other.m_value != null) { 465 return false; 466 } 467 } else if (!m_value.equals(other.m_value)) { 468 return false; 469 } 470 if (m_option == null) { 471 if (other.m_option != null) { 472 return false; 473 } 474 } else if (!m_option.equals(other.m_option)) { 475 return false; 476 } 477 if (m_help == null) { 478 if (other.m_help != null) { 479 return false; 480 } 481 } else if (!m_help.equals(other.m_help)) { 482 return false; 483 } 484 return true; 485 } 486 487 /** 488 * Returns the (optional) help text of this select option.<p> 489 * 490 * @return the (optional) help text of this select option 491 */ 492 public String getHelp() { 493 494 return m_help; 495 } 496 497 /** 498 * Returns the option text of this select option.<p> 499 * 500 * If this has not been set, the result of <code>{@link #getValue()}</code> is returned, 501 * there will always be a result other than <code>null</code> returned.<p> 502 * 503 * @return the option text of this select option 504 */ 505 public String getOption() { 506 507 if (m_option == null) { 508 return getValue(); 509 } 510 return m_option; 511 } 512 513 /** 514 * Returns the value of this select option.<p> 515 * 516 * @return the value of this select option 517 */ 518 public String getValue() { 519 520 return m_value; 521 } 522 523 /** 524 * @see java.lang.Object#hashCode() 525 */ 526 @Override 527 public int hashCode() { 528 529 if (m_hashcode == 0) { 530 StringBuffer hash = new StringBuffer(128); 531 hash.append(m_value); 532 hash.append('|'); 533 hash.append(m_default); 534 hash.append('|'); 535 hash.append(m_option); 536 hash.append('|'); 537 hash.append(m_help); 538 m_hashcode = hash.toString().hashCode(); 539 } 540 return m_hashcode; 541 } 542 543 /** 544 * Returns <code>true</code> if this is the default value of the selection.<p> 545 * 546 * @return <code>true</code> if this is the default value of the selection 547 */ 548 public boolean isDefault() { 549 550 return m_default; 551 } 552 553 /** 554 * @see java.lang.Object#toString() 555 */ 556 @Override 557 public String toString() { 558 559 StringBuffer result = new StringBuffer(128); 560 561 result.append(KEY_VALUE); 562 result.append(m_value); 563 result.append('\''); 564 if (m_default) { 565 result.append(' '); 566 result.append(KEY_DEFAULT); 567 result.append(m_default); 568 result.append('\''); 569 } 570 if (m_option != null) { 571 result.append(' '); 572 result.append(KEY_OPTION); 573 result.append(m_option); 574 result.append('\''); 575 } 576 if (m_help != null) { 577 result.append(' '); 578 result.append(KEY_HELP); 579 result.append(m_help); 580 result.append('\''); 581 } 582 return result.toString(); 583 } 584}