View Javadoc
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 }