001: /*
002: * $Id: ActionMessages.java 471754 2006-11-06 14:55:09Z husted $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts.action;
022:
023: import java.io.Serializable;
024:
025: import java.util.ArrayList;
026: import java.util.Collections;
027: import java.util.Comparator;
028: import java.util.HashMap;
029: import java.util.Iterator;
030: import java.util.List;
031:
032: /**
033: * <p>A class that encapsulates messages. Messages can be either global or
034: * they are specific to a particular bean property.</p>
035: *
036: * <p>Each individual message is described by an <code>ActionMessage</code>
037: * object, which contains a message key (to be looked up in an appropriate
038: * message resources database), and up to four placeholder arguments used for
039: * parametric substitution in the resulting message.</p>
040: *
041: * <p><strong>IMPLEMENTATION NOTE</strong> - It is assumed that these objects
042: * are created and manipulated only within the context of a single thread.
043: * Therefore, no synchronization is required for access to internal
044: * collections.</p>
045: *
046: * @version $Rev: 471754 $ $Date: 2005-08-26 21:58:39 -0400 (Fri, 26 Aug 2005)
047: * $
048: * @since Struts 1.1
049: */
050: public class ActionMessages implements Serializable {
051: /**
052: * <p>Compares ActionMessageItem objects.</p>
053: */
054: private static final Comparator ACTION_ITEM_COMPARATOR = new Comparator() {
055: public int compare(Object o1, Object o2) {
056: return ((ActionMessageItem) o1).getOrder()
057: - ((ActionMessageItem) o2).getOrder();
058: }
059: };
060:
061: // ----------------------------------------------------- Manifest Constants
062:
063: /**
064: * <p>The "property name" marker to use for global messages, as opposed to
065: * those related to a specific property.</p>
066: */
067: public static final String GLOBAL_MESSAGE = "org.apache.struts.action.GLOBAL_MESSAGE";
068:
069: // ----------------------------------------------------- Instance Variables
070:
071: /**
072: * <p>Have the messages been retrieved from this object?</p>
073: *
074: * <p>The controller uses this property to determine if session-scoped
075: * messages can be removed.</p>
076: *
077: * @since Struts 1.2
078: */
079: protected boolean accessed = false;
080:
081: /**
082: * <p>The accumulated set of <code>ActionMessage</code> objects
083: * (represented as an ArrayList) for each property, keyed by property
084: * name.</p>
085: */
086: protected HashMap messages = new HashMap();
087:
088: /**
089: * <p>The current number of the property/key being added. This is used to
090: * maintain the order messages are added.</p>
091: */
092: protected int iCount = 0;
093:
094: // --------------------------------------------------------- Public Methods
095:
096: /**
097: * <p>Create an empty <code>ActionMessages</code> object.</p>
098: */
099: public ActionMessages() {
100: super ();
101: }
102:
103: /**
104: * <p>Create an <code>ActionMessages</code> object initialized with the
105: * given messages.</p>
106: *
107: * @param messages The messages to be initially added to this object. This
108: * parameter can be <code>null</code>.
109: * @since Struts 1.1
110: */
111: public ActionMessages(ActionMessages messages) {
112: super ();
113: this .add(messages);
114: }
115:
116: /**
117: * <p>Add a message to the set of messages for the specified property. An
118: * order of the property/key is maintained based on the initial addition
119: * of the property/key.</p>
120: *
121: * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
122: * @param message The message to be added
123: */
124: public void add(String property, ActionMessage message) {
125: ActionMessageItem item = (ActionMessageItem) messages
126: .get(property);
127: List list;
128:
129: if (item == null) {
130: list = new ArrayList();
131: item = new ActionMessageItem(list, iCount++, property);
132:
133: messages.put(property, item);
134: } else {
135: list = item.getList();
136: }
137:
138: list.add(message);
139: }
140:
141: /**
142: * <p>Adds the messages from the given <code>ActionMessages</code> object
143: * to this set of messages. The messages are added in the order they are
144: * returned from the <code>properties</code> method. If a message's
145: * property is already in the current <code>ActionMessages</code> object,
146: * it is added to the end of the list for that property. If a message's
147: * property is not in the current list it is added to the end of the
148: * properties.</p>
149: *
150: * @param actionMessages The <code>ActionMessages</code> object to be
151: * added. This parameter can be <code>null</code>.
152: * @since Struts 1.1
153: */
154: public void add(ActionMessages actionMessages) {
155: if (actionMessages == null) {
156: return;
157: }
158:
159: // loop over properties
160: Iterator props = actionMessages.properties();
161:
162: while (props.hasNext()) {
163: String property = (String) props.next();
164:
165: // loop over messages for each property
166: Iterator msgs = actionMessages.get(property);
167:
168: while (msgs.hasNext()) {
169: ActionMessage msg = (ActionMessage) msgs.next();
170:
171: this .add(property, msg);
172: }
173: }
174: }
175:
176: /**
177: * <p>Clear all messages recorded by this object.</p>
178: */
179: public void clear() {
180: messages.clear();
181: }
182:
183: /**
184: * <p>Return <code>true</code> if there are no messages recorded in this
185: * collection, or <code>false</code> otherwise.</p>
186: *
187: * @return <code>true</code> if there are no messages recorded in this
188: * collection; <code>false</code> otherwise.
189: * @since Struts 1.1
190: */
191: public boolean isEmpty() {
192: return (messages.isEmpty());
193: }
194:
195: /**
196: * <p>Return the set of all recorded messages, without distinction by
197: * which property the messages are associated with. If there are no
198: * messages recorded, an empty enumeration is returned.</p>
199: *
200: * @return An iterator over the messages for all properties.
201: */
202: public Iterator get() {
203: this .accessed = true;
204:
205: if (messages.isEmpty()) {
206: return Collections.EMPTY_LIST.iterator();
207: }
208:
209: ArrayList results = new ArrayList();
210: ArrayList actionItems = new ArrayList();
211:
212: for (Iterator i = messages.values().iterator(); i.hasNext();) {
213: actionItems.add(i.next());
214: }
215:
216: // Sort ActionMessageItems based on the initial order the
217: // property/key was added to ActionMessages.
218: Collections.sort(actionItems, ACTION_ITEM_COMPARATOR);
219:
220: for (Iterator i = actionItems.iterator(); i.hasNext();) {
221: ActionMessageItem ami = (ActionMessageItem) i.next();
222:
223: for (Iterator msgsIter = ami.getList().iterator(); msgsIter
224: .hasNext();) {
225: results.add(msgsIter.next());
226: }
227: }
228:
229: return results.iterator();
230: }
231:
232: /**
233: * <p>Return the set of messages related to a specific property. If there
234: * are no such messages, an empty enumeration is returned.</p>
235: *
236: * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
237: * @return An iterator over the messages for the specified property.
238: */
239: public Iterator get(String property) {
240: this .accessed = true;
241:
242: ActionMessageItem item = (ActionMessageItem) messages
243: .get(property);
244:
245: if (item == null) {
246: return (Collections.EMPTY_LIST.iterator());
247: } else {
248: return (item.getList().iterator());
249: }
250: }
251:
252: /**
253: * <p>Returns <code>true</code> if the <code>get()</code> or
254: * <code>get(String)</code> methods are called.</p>
255: *
256: * @return <code>true</code> if the messages have been accessed one or
257: * more times.
258: * @since Struts 1.2
259: */
260: public boolean isAccessed() {
261: return this .accessed;
262: }
263:
264: /**
265: * <p>Return the set of property names for which at least one message has
266: * been recorded. If there are no messages, an empty <code>Iterator</code>
267: * is returned. If you have recorded global messages, the
268: * <code>String</code> value of <code>ActionMessages.GLOBAL_MESSAGE</code>
269: * will be one of the returned property names.</p>
270: *
271: * @return An iterator over the property names for which messages exist.
272: */
273: public Iterator properties() {
274: if (messages.isEmpty()) {
275: return Collections.EMPTY_LIST.iterator();
276: }
277:
278: ArrayList results = new ArrayList();
279: ArrayList actionItems = new ArrayList();
280:
281: for (Iterator i = messages.values().iterator(); i.hasNext();) {
282: actionItems.add(i.next());
283: }
284:
285: // Sort ActionMessageItems based on the initial order the
286: // property/key was added to ActionMessages.
287: Collections.sort(actionItems, ACTION_ITEM_COMPARATOR);
288:
289: for (Iterator i = actionItems.iterator(); i.hasNext();) {
290: ActionMessageItem ami = (ActionMessageItem) i.next();
291:
292: results.add(ami.getProperty());
293: }
294:
295: return results.iterator();
296: }
297:
298: /**
299: * <p>Return the number of messages recorded for all properties (including
300: * global messages). <strong>NOTE</strong> - it is more efficient to call
301: * <code>isEmpty</code> if all you care about is whether or not there are
302: * any messages at all.</p>
303: *
304: * @return The number of messages associated with all properties.
305: */
306: public int size() {
307: int total = 0;
308:
309: for (Iterator i = messages.values().iterator(); i.hasNext();) {
310: ActionMessageItem ami = (ActionMessageItem) i.next();
311:
312: total += ami.getList().size();
313: }
314:
315: return (total);
316: }
317:
318: /**
319: * <p>Return the number of messages associated with the specified
320: * property. </p>
321: *
322: * @param property Property name (or ActionMessages.GLOBAL_MESSAGE)
323: * @return The number of messages associated with the property.
324: */
325: public int size(String property) {
326: ActionMessageItem item = (ActionMessageItem) messages
327: .get(property);
328:
329: return (item == null) ? 0 : item.getList().size();
330: }
331:
332: /**
333: * <p>Returns a String representation of this ActionMessages' property
334: * name=message list mapping.</p>
335: *
336: * @return String representation of the messages
337: * @see Object#toString()
338: */
339: public String toString() {
340: return this .messages.toString();
341: }
342:
343: /**
344: * <p>This class is used to store a set of messages associated with a
345: * property/key and the position it was initially added to list.</p>
346: */
347: protected class ActionMessageItem implements Serializable {
348: /**
349: * <p>The list of <code>ActionMessage</code>s.</p>
350: */
351: protected List list = null;
352:
353: /**
354: * <p>The position in the list of messages.</p>
355: */
356: protected int iOrder = 0;
357:
358: /**
359: * <p>The property associated with <code>ActionMessage</code>.</p>
360: */
361: protected String property = null;
362:
363: /**
364: * <p>Construct an instance of this class.</p>
365: *
366: * @param list The list of ActionMessages.
367: * @param iOrder The position in the list of messages.
368: * @param property The property associated with ActionMessage.
369: */
370: public ActionMessageItem(List list, int iOrder, String property) {
371: this .list = list;
372: this .iOrder = iOrder;
373: this .property = property;
374: }
375:
376: /**
377: * <p>Retrieve the list of messages associated with this item.</p>
378: *
379: * @return The list of messages associated with this item.
380: */
381: public List getList() {
382: return list;
383: }
384:
385: /**
386: * <p>Set the list of messages associated with this item.</p>
387: *
388: * @param list The list of messages associated with this item.
389: */
390: public void setList(List list) {
391: this .list = list;
392: }
393:
394: /**
395: * <p>Retrieve the position in the message list.</p>
396: *
397: * @return The position in the message list.
398: */
399: public int getOrder() {
400: return iOrder;
401: }
402:
403: /**
404: * <p>Set the position in the message list.</p>
405: *
406: * @param iOrder The position in the message list.
407: */
408: public void setOrder(int iOrder) {
409: this .iOrder = iOrder;
410: }
411:
412: /**
413: * <p>Retrieve the property associated with this item.</p>
414: *
415: * @return The property associated with this item.
416: */
417: public String getProperty() {
418: return property;
419: }
420:
421: /**
422: * <p>Set the property associated with this item.</p>
423: *
424: * @param property The property associated with this item.
425: */
426: public void setProperty(String property) {
427: this .property = property;
428: }
429:
430: /**
431: * <p>Construct a string representation of this object.</p>
432: *
433: * @return A string representation of this object.
434: */
435: public String toString() {
436: return this.list.toString();
437: }
438: }
439: }
|