001// -------------------------------------------------------------------------------- 002// Copyright 2002-2024 Echo Three, LLC 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// -------------------------------------------------------------------------------- 016 017/* ==================================================================== 018 * 019 * The Apache Software License, Version 1.1 020 * 021 * Copyright (c) 1999-2003 The Apache Software Foundation. All rights 022 * reserved. 023 * 024 * Redistribution and use in source and binary forms, with or without 025 * modification, are permitted provided that the following conditions 026 * are met: 027 * 028 * 1. Redistributions of source code must retain the above copyright 029 * notice, this list of conditions and the following disclaimer. 030 * 031 * 2. Redistributions in binary form must reproduce the above copyright 032 * notice, this list of conditions and the following disclaimer in 033 * the documentation and/or other materials provided with the 034 * distribution. 035 * 036 * 3. The end-user documentation included with the redistribution, if 037 * any, must include the following acknowlegement: 038 * "This product includes software developed by the 039 * Apache Software Foundation (http://www.apache.org/)." 040 * Alternately, this acknowlegement may appear in the software itself, 041 * if and wherever such third-party acknowlegements normally appear. 042 * 043 * 4. The names "The Jakarta Project", "Struts", and "Apache Software 044 * Foundation" must not be used to endorse or promote products derived 045 * from this software without prior written permission. For written 046 * permission, please contact apache@apache.org. 047 * 048 * 5. Products derived from this software may not be called "Apache" 049 * nor may "Apache" appear in their names without prior written 050 * permission of the Apache Group. 051 * 052 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED 053 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 054 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 055 * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR 056 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 057 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 058 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 059 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 060 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 061 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 062 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 063 * SUCH DAMAGE. 064 * ==================================================================== 065 * 066 * This software consists of voluntary contributions made by many 067 * individuals on behalf of the Apache Software Foundation. For more 068 * information on the Apache Software Foundation, please see 069 * <http://www.apache.org/>. 070 * 071 */ 072 073package com.echothree.util.common.message; 074 075import java.io.Serializable; 076import java.util.ArrayList; 077import java.util.Arrays; 078import java.util.Collections; 079import java.util.HashMap; 080import java.util.Iterator; 081import java.util.List; 082import java.util.Map; 083 084/** 085 * <p>A class that encapsulates messages. Messages can be either global 086 * or they are specific to a particular bean property.</p> 087 * 088 * <p>Each individual message is described by an <code>Message</code> 089 * object, which contains a message key (to be looked up in an appropriate 090 * message resources database), and up to four placeholder arguments used for 091 * parametric substitution in the resulting message.</p> 092 * 093 * <p><strong>IMPLEMENTATION NOTE</strong> - It is assumed that these objects 094 * are created and manipulated only within the context of a single thread. 095 * Therefore, no synchronization is required for access to internal 096 * collections.</p> 097 * 098 * @since Struts 1.1 099 */ 100 101public class Messages 102 implements Serializable { 103 104 // ----------------------------------------------------- Manifest Constants 105 106 /** 107 * The "property name" marker to use for BaseCommand messages, as opposed to 108 * those related to a specific property. 109 */ 110 public static final String EXECUTION_WARNING = "com.echothree.util.common.message.EXECUTION_WARNING"; 111 public static final String EXECUTION_ERROR = "com.echothree.util.common.message.EXECUTION_ERROR"; 112 public static final String SECURITY_MESSAGE = "com.echothree.util.common.message.SECURITY_MESSAGE"; 113 114 // ----------------------------------------------------- Instance Variables 115 116 /** 117 * The accumulated set of <code>Message</code> objects (represented 118 * as an ArrayList) for each property, keyed by property name. 119 */ 120 protected Map<String, MessageItem> messages = new HashMap<>(); 121 122 /** 123 * The current number of the property/key being added. This is used 124 * to maintain the order messages are added. 125 */ 126 protected int iCount = 0; 127 128 // --------------------------------------------------------- Public Methods 129 130 /** 131 * Create an empty <code>Messages</code> object. 132 */ 133 /** Creates a new instance of Messages */ 134 public Messages() { 135 super(); 136 } 137 138 /** 139 * Create an <code>Messages</code> object initialized with the given 140 * messages. 141 * 142 * @param messages The messages to be initially added to this object. 143 * This parameter can be <code>null</code>. 144 * @since Struts 1.1 145 */ 146 /** Creates a new instance of Messages */ 147 public Messages(Messages messages) { 148 super(); 149 this.add(messages); 150 } 151 152 /** 153 * Add a message to the set of messages for the specified property. An 154 * order of the property/key is maintained based on the initial addition 155 * of the property/key. 156 * 157 * @param property Property name (or Messages.GLOBAL_MESSAGE) 158 * @param message The message to be added 159 */ 160 public Messages add(String property, Message message) { 161 MessageItem item = messages.get(property); 162 Map<String, Message> hashMap = null; 163 164 if(item == null) { 165 hashMap = new HashMap<>(); 166 item = new MessageItem(hashMap, iCount++); 167 168 messages.put(property, item); 169 } else { 170 hashMap = item.getHashMap(); 171 } 172 173 hashMap.put(message.getKey(), message); 174 175 return this; 176 } 177 178 /** 179 * Adds the messages from the given <code>Messages</code> object to 180 * this set of messages. The messages are added in the order they are returned from 181 * the properties() method. If a message's property is already in the current 182 * <code>Messages</code> object it is added to the end of the list for that 183 * property. If a message's property is not in the current list it is added to the end 184 * of the properties. 185 * 186 * @param messages The <code>Messages</code> object to be added. 187 * This parameter can be <code>null</code>. 188 * @since Struts 1.1 189 */ 190 public Messages add(Messages messages) { 191 if(messages == null) { 192 return this; 193 } 194 // loop over properties 195 Iterator props = messages.properties(); 196 while(props.hasNext()) { 197 String property = (String) props.next(); 198 199 // loop over messages for each property 200 Iterator msgs = messages.get(property); 201 while(msgs.hasNext()) { 202 Message msg = (Message)msgs.next(); 203 this.add(property, msg); 204 } 205 } 206 207 return this; 208 } 209 210 /** 211 * Clear all messages recorded by this object. 212 */ 213 public void clear() { 214 messages.clear(); 215 } 216 217 /** 218 * Return <code>true</code> if there are no messages recorded 219 * in this collection, or <code>false</code> otherwise. 220 * @since Struts 1.1 221 */ 222 public boolean isEmpty(){ 223 return messages.isEmpty(); 224 } 225 226 /** 227 * Return the set of all recorded messages, without distinction 228 * by which property the messages are associated with. If there are 229 * no messages recorded, an empty enumeration is returned. 230 */ 231 public Iterator<Message> get() { 232 if(messages.isEmpty()) { 233 return Collections.<Message>emptyList().iterator(); 234 } 235 236 List<Message> results = new ArrayList<>(); 237 List<MessageItem> actionItems = new ArrayList<>(); 238 239 for(Iterator<MessageItem> i = messages.values().iterator(); i.hasNext();) { 240 actionItems.add(i.next()); 241 } 242 243 // Sort MessageItems based on the initial order the 244 // property/key was added to Messages. 245 Collections.sort(actionItems, (MessageItem o1, MessageItem o2) -> o1.getOrder() - o2.getOrder()); 246 247 actionItems.forEach((ami) -> { 248 for(Iterator<Message> messages = ami.getHashMap().values().iterator(); messages.hasNext();) { 249 results.add(messages.next()); 250 } 251 }); 252 253 return results.iterator(); 254 } 255 256 /** 257 * Return the set of messages related to a specific property. 258 * If there are no such messages, an empty enumeration is returned. 259 * 260 * @param property Property name (or Messages.GLOBAL_MESSAGE) 261 */ 262 public Iterator<Message> get(String property) { 263 MessageItem item = (MessageItem) messages.get(property); 264 265 if(item == null) { 266 return Collections.<Message>emptyList().iterator(); 267 } else { 268 return item.getHashMap().values().iterator(); 269 } 270 } 271 272 public boolean containsKey(String property, String key) { 273 MessageItem item = (MessageItem)messages.get(property); 274 boolean result; 275 276 if(item != null) { 277 result = item.getHashMap().containsKey(key); 278 } else { 279 result = false; 280 } 281 282 return result; 283 } 284 285 public boolean containsKeys(String property, String... keys) { 286 MessageItem item = (MessageItem)messages.get(property); 287 boolean result = false; 288 289 if(item != null) { 290 for(String key : Arrays.asList(keys)) { 291 result = item.getHashMap().containsKey(key); 292 if(result) { 293 break; 294 } 295 } 296 } 297 298 return result; 299 } 300 301 /** 302 * Return the set of property names for which at least one message has 303 * been recorded. If there are no messages, an empty Iterator is returned. 304 * If you have recorded global messages, the String value of 305 * <code>Messages.GLOBAL_MESSAGE</code> will be one of the returned 306 * property names. 307 */ 308 public Iterator<String> properties() { 309 return messages.keySet().iterator(); 310 } 311 312 /** 313 * Return the number of messages recorded for all properties (including 314 * global messages). <strong>NOTE</strong> - it is more efficient to call 315 * <code>empty()</code> if all you care about is whether or not there are 316 * any messages at all. 317 */ 318 public int size() { 319 int total = 0; 320 321 for(Iterator i = messages.values().iterator(); i.hasNext();) { 322 MessageItem ami = (MessageItem)i.next(); 323 total += ami.getHashMap().size(); 324 } 325 326 return total; 327 } 328 329 /** 330 * Return the number of messages associated with the specified property. 331 * 332 * @param property Property name (or Messages.GLOBAL_MESSAGE) 333 */ 334 public int size(String property) { 335 MessageItem ami = (MessageItem) messages.get(property); 336 337 if(ami == null) { 338 return 0; 339 } else { 340 return ami.getHashMap().size(); 341 } 342 } 343 344 /** 345 * This class is used to store a set of messages associated with a 346 * property/key and the position it was initially added to list. 347 */ 348 protected static class MessageItem 349 implements Serializable { 350 351 /** 352 * The list of <code>Message</code>s. 353 */ 354 protected Map<String, Message> hashMap = null; 355 356 /** 357 * The position in the list of messages. 358 */ 359 protected int iOrder = 0; 360 361 public MessageItem(Map<String, Message> hashMap, int iOrder) { 362 this.hashMap = hashMap; 363 this.iOrder = iOrder; 364 } 365 366 public Map<String, Message> getHashMap() { 367 return hashMap; 368 } 369 370 public void setHashMap(Map<String, Message> hashMap) { 371 this.hashMap = hashMap; 372 } 373 374 public int getOrder() { 375 return iOrder; 376 } 377 378 public void setOrder(int iOrder) { 379 this.iOrder = iOrder; 380 } 381 382 /** 383 * Converts to a string representing the data contained within this set of MessageItem. 384 */ 385 @Override 386 public String toString() { 387 return new StringBuilder().append("{ hashMap = ").append(hashMap).append(", iOrder = ").append(iOrder).append(" }").toString(); 388 } 389 390 } 391 392 /** 393 * Converts to a string representing the data contained within this set of Messages. 394 */ 395 @Override 396 public String toString() { 397 return new StringBuilder().append("{ messages = ").append(messages).append(", iCount = ").append(iCount).append(" }").toString(); 398 } 399 400}