001: /*
002:
003: This software is OSI Certified Open Source Software.
004: OSI Certified is a certification mark of the Open Source Initiative.
005:
006: The license (Mozilla version 1.0) can be read at the MMBase site.
007: See http://www.MMBase.org/license
008:
009: */
010:
011: package org.mmbase.util.functions;
012:
013: import org.mmbase.util.Casting;
014: import java.util.*;
015: import org.mmbase.util.logging.*;
016:
017: /**
018: * This class implemements the Parameters interface.
019: * It provides a convenient way to create a List that allows the use of 'named parameters'.
020: * This List is therefore backed by a HashMap, but it behaves as a list. So if you set
021: * a parameter with a certain name, it always appears in the same location of the List.
022: * This List is modifiable but not resizeable. It is always the size of the definition array.
023: *
024: * @author Michiel Meeuwissen
025: * @since MMBase-1.7
026: * @version $Id: Parameters.java,v 1.39 2008/01/22 16:43:41 michiel Exp $
027: * @see Parameter
028: * @see #Parameters(Parameter[])
029: */
030:
031: public class Parameters extends AbstractList<Object> implements
032: java.io.Serializable {
033: private static final Logger log = Logging
034: .getLoggerInstance(Parameters.class);
035:
036: private static final long serialVersionUID = 1L;
037:
038: /**
039: * No need to bother for the functions with no parameters. This is a constant you could supply.
040: */
041: public static final Parameters VOID = new Parameters(Parameter
042: .emptyArray());
043:
044: /**
045: * The contents of this List are stored in this HashMap.
046: */
047: protected final Map<String, Object> backing;
048:
049: /**
050: * This array maps integers (position in array) to map keys, making it possible to implement
051: * List.
052: */
053: protected Parameter<Object>[] definition;
054:
055: /**
056: * If <code>true</code>, values are automatically cast to the right type (if possible) when set.
057: */
058: protected boolean autoCasting = false;
059:
060: private int fromIndex = 0;
061: protected int toIndex;
062:
063: /**
064: * Constructor, taking an Parameter[] array argument.
065: * The Parameter may also be Parameter.Wrapper
066: * (to implement overriding of functions). The idea is that these array arguments are defined
067: * as constants in the classes which define a function with variable arguments.
068: * <br />
069: * The Parameter[] array could e.g. be somewhere defined as a constant, like this:
070: * <pre>
071: * <code>
072: * public final static Parameter[] MYFUNCTION_PARAMETERS = {
073: * new Parameter("type", Integer.class),
074: * new Parameter("text", String.class),
075: * Parameter.CLOUD, // a predefined parameter
076: * new Parameter.Wrapper(OTHERFUNCTION_PARAMETERS) // a way to include another definition in this one
077: * };
078: * </code>
079: * </pre>
080: */
081: public Parameters(Parameter<?>... def) {
082: definition = Functions.define(def,
083: new ArrayList<Parameter<?>>()).toArray(
084: Parameter.emptyArray());
085: toIndex = definition.length;
086: if (log.isDebugEnabled()) {
087: log.debug("Found definition " + Arrays.asList(definition));
088: }
089: backing = new HashMap<String, Object>();
090: // fill with default values, and check for non-unique keys.
091: for (int i = fromIndex; i < toIndex; i++) {
092: if (backing.put(definition[i].getName(), definition[i]
093: .getDefaultValue()) != null) {
094: throw new IllegalArgumentException(
095: "Parameter keys not unique");
096: }
097:
098: }
099:
100: }
101:
102: /**
103: * If you happen to have a List of parameters, then you can wrap it into an Parameters with this constructor.
104: *
105: * @param values Collection with values. This Collection should have a predictable iteration order.
106: * @throws NullPointerException if definition is null
107: * @see #Parameters(Parameter[])
108: */
109: public Parameters(Parameter<?>[] def, Collection<?> values) {
110: this (def);
111: setAll(values);
112: }
113:
114: /**
115: * @since MMBase-1.9
116: */
117: public Parameters(Parameter<?>[] def, Object... values) {
118: this (def);
119: setAll(values);
120: }
121:
122: /**
123: * @since MMBase-1.9
124: */
125: public Parameters(Map<String, Object> backing) {
126: this .backing = backing;
127: toIndex = backing.size() - 1;
128: definition = null;
129: }
130:
131: /**
132: * @since MMBase-1.9
133: */
134: public Parameters(final List<Map.Entry<String, Object>> list) {
135: backing = new HashMap<String, Object>();
136: Set<String> myCollections = null;
137: for (Map.Entry<String, Object> entry : list) {
138: String key = entry.getKey();
139: Object value = entry.getValue();
140: Object prevValue = backing.put(key, value);
141: if (prevValue != null) {
142: List<Object> newValue;
143: if (myCollections == null) {
144: myCollections = new HashSet<String>();
145: }
146: if (myCollections.contains(key)) {
147: newValue = (ArrayList<Object>) prevValue;
148: } else {
149: myCollections.add(key);
150: newValue = new ArrayList<Object>();
151: if (prevValue instanceof Collection) {
152: newValue.addAll((Collection<?>) prevValue);
153: } else {
154: newValue.add(prevValue);
155: }
156: }
157: if (value instanceof Collection) {
158: newValue.addAll((Collection<?>) value);
159: } else {
160: newValue.add(value);
161: }
162: backing.put(key, newValue);
163: }
164: }
165: toIndex = backing.size() - 1;
166: definition = null;
167: }
168:
169: /**
170: * Used for nicer implemenation of subList (which we want to also be instanceof Parameters).
171: */
172: protected Parameters(Parameters params, int from, int to) {
173: backing = params.backing;
174: definition = params.definition;
175: fromIndex = from + params.fromIndex;
176: toIndex = to + params.fromIndex;
177: if (fromIndex < 0)
178: throw new IndexOutOfBoundsException("fromIndex < 0");
179: if (toIndex > definition.length)
180: throw new IndexOutOfBoundsException(
181: "toIndex greater than length of list");
182: if (fromIndex > toIndex)
183: throw new IndexOutOfBoundsException("fromIndex > toIndex");
184:
185: }
186:
187: protected final void checkDef() {
188: if (definition == null) {
189: definition = new Parameter[backing.size()];
190: int i = 0;
191: for (Map.Entry<String, Object> entry : backing.entrySet()) {
192: definition[i++] = new Parameter<Object>(entry);
193: }
194: }
195: }
196:
197: public String toString() {
198: StringBuilder buf = new StringBuilder("[");
199: checkDef();
200: for (int i = fromIndex; i < toIndex; i++) {
201: if (i > fromIndex)
202: buf.append(", ");
203: buf.append(definition[i]).append('=').append(get(i));
204: }
205: buf.append("]");
206: return buf.toString();
207: }
208:
209: public Class<?>[] toClassArray() {
210: Class<?>[] array = new Class[toIndex - fromIndex];
211: checkDef();
212: for (int i = fromIndex; i < toIndex; i++) {
213: array[i - fromIndex] = definition[i].getDataType()
214: .getTypeAsClass();
215: }
216: return array;
217: }
218:
219: /**
220: * Sets the 'auto casting' property (which on default is false)
221: * @param autocast the new value for autocast
222: * @see #isAutoCasting
223: */
224: public void setAutoCasting(boolean autocast) {
225: autoCasting = autocast;
226: }
227:
228: public Parameter<?>[] getDefinition() {
229: checkDef();
230: if (fromIndex > 0 || toIndex != definition.length - 1) {
231: return Arrays.asList(definition)
232: .subList(fromIndex, toIndex).toArray(
233: Parameter.emptyArray());
234: } else {
235: return definition;
236: }
237: }
238:
239: /**
240: * Whether this Parameters object is 'automaticly casting'. If it is, that means that you can set e.g.
241: * an Integer by a String.
242: * @return <code>true</code> if autocasting is on
243: */
244: public boolean isAutoCasting() {
245: return autoCasting;
246: }
247:
248: // implementation of List
249: public int size() {
250: return toIndex - fromIndex;
251: }
252:
253: // implementation of List
254: public Object get(int i) {
255: checkDef();
256: return backing.get(definition[i + fromIndex].getName());
257: }
258:
259: // implementation of (modifiable) List
260: // @throws NullPointerException if definition not set
261: public Object set(int i, Object value) {
262: checkDef();
263: Parameter<?> a = definition[i + fromIndex];
264: if (autoCasting)
265: value = a.autoCast(value);
266: a.checkType(value);
267: return backing.put(a.getName(), value);
268: }
269:
270: /**
271: * Throws an IllegalArgumentException if one of the required parameters was not entered.
272: */
273: public void checkRequiredParameters() {
274: checkDef();
275: for (int i = fromIndex; i < toIndex; i++) {
276: Parameter<?> a = definition[i];
277: if (a.isRequired() && (get(a.getName()) == null)) {
278: throw new IllegalArgumentException(
279: "Required parameter '" + a.getName()
280: + "' is null (of (" + toString() + ")");
281: }
282: }
283: }
284:
285: /**
286: * Returns the position of a parameter in the parameters list, using the Parameter as a qualifier.
287: * you can tehn acecss that paramter with {@link #get(int)}.
288: * @param parameter the parameter
289: * @return the index of the parameter, or -1 if it doesn't exist
290: */
291:
292: public int indexOfParameter(Parameter<?> parameter) {
293: checkDef();
294: int index = -1;
295: for (int i = fromIndex; i < toIndex; i++) {
296: if (definition[i].equals(parameter)) {
297: index = i - fromIndex;
298: break;
299: }
300: }
301: return index;
302: }
303:
304: /**
305: * Returns the position of a parameter in the parameters list, using the parameter name as a qualifier.
306: * you can then acecss that paramter with {@link #get(int)}.
307: * @param parameterName the name of the parameter
308: * @return the index of the parameter, or -1 if it doesn't exist
309: */
310: public int indexOfParameter(String parameterName) {
311: checkDef();
312: int index = -1;
313: for (int i = fromIndex; i < toIndex; i++) {
314: if (definition[i].getName().equals(parameterName)) {
315: index = i - fromIndex;
316: break;
317: }
318: }
319: return index;
320: }
321:
322: /**
323: * Checks wether a certain parameter is available, using the Parameter as a qualifier.
324: * @param parameter the parameter
325: * @return <code>true</code> if a parameter exists.
326: */
327: public boolean containsParameter(Parameter<?> parameter) {
328: return indexOfParameter(parameter) != -1;
329: }
330:
331: /**
332: * Checks wether a certain parameter is available, using the parameter name as a qualifier.
333: * @param parameterName the name of the parameter
334: * @return <code>true</code> if a parameter exists.
335: */
336: public boolean containsParameter(String parameterName) {
337: return indexOfParameter(parameterName) != -1;
338: }
339:
340: /**
341: * Sets the value of a parameter.
342: * @param parameter the Parameter describing the parameter to set
343: * @param value the object value to set
344: * @throws IllegalArgumentException if either the argument name is unknown to this Parameters, or the value is of the wrong type.
345: */
346: public <F> Parameters set(Parameter<F> parameter, F value) {
347: int index = indexOfParameter(parameter);
348: if (index > -1) {
349: set(index, value);
350: return this ;
351: } else {
352: throw new IllegalArgumentException("The parameter '"
353: + parameter + "' is not defined (defined are "
354: + toString() + ")");
355: }
356: }
357:
358: /**
359: * Sets the value of a parameter.
360: * @param parameterName the name of the parameter to set
361: * @param value the object value to set
362: * @throws IllegalArgumentException if either the argument name is unknown to this Parameters, or the value is of the wrong type.
363: */
364: public Parameters set(String parameterName, Object value) {
365: int index = indexOfParameter(parameterName);
366: if (index > -1) {
367: set(index, value);
368: return this ;
369: } else {
370: throw new IllegalArgumentException("The parameter '"
371: + parameterName + "' is not defined (defined are "
372: + toString() + ")");
373: }
374: }
375:
376: /**
377: * Copies all values of a map to the corresponding values of this Parameters Object.
378: */
379: public Parameters setAll(Map<String, ?> map) {
380: if (map != null) {
381: for (Map.Entry<String, ?> entry : map.entrySet()) {
382: set(entry.getKey(), entry.getValue());
383: }
384: }
385: return this ;
386: }
387:
388: /**
389: * Copies all values of a collection to the corresponding values of this Parameters Object.
390: */
391: public Parameters setAll(Collection<?> values) {
392: if (values != null) {
393: if (log.isDebugEnabled()) {
394: checkDef();
395: if (values.size() > definition.length) {
396: log.debug("Given too many values. " + values
397: + " does not match "
398: + Arrays.asList(definition));
399: }
400: }
401: Iterator<?> valueIterator = values.iterator();
402: int i = 0;
403: while (valueIterator.hasNext()) {
404: set(i++, valueIterator.next());
405: }
406: }
407: return this ;
408: }
409:
410: /**
411: * @since MMBase-1.9
412: */
413: public Parameters setAll(Object... values) {
414: int i = 0;
415: for (Object value : values) {
416: set(i++, value);
417: }
418: return this ;
419: }
420:
421: /**
422: * @since MMBase-1.9
423: */
424: public Parameters setAllIfDefinied(Parameters params) {
425: for (Parameter param : params.getDefinition()) {
426: setIfDefined(param, params.get(param));
427: }
428: return this ;
429: }
430:
431: public Parameters subList(int fromIndex, int toIndex) {
432: return new Parameters(this , fromIndex, toIndex);
433: }
434:
435: /**
436: * Sets the value of an argument, if the argument is defined, otherwise do nothing.
437: * @param parameter the parameter to set
438: * @param value the object value to set
439: */
440: public <F> Parameters setIfDefined(Parameter<F> parameter, F value) {
441: int index = indexOfParameter(parameter);
442: if (index > -1) {
443: set(index, value);
444: }
445: return this ;
446: }
447:
448: /**
449: * Sets the value of an argument, if the argument is defined, otherwise do nothing.
450: * @param parameterName the name of the parameter to set
451: * @param value the object value to set
452: */
453: public Parameters setIfDefined(String parameterName, Object value) {
454: int index = indexOfParameter(parameterName);
455: if (index > -1) {
456: set(index, value);
457: }
458: return this ;
459: }
460:
461: /**
462: * Gets the value of a parameter.
463: * @param parameter the parameter to get
464: * @return value the parameter value
465: */
466: public <F> F get(Parameter<F> parameter) {
467: return (F) get(parameter.getName());
468: }
469:
470: /**
471: * Gets the value of a parameter.
472: * @param parameterName the name of the parameter to get
473: * @return value the parameter value
474: */
475: public Object get(String parameterName) {
476: return backing.get(parameterName);
477: }
478:
479: /**
480: * Gets the value of a parameter, cast to a String.
481: * @param parameter the parameter to get
482: * @return value the parameter value as a <code>STring</code>
483: */
484:
485: public String getString(Parameter<?> parameter) {
486: return getString(parameter.getName());
487: }
488:
489: /**
490: * Gets the value of a parameter, cast to a String.
491: * @param parameterName the name of the parameter to get
492: * @return value the parameter value as a <code>String</code>
493: */
494: public String getString(String parameterName) {
495: return Casting.toString(get(parameterName));
496: }
497:
498: /**
499: * Gives the arguments back as a (unmodifiable) map.
500: */
501: public Map<String, Object> toMap() {
502: return Collections.unmodifiableMap(backing);
503: }
504:
505: /**
506: * Returns the Parameters as an unmodifiable List of Map.Entrys with predictable iteration order
507: * (the same order of this Parameters, which is a List of the values only, itself)
508: * @since MMBase-1.9
509: */
510: public List<Map.Entry<String, Object>> toEntryList() {
511: return new AbstractList<Map.Entry<String, Object>>() {
512: public int size() {
513: return Parameters.this .size();
514: }
515:
516: public Map.Entry<String, Object> get(final int i) {
517:
518: return new Map.Entry<String, Object>() {
519: final Parameter<?> a = Parameters.this .definition[i
520: + Parameters.this .fromIndex];
521:
522: // see Map.Entry
523: public String getKey() {
524: return a.getName();
525: }
526:
527: // see Map.Entry
528: public Object getValue() {
529: return Parameters.this .backing.get(a.getName());
530: }
531:
532: // see Map.Entry
533: public Object setValue(Object v) {
534: return Parameters.this .backing.put(a.getName(),
535: v);
536: }
537:
538: public int hashCode() {
539: Object value = getValue();
540: return a.getName().hashCode()
541: ^ (value == null ? 0 : value.hashCode());
542: }
543:
544: public boolean equals(Object o) {
545: if (o instanceof Map.Entry) {
546: Map.Entry<String, Object> entry = (Map.Entry<String, Object>) o;
547: Object value = getValue();
548: return a.getName().equals(entry.getKey())
549: && (value == null ? entry
550: .getValue() == null : value
551: .equals(entry.getValue()));
552: } else {
553: return false;
554: }
555: }
556: };
557: }
558: };
559: }
560: }
|