1 /*
2 * $Id$
3 *
4 * Licensed to the Apache Software Foundation (ASF) under one
5 * or more contributor license agreements. See the NOTICE file
6 * distributed with this work for additional information
7 * regarding copyright ownership. The ASF licenses this file
8 * to you under the Apache License, Version 2.0 (the
9 * "License"); you may not use this file except in compliance
10 * with the License. You may obtain a copy of the License at
11 *
12 * http://www.apache.org/licenses/LICENSE-2.0
13 *
14 * Unless required by applicable law or agreed to in writing,
15 * software distributed under the License is distributed on an
16 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17 * KIND, either express or implied. See the License for the
18 * specific language governing permissions and limitations
19 * under the License.
20 */
21 package org.apache.struts.action;
22
23 import java.io.Serializable;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.Comparator;
28 import java.util.HashMap;
29 import java.util.Iterator;
30 import java.util.List;
31
32 /**
33 * <p>A class that encapsulates messages. Messages can be either global or
34 * they are specific to a particular bean property.</p>
35 *
36 * <p>Each individual message is described by an <code>ActionMessage</code>
37 * object, which contains a message key (to be looked up in an appropriate
38 * message resources database), and up to four placeholder arguments used for
39 * parametric substitution in the resulting message.</p>
40 *
41 * <p><strong>IMPLEMENTATION NOTE</strong> - It is assumed that these objects
42 * are created and manipulated only within the context of a single thread.
43 * Therefore, no synchronization is required for access to internal
44 * collections.</p>
45 *
46 * @version $Rev$ $Date: 2005-08-26 21:58:39 -0400 (Fri, 26 Aug 2005)
47 * $
48 * @since Struts 1.1
49 */
50 public class ActionMessages implements Serializable {
51 private static final long serialVersionUID = -4363118850236216381L;
52
53 /**
54 * <p>Compares ActionMessageItem objects.</p>
55 */
56 private static final Comparator<ActionMessageItem> ACTION_ITEM_COMPARATOR =
57 (ami1, ami2) -> ami1.getOrder() - ami2.getOrder();
58
59 // ----------------------------------------------------- Manifest Constants
60
61 /**
62 * <p>The "property name" marker to use for global messages, as opposed to
63 * those related to a specific property.</p>
64 */
65 public static final String GLOBAL_MESSAGE =
66 "org.apache.struts.action.GLOBAL_MESSAGE";
67
68 // ----------------------------------------------------- Instance Variables
69
70 /**
71 * <p>Have the messages been retrieved from this object?</p>
72 *
73 * <p>The controller uses this property to determine if session-scoped
74 * messages can be removed.</p>
75 *
76 * @since Struts 1.2
77 */
78 protected boolean accessed = false;
79
80 /**
81 * <p>The accumulated set of <code>ActionMessage</code> objects
82 * (represented as an ArrayList) for each property, keyed by property
83 * name.</p>
84 */
85 protected HashMap<String, ActionMessageItem> messages = new HashMap<>();
86
87 /**
88 * <p>The current number of the property/key being added. This is used to
89 * maintain the order messages are added.</p>
90 */
91 protected int iCount = 0;
92
93 // --------------------------------------------------------- Public Methods
94
95 /**
96 * <p>Create an empty <code>ActionMessages</code> object.</p>
97 */
98 public ActionMessages() {
99 super();
100 }
101
102 /**
103 * <p>Create an <code>ActionMessages</code> object initialized with the
104 * given messages.</p>
105 *
106 * @param messages The messages to be initially added to this object. This
107 * parameter can be <code>null</code>.
108 * @since Struts 1.1
109 */
110 public ActionMessages(ActionMessages messages) {
111 super();
112 this.add(messages);
113 }
114
115 /**
116 * <p>Add a message to the set of messages for the specified property. An
117 * order of the property/key is maintained based on the initial addition
118 * of the property/key.</p>
119 *
120 * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
121 * @param message The message to be added
122 */
123 public void add(String property, ActionMessage message) {
124 ActionMessageItem item = messages.get(property);
125 List<ActionMessage> list;
126
127 if (item == null) {
128 list = new ArrayList<>();
129 item = new ActionMessageItem(list, iCount++, property);
130
131 messages.put(property, item);
132 } else {
133 list = item.getList();
134 }
135
136 list.add(message);
137 }
138
139 /**
140 * <p>Adds the messages from the given <code>ActionMessages</code> object
141 * to this set of messages. The messages are added in the order they are
142 * returned from the <code>properties</code> method. If a message's
143 * property is already in the current <code>ActionMessages</code> object,
144 * it is added to the end of the list for that property. If a message's
145 * property is not in the current list it is added to the end of the
146 * properties.</p>
147 *
148 * @param actionMessages The <code>ActionMessages</code> object to be
149 * added. This parameter can be <code>null</code>.
150 * @since Struts 1.1
151 */
152 public void add(ActionMessages actionMessages) {
153 if (actionMessages == null) {
154 return;
155 }
156
157 // loop over properties
158 Iterator<String> props = actionMessages.properties();
159
160 while (props.hasNext()) {
161 String property = props.next();
162
163 // loop over messages for each property
164 Iterator<ActionMessage> msgs = actionMessages.get(property);
165
166 while (msgs.hasNext()) {
167 this.add(property, msgs.next());
168 }
169 }
170 }
171
172 /**
173 * <p>Clear all messages recorded by this object.</p>
174 */
175 public void clear() {
176 messages.clear();
177 }
178
179 /**
180 * <p>Return <code>true</code> if there are no messages recorded in this
181 * collection, or <code>false</code> otherwise.</p>
182 *
183 * @return <code>true</code> if there are no messages recorded in this
184 * collection; <code>false</code> otherwise.
185 * @since Struts 1.1
186 */
187 public boolean isEmpty() {
188 return (messages.isEmpty());
189 }
190
191 /**
192 * <p>Return the set of all recorded messages, without distinction by
193 * which property the messages are associated with. If there are no
194 * messages recorded, an empty enumeration is returned.</p>
195 *
196 * @return An iterator over the messages for all properties.
197 */
198 public Iterator<ActionMessage> get() {
199 this.accessed = true;
200
201 if (messages.isEmpty()) {
202 return Collections.emptyListIterator();
203 }
204
205 ActionMessageItem[] actionItems = messages.values().toArray(new ActionMessageItem[0]);
206
207 // Sort ActionMessageItems based on the initial order the
208 // property/key was added to ActionMessages.
209 Arrays.sort(actionItems, ACTION_ITEM_COMPARATOR);
210
211 ArrayList<ActionMessage> results = new ArrayList<>();
212 for (ActionMessageItem ami : actionItems) {
213 results.addAll(ami.getList());
214 }
215
216 return results.iterator();
217 }
218
219 /**
220 * <p>Return the set of messages related to a specific property. If there
221 * are no such messages, an empty enumeration is returned.</p>
222 *
223 * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
224 * @return An iterator over the messages for the specified property.
225 */
226 public Iterator<ActionMessage> get(String property) {
227 this.accessed = true;
228
229 ActionMessageItem item = messages.get(property);
230
231 if (item == null) {
232 return (Collections.emptyListIterator());
233 } else {
234 return (item.getList().iterator());
235 }
236 }
237
238 /**
239 * <p>Returns <code>true</code> if the <code>get()</code> or
240 * <code>get(String)</code> methods are called.</p>
241 *
242 * @return <code>true</code> if the messages have been accessed one or
243 * more times.
244 * @since Struts 1.2
245 */
246 public boolean isAccessed() {
247 return this.accessed;
248 }
249
250 /**
251 * <p>Return the set of property names for which at least one message has
252 * been recorded. If there are no messages, an empty <code>Iterator</code>
253 * is returned. If you have recorded global messages, the
254 * <code>String</code> value of <code>ActionMessages.GLOBAL_MESSAGE</code>
255 * will be one of the returned property names.</p>
256 *
257 * @return An iterator over the property names for which messages exist.
258 */
259 public Iterator<String> properties() {
260 if (messages.isEmpty()) {
261 return Collections.emptyListIterator();
262 }
263
264 ActionMessageItem[] actionItems = messages.values().toArray(new ActionMessageItem[0]);
265
266 // Sort ActionMessageItems based on the initial order the
267 // property/key was added to ActionMessages.
268 Arrays.sort(actionItems, ACTION_ITEM_COMPARATOR);
269
270 ArrayList<String> results = new ArrayList<>(actionItems.length);
271 for (ActionMessageItem ami : actionItems) {
272 results.add(ami.getProperty());
273 }
274
275 return results.iterator();
276 }
277
278 /**
279 * <p>Return the number of messages recorded for all properties (including
280 * global messages). <strong>NOTE</strong> - it is more efficient to call
281 * <code>isEmpty</code> if all you care about is whether or not there are
282 * any messages at all.</p>
283 *
284 * @return The number of messages associated with all properties.
285 */
286 public int size() {
287 int total = 0;
288
289 for (ActionMessageItem ami : messages.values()) {
290 total += ami.getList().size();
291 }
292
293 return (total);
294 }
295
296 /**
297 * <p>Return the number of messages associated with the specified
298 * property. </p>
299 *
300 * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
301 * @return The number of messages associated with the property.
302 */
303 public int size(String property) {
304 ActionMessageItem item = messages.get(property);
305
306 return (item == null) ? 0 : item.getList().size();
307 }
308
309 /**
310 * <p>Returns a String representation of this ActionMessages' property
311 * name=message list mapping.</p>
312 *
313 * @return String representation of the messages
314 * @see Object#toString()
315 */
316 public String toString() {
317 return this.messages.toString();
318 }
319
320 /**
321 * <p>This class is used to store a set of messages associated with a
322 * property/key and the position it was initially added to list.</p>
323 */
324 protected class ActionMessageItem implements Serializable {
325 private static final long serialVersionUID = 6569527708054508739L;
326
327 /**
328 * <p>The list of <code>ActionMessage</code>s.</p>
329 */
330 protected List<ActionMessage> list = null;
331
332 /**
333 * <p>The position in the list of messages.</p>
334 */
335 protected int iOrder = 0;
336
337 /**
338 * <p>The property associated with <code>ActionMessage</code>.</p>
339 */
340 protected String property = null;
341
342 /**
343 * <p>Construct an instance of this class.</p>
344 *
345 * @param list The list of ActionMessages.
346 * @param iOrder The position in the list of messages.
347 * @param property The property associated with ActionMessage.
348 */
349 public ActionMessageItem(List<ActionMessage> list, int iOrder, String property) {
350 this.list = list;
351 this.iOrder = iOrder;
352 this.property = property;
353 }
354
355 /**
356 * <p>Retrieve the list of messages associated with this item.</p>
357 *
358 * @return The list of messages associated with this item.
359 */
360 public List<ActionMessage> getList() {
361 return list;
362 }
363
364 /**
365 * <p>Set the list of messages associated with this item.</p>
366 *
367 * @param list The list of messages associated with this item.
368 */
369 public void setList(List<ActionMessage> list) {
370 this.list = list;
371 }
372
373 /**
374 * <p>Retrieve the position in the message list.</p>
375 *
376 * @return The position in the message list.
377 */
378 public int getOrder() {
379 return iOrder;
380 }
381
382 /**
383 * <p>Set the position in the message list.</p>
384 *
385 * @param iOrder The position in the message list.
386 */
387 public void setOrder(int iOrder) {
388 this.iOrder = iOrder;
389 }
390
391 /**
392 * <p>Retrieve the property associated with this item.</p>
393 *
394 * @return The property associated with this item.
395 */
396 public String getProperty() {
397 return property;
398 }
399
400 /**
401 * <p>Set the property associated with this item.</p>
402 *
403 * @param property The property associated with this item.
404 */
405 public void setProperty(String property) {
406 this.property = property;
407 }
408
409 /**
410 * <p>Construct a string representation of this object.</p>
411 *
412 * @return A string representation of this object.
413 */
414 public String toString() {
415 return this.list.toString();
416 }
417 }
418 }