001: /*
002: * <copyright>
003: *
004: * Copyright 2002-2007 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026: package org.cougaar.util.annotations;
027:
028: import java.lang.annotation.Annotation;
029: import java.lang.reflect.Field;
030: import java.lang.reflect.Method;
031: import java.lang.reflect.Modifier;
032: import java.lang.reflect.ParameterizedType;
033: import java.lang.reflect.Type;
034: import java.net.URI;
035: import java.net.URISyntaxException;
036: import java.util.ArrayList;
037: import java.util.Arrays;
038: import java.util.Collections;
039: import java.util.HashMap;
040: import java.util.HashSet;
041: import java.util.LinkedHashSet;
042: import java.util.List;
043: import java.util.Map;
044: import java.util.Set;
045:
046: import org.cougaar.util.Arguments;
047:
048: /**
049: * This class provides support for field metatdata in plugins and components via
050: * annotations.
051: *
052: */
053: public class Argument {
054: private static final String ARG_GROUP_SUFFIX = "ArgGroup";
055:
056: private final Arguments args;
057:
058: public Argument(Arguments args) {
059: this .args = args;
060: }
061:
062: private String getArgGroupName(Class<?> annotationClass) {
063: // remove 'ArgGroup' from the end of the simplename
064: String name = annotationClass.getSimpleName();
065: return name.substring(0, name.length()
066: - ARG_GROUP_SUFFIX.length());
067: }
068:
069: private Set<String> getGroups(Field field) {
070: Set<String> groups = null;
071: for (Annotation anno : field.getAnnotations()) {
072: Class<?> annoClass = anno.annotationType();
073: if (anno instanceof Cougaar.ArgGroup) {
074: Cougaar.ArgGroup group = (Cougaar.ArgGroup) anno;
075: if (group.role() == Argument.GroupRole.MEMBER) {
076: if (groups == null) {
077: groups = new HashSet<String>();
078: }
079: groups.add(group.name());
080: }
081: } else if (annoClass.getName().endsWith(ARG_GROUP_SUFFIX)) {
082: try {
083: Class<?>[] parameterTypes = {};
084: Method roleGetter = annoClass.getDeclaredMethod(
085: "role", parameterTypes);
086: Object[] args = {};
087: Argument.GroupRole role = (Argument.GroupRole) roleGetter
088: .invoke(anno, args);
089: String name = getArgGroupName(annoClass);
090: if (role == Argument.GroupRole.MEMBER) {
091: if (groups == null) {
092: groups = new HashSet<String>();
093: }
094: groups.add(name);
095: }
096: } catch (Exception e) {
097: }
098: }
099: }
100: return groups;
101: }
102:
103: private Annotation getOwnedGroup(Field field) {
104: for (Annotation anno : field.getAnnotations()) {
105: Class<?> annoClass = anno.annotationType();
106: if (anno instanceof Cougaar.ArgGroup) {
107: Cougaar.ArgGroup group = (Cougaar.ArgGroup) anno;
108: if (group.role() == Argument.GroupRole.OWNER) {
109: return group;
110: }
111: } else if (annoClass.getName().endsWith(ARG_GROUP_SUFFIX)) {
112: try {
113: Class<?>[] parameterTypes = {};
114: Method roleGetter = annoClass.getDeclaredMethod(
115: "role", parameterTypes);
116: Object[] args = {};
117: Argument.GroupRole role = (Argument.GroupRole) roleGetter
118: .invoke(anno, args);
119: if (role == Argument.GroupRole.OWNER) {
120: return anno;
121: }
122: } catch (Exception e) {
123: // these can safely be ignored
124: }
125: }
126: }
127: return null;
128: }
129:
130: private void setSequenceFieldFromSpec(Field field, Object object,
131: Cougaar.Arg spec) throws ParseException,
132: IllegalAccessException, IllegalStateException {
133: String defaultValue = spec.defaultValue();
134: String key = spec.name();
135: boolean isRequired = spec.required()
136: && Cougaar.NO_VALUE.equals(defaultValue);
137: List<String> rawValues = null;
138: if (args.containsKey(key)) {
139: rawValues = args.getStrings(key);
140: } else if (isRequired) {
141: throw new IllegalStateException("Required argument " + key
142: + " was not provided");
143: } else if (defaultValue.equals(Cougaar.NULL_VALUE)) {
144: field.set(object, null);
145: return;
146: } else if (Cougaar.NO_VALUE.equals(defaultValue)) {
147: return;
148: } else {
149: // Should be in the form [x,y,z]
150: // TODO: Use the existing Arguments code for this, if I can ever
151: // find it
152: String[] valueArray;
153: int end = defaultValue.length() - 1;
154: if (defaultValue.charAt(0) == '['
155: && defaultValue.charAt(end) == ']') {
156: valueArray = defaultValue.substring(1, end).split(",");
157: } else {
158: valueArray = defaultValue.split(",");
159: }
160: rawValues = Arrays.asList(valueArray);
161: }
162: List<Object> values = new ArrayList<Object>(rawValues.size());
163: for (String rawValue : rawValues) {
164: values.add(DataType.fromField(field, rawValue));
165: }
166: field.set(object, Collections.unmodifiableList(values));
167: }
168:
169: private void setSimpleFieldFromSpec(Field field, Object object,
170: Cougaar.Arg spec) throws ParseException,
171: IllegalAccessException, IllegalStateException {
172: String defaultValue = spec.defaultValue();
173: String key = spec.name();
174: boolean isRequired = spec.required()
175: && Cougaar.NO_VALUE.equals(defaultValue);
176: String rawValue;
177: if (args.containsKey(key)) {
178: List<String> values = args.getStrings(key);
179: rawValue = values.get(0);
180: } else if (isRequired) {
181: throw new IllegalStateException("Required argument " + key
182: + " was not provided");
183: } else {
184: rawValue = defaultValue;
185: }
186: if (rawValue.equals(Cougaar.NO_VALUE)) {
187: return;
188: }
189: Object parsedValue = rawValue.equals(Cougaar.NULL_VALUE) ? null
190: : DataType.fromField(field, rawValue);
191: field.set(object, parsedValue);
192: }
193:
194: private void setFieldFromSpec(Field field, Cougaar.Arg spec,
195: Object object) throws ParseException,
196: IllegalAccessException, IllegalStateException {
197: try {
198: Class<?> valueType = field.getType();
199: boolean isSequence = List.class.isAssignableFrom(valueType);
200: if (isSequence) {
201: setSequenceFieldFromSpec(field, object, spec);
202: } else {
203: setSimpleFieldFromSpec(field, object, spec);
204: }
205: } catch (IllegalAccessException e) {
206: String exceptionMsg = e.getMessage();
207: String msg = "Couldn't set field " + field.getName()
208: + " from argument " + spec.name();
209: if (exceptionMsg != null) {
210: msg += ": " + exceptionMsg;
211: }
212: throw new IllegalAccessException(msg);
213: } catch (IllegalArgumentException e) {
214: String exceptionMsg = e.getMessage();
215: String msg = "Couldn't set field " + field.getName()
216: + " from argument " + spec.name();
217: if (exceptionMsg != null) {
218: msg += ": " + exceptionMsg;
219: }
220: throw new IllegalArgumentException(msg);
221: }
222: }
223:
224: private void setGroupOwnerField(Field field, Object object,
225: Argument.GroupIterationPolicy policy, Set<String> members)
226: throws IllegalAccessException, IllegalStateException {
227: List<Arguments> split = policy.split(args, members);
228: field.set(object, split);
229: }
230:
231: /**
232: * Set whatever {@link Cougaar.Arg}-annotated fields we have values for.
233: */
234: public void setFields(Object object) throws ParseException,
235: IllegalAccessException, IllegalStateException {
236: for (Field field : object.getClass().getFields()) {
237: int mod = field.getModifiers();
238: if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
239: // skip finals and statics
240: continue;
241: } else if (field.isAnnotationPresent(Cougaar.Arg.class)) {
242: Cougaar.Arg spec = field
243: .getAnnotation(Cougaar.Arg.class);
244: String argName = spec.name();
245: if (args.containsKey(argName)) {
246: setFieldFromSpec(field, spec, object);
247: }
248: }
249: }
250: }
251:
252: /**
253: * Set values of every field that has either a {@link Cougaar.Arg}
254: * annotation, or a Group annotation with role OWNER.
255: *
256: */
257: public void setAllFields(Object object) throws ParseException,
258: IllegalAccessException, IllegalStateException {
259: Map<String, Set<String>> groupMembers = new HashMap<String, Set<String>>();
260: Map<Annotation, Field> groupFields = new HashMap<Annotation, Field>();
261: for (Field field : object.getClass().getFields()) {
262: int mod = field.getModifiers();
263: if (Modifier.isFinal(mod) || Modifier.isStatic(mod)) {
264: // skip finals and statics
265: continue;
266: } else if (field.isAnnotationPresent(Cougaar.Arg.class)) {
267: Cougaar.Arg spec = field
268: .getAnnotation(Cougaar.Arg.class);
269: setFieldFromSpec(field, spec, object);
270: Set<String> groups = getGroups(field);
271: if (groups != null) {
272: for (String group : groups) {
273: Set<String> members = groupMembers.get(group);
274: if (members == null) {
275: members = new LinkedHashSet<String>();
276: groupMembers.put(group, members);
277: }
278: members.add(spec.name());
279: }
280: }
281: } else {
282: // Check for group owners
283: Annotation anno = getOwnedGroup(field);
284: if (anno != null) {
285: groupFields.put(anno, field);
286: }
287: }
288: }
289: // Now set up group owners
290: for (Map.Entry<Annotation, Field> entry : groupFields
291: .entrySet()) {
292: Annotation anno = entry.getKey();
293: Field field = entry.getValue();
294: Set<String> members = null;
295: Argument.GroupIterationPolicy policy = null;
296: Class<?> annoClass = anno.annotationType();
297: if (anno instanceof Cougaar.ArgGroup) {
298: Cougaar.ArgGroup group = (Cougaar.ArgGroup) anno;
299: members = groupMembers.get(group.name());
300: policy = group.policy();
301: } else if (annoClass.getName().endsWith(ARG_GROUP_SUFFIX)) {
302: try {
303: Class<?>[] parameterTypes = {};
304: Method policyGetter = annoClass.getDeclaredMethod(
305: "policy", parameterTypes);
306: Object[] methodArgs = {};
307: policy = (Argument.GroupIterationPolicy) policyGetter
308: .invoke(anno, methodArgs);
309: String name = getArgGroupName(annoClass);
310: members = groupMembers.get(name);
311: } catch (Exception e) {
312: }
313: }
314: if (policy != null && members != null && !members.isEmpty()) {
315: setGroupOwnerField(field, object, policy, members);
316: }
317: }
318: }
319:
320: public static class ParseException extends Exception {
321: public ParseException(Field field, String value, Throwable cause) {
322: super ("Couldn't parse " + value + " for field "
323: + field.getName() + ": " + cause.getMessage());
324: }
325: }
326:
327: public static enum DataType {
328: INT {
329: Object parse(Class<?> valueClass, Field field,
330: String rawValue) throws ParseException {
331: try {
332: return Integer.parseInt(rawValue);
333: } catch (NumberFormatException e) {
334: throw new ParseException(field, rawValue, e);
335: }
336: }
337: },
338: LONG {
339: Object parse(Class<?> valueClass, Field field,
340: String rawValue) throws ParseException {
341: try {
342: return Long.parseLong(rawValue);
343: } catch (NumberFormatException e) {
344: throw new ParseException(field, rawValue, e);
345: }
346: }
347: },
348: FLOAT {
349: Object parse(Class<?> valueClass, Field field,
350: String rawValue) throws ParseException {
351: try {
352: return Float.parseFloat(rawValue);
353: } catch (NumberFormatException e) {
354: throw new ParseException(field, rawValue, e);
355: }
356: }
357: },
358: DOUBLE {
359: Object parse(Class<?> valueClass, Field field,
360: String rawValue) throws ParseException {
361: try {
362: return Double.parseDouble(rawValue);
363: } catch (NumberFormatException e) {
364: throw new ParseException(field, rawValue, e);
365: }
366: }
367: },
368: STRING {
369: Object parse(Class<?> valueClass, Field field,
370: String rawValue) throws ParseException {
371: return rawValue;
372: }
373: },
374: BOOLEAN {
375: Object parse(Class<?> valueClass, Field field,
376: String rawValue) {
377: return Boolean.parseBoolean(rawValue);
378: }
379: },
380: URI {
381: Object parse(Class<?> valueClass, Field field,
382: String rawValue) throws ParseException {
383: try {
384: return new URI(rawValue);
385: } catch (URISyntaxException e) {
386: throw new ParseException(field, rawValue, e);
387: }
388: }
389: },
390: OTHER {
391: @SuppressWarnings("unchecked")
392: // enum fiddling causes unavoidable warnings
393: Object parse(Class valueClass, Field field, String rawValue)
394: throws ParseException {
395: try {
396: if (valueClass.isEnum()) {
397: // coerce the string in the default enum way
398: return Enum.valueOf(valueClass, rawValue);
399: }
400: for (Method method : valueClass.getMethods()) {
401: if (method
402: .isAnnotationPresent(Cougaar.Resolver.class)) {
403: return method.invoke(valueClass, rawValue);
404: }
405: }
406: throw new RuntimeException("No Resolver for "
407: + valueClass);
408: } catch (Exception ex) {
409: throw new ParseException(field, rawValue, ex);
410: }
411: }
412: };
413:
414: abstract Object parse(Class<?> valueClass, Field field,
415: String rawValue) throws ParseException;
416:
417: static Class<?> elementType(Field field, Class<?> valueType) {
418: Type gtype = field.getGenericType();
419: if (gtype == null) {
420: throw new IllegalStateException(
421: "Can't handle unqualified List types");
422: }
423: if (!(gtype instanceof ParameterizedType)) {
424: throw new IllegalStateException(
425: "Can't handle fields of type " + valueType);
426: }
427: Type etype = ((ParameterizedType) gtype)
428: .getActualTypeArguments()[0];
429: if (!(etype instanceof Class)) {
430: throw new IllegalStateException(
431: "Can't handle Lists of type " + etype);
432: }
433: return (Class<?>) etype;
434: }
435:
436: static Object fromField(Field field, String rawValue)
437: throws ParseException {
438: Class<?> valueClass = field.getType();
439: if (List.class.isAssignableFrom(valueClass)) {
440: valueClass = elementType(field, valueClass);
441: }
442: DataType type = OTHER;
443: if (valueClass == long.class || valueClass == Long.class) {
444: type = LONG;
445: } else if (valueClass == int.class
446: || valueClass == Integer.class) {
447: type = INT;
448: } else if (valueClass == double.class
449: || valueClass == Double.class) {
450: type = DOUBLE;
451: } else if (valueClass == float.class
452: || valueClass == Float.class) {
453: type = FLOAT;
454: } else if (valueClass == boolean.class
455: || valueClass == Boolean.class) {
456: type = BOOLEAN;
457: } else if (valueClass == String.class) {
458: type = STRING;
459: } else if (URI.class.isAssignableFrom(valueClass)) {
460: type = URI;
461: }
462: return type.parse(valueClass, field, rawValue);
463: }
464: }
465:
466: public static enum GroupRole {
467: MEMBER, OWNER
468: }
469:
470: public static enum GroupIterationPolicy {
471: ROUND_ROBIN, FIRST_UP, CLOSEST, RANDOM;
472:
473: // Default is to restrict the arguments to the
474: // given members, and then split it.
475: //
476: // TODO: Specialize this per policy
477: public List<Arguments> split(Arguments arguments,
478: Set<String> members) {
479: return new Arguments(arguments, null, null, members)
480: .split();
481: }
482: }
483: }
|