001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.persistence;
019:
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Type;
022: import java.util.HashMap;
023: import java.util.Map;
024:
025: import de.finix.contelligent.ComponentPath;
026: import de.finix.contelligent.PropertyException;
027: import de.finix.contelligent.core.PropertyType;
028: import de.finix.contelligent.exception.TypeException;
029: import de.finix.contelligent.logging.LoggingService;
030:
031: /**
032: * This class represents a property of a specific {@link Type} which is a
033: * name/value pair plus additional attributes. Although properties may have
034: * different {@link com.finix.contelligent.base.PropertyType types} their values
035: * are stored either as <code>String</code> or <code>Double</code>.
036: * Therefor this class holds the value as <code>Object</code>.
037: */
038: public class TypeProperty implements Cloneable {
039: final static org.apache.log4j.Logger log = LoggingService
040: .getLogger(TypeProperty.class);
041:
042: /** read-only but class has a set method so persistence is possible */
043: final static public String READONLY = "r";
044:
045: /** read-only and class has no set method (self generated valued) */
046: final static public String READONLY_NOSET = "rn";
047:
048: /** writeType-only but class has a read method so persistence is possible */
049: final static public String WRITEONLY = "w";
050:
051: /** writeType-only and class has no read method */
052: final static public String WRITEONLY_NOREAD = "wn";
053:
054: /**
055: * This map contains (String,Class) entries mapping the possible names of a
056: * {@link PropertyType property-type} to the class. We make the convention
057: * that the names are always defined in lowercase only!
058: */
059: final static private Map propertyTypeMap = new HashMap();
060:
061: /**
062: * This map contains (Class,Class) entries mapping the class of a
063: * {@link PropertyType property-type} to the class which makes the
064: * conversion. For example Integer.TYPE is the Class representation of the
065: * primitive type int and Integer is the class which does the conversion.
066: * For non-primitive types this classes should be equal.
067: */
068: final static private Map propertyHandlerMap = new HashMap();
069:
070: /**
071: * Setup the map of allowed property types. Typenames should be lowercase
072: * only.
073: */
074: static {
075: propertyTypeMap.put("string", String.class);
076: propertyTypeMap.put("path", ComponentPath.class);
077: propertyTypeMap.put("int", Integer.TYPE);
078: propertyTypeMap.put("long", Long.TYPE);
079: propertyTypeMap.put("double", Double.TYPE);
080: propertyTypeMap.put("float", Float.TYPE);
081: propertyTypeMap.put("boolean", Boolean.TYPE);
082: propertyHandlerMap.put(String.class, String.class);
083: propertyHandlerMap
084: .put(ComponentPath.class, ComponentPath.class);
085: propertyHandlerMap.put(Integer.TYPE, Integer.class);
086: propertyHandlerMap.put(Long.TYPE, Long.class);
087: propertyHandlerMap.put(Double.TYPE, Double.class);
088: propertyHandlerMap.put(Float.TYPE, Float.class);
089: propertyHandlerMap.put(Boolean.TYPE, Boolean.class);
090: }
091:
092: final private String name;
093:
094: /** The type of this property */
095: final private String propertyType;
096:
097: /**
098: * If the property type is numeric the value contains a Double otherwise a
099: * String instance.
100: */
101: private Object value;
102:
103: private String constraints;
104:
105: private String mode;
106:
107: private String group;
108:
109: private boolean isFinal; // default should be 'false'
110:
111: private boolean isRequired; // default should be 'false'
112:
113: private boolean inheritType; // default should be 'true'
114:
115: private boolean inheritMode; // default should be 'true'
116:
117: // cache reflection stuff
118: private String propertyMethodString = null;
119:
120: private Method getMethod = null;
121:
122: private Method setMethod = null;
123:
124: private Class typeClass = null;
125:
126: /**
127: * Contains the name of the type this property is defined in. If a property
128: * is defined multiple times in a type-hierarchie this will be the name of
129: * the last sub-type who overwrites the property.
130: */
131: private String definingTypeName;
132:
133: public TypeProperty(String name, String propertyType,
134: String constraints, String mode, Object value,
135: String group, boolean isFinal, boolean isRequired,
136: boolean inheritType, boolean inheritMode) {
137: this .name = name;
138: this .propertyType = propertyType;
139: this .constraints = constraints;
140: this .mode = mode;
141: this .value = value;
142: this .group = group;
143: this .isFinal = isFinal;
144: this .isRequired = isRequired;
145: this .inheritType = inheritType;
146: this .inheritMode = inheritMode;
147: this .definingTypeName = "";
148: }
149:
150: public TypeProperty(String name, String propertyType,
151: String constraints, String mode, Object value, String group) {
152: this (name, propertyType, constraints, mode, value, group,
153: false, false, true, true);
154: }
155:
156: public String getName() {
157: return name;
158: }
159:
160: public String getPropertyType() {
161: return propertyType;
162: }
163:
164: public Object getValue() {
165: return value;
166: }
167:
168: public void setValue(Object value) {
169: this .value = value;
170: }
171:
172: public String getMode() {
173: return mode;
174: }
175:
176: public void setMode(String mode) {
177: this .mode = mode;
178: }
179:
180: public String getGroup() {
181: return group;
182: }
183:
184: public void setGroup(String group) {
185: this .group = group;
186: }
187:
188: public String getConstraints() {
189: return constraints;
190: }
191:
192: public void setConstraints(String constraints) {
193: this .constraints = constraints;
194: }
195:
196: public void setIsFinal(boolean isFinal) {
197: this .isFinal = isFinal;
198: }
199:
200: public boolean isFinal() {
201: return isFinal;
202: }
203:
204: public void setIsRequired(boolean isRequired) {
205: this .isRequired = isRequired;
206: }
207:
208: public boolean isRequired() {
209: return isRequired;
210: }
211:
212: public void setDefiningTypeName(String definingTypeName) {
213: this .definingTypeName = definingTypeName;
214: }
215:
216: public String getDefiningTypeName() {
217: return definingTypeName;
218: }
219:
220: public void setInheritType(boolean inheritType) {
221: this .inheritType = inheritType;
222: }
223:
224: public boolean inheritType() {
225: return inheritType;
226: }
227:
228: public void setInheritMode(boolean inheritMode) {
229: this .inheritMode = inheritMode;
230: }
231:
232: public boolean inheritMode() {
233: return inheritMode;
234: }
235:
236: public void setTypeClass(Class typeClass) {
237: this .typeClass = typeClass;
238: }
239:
240: /**
241: * Returns a clone of this <code>TypeProperty</code>.
242: *
243: * @return a clone of this type-property.
244: */
245: public Object clone() {
246: TypeProperty clone = new TypeProperty(name, propertyType,
247: constraints, mode, value, group, isFinal, isRequired,
248: inheritType, inheritMode);
249: clone.setDefiningTypeName(definingTypeName);
250: return clone;
251: }
252:
253: public String toString() {
254: return ("[TypeProperty '" + name + "' with value '" + value
255: + "' (final=" + isFinal + ", defined in type '"
256: + definingTypeName + "') ]");
257: }
258:
259: /**
260: * Set this property of the receiver to the given value.
261: */
262: public void setProperty(Object receiver, Object value)
263: throws TypeException {
264: try {
265: Object convertedValue = valueOf(value);
266: getSetMethod().invoke(receiver,
267: new Object[] { convertedValue });
268: } catch (Exception e) {
269: throw new TypeException("Could not set property '" + name
270: + "' on instance '" + receiver + "' (type="
271: + typeClass + ")!", e);
272: }
273: }
274:
275: /**
276: * Set the value of the receiver of this property.
277: */
278: public Object getProperty(Object receiver) throws TypeException {
279: try {
280: return convertValue(getGetMethod().invoke(receiver,
281: new Object[0]));
282: } catch (Exception e) {
283: e.printStackTrace();
284: throw new TypeException("Could not get property '" + name
285: + "' from instance '" + receiver + "' (type="
286: + typeClass + "!", e);
287: }
288: }
289:
290: public Object convertProperty(String value) throws TypeException {
291: return convertStringValue(value);
292: }
293:
294: private String getPropertyMethodString() {
295: if (propertyMethodString == null) {
296: propertyMethodString = Character
297: .toUpperCase(name.charAt(0))
298: + name.substring(1);
299: }
300: return propertyMethodString;
301: }
302:
303: private Method getGetMethod() throws NoSuchMethodException {
304: if (getMethod == null) {
305: getMethod = typeClass.getMethod("get"
306: + getPropertyMethodString(), new Class[0]);
307: }
308: return getMethod;
309: }
310:
311: private Method getSetMethod() throws NoSuchMethodException {
312: if (setMethod == null) {
313: Class propClass = getPropertyClass();
314: setMethod = typeClass.getMethod("set"
315: + getPropertyMethodString(),
316: new Class[] { propClass });
317: }
318: return setMethod;
319: }
320:
321: /**
322: * Returns the implementation class of the given <code>TypeProperty</code>
323: * or throws an exception if the type is unknown.
324: *
325: * @param property
326: * a <code>TypeProperty</code> value
327: * @return a <code>Class</code> value
328: * @exception PropertyTypeException
329: * if an error occurs
330: */
331: private Class getPropertyClass() {
332: return (Class) propertyTypeMap.get(propertyType);
333: }
334:
335: /**
336: * Returns a newly created <code>Object</code> which is an instance of the
337: * given <tt>propertyClass</tt> representing the given <code>Object</code>
338: * value. This method is able to convert every primitive types except
339: * <tt>void</tt> and <tt>char</tt> into the corresponding handler class.
340: * For example
341: *
342: * <pre>
343: * Integer.TYPE
344: * </pre>
345: *
346: * is converted into <code>Integer</code>. <BR>
347: * All non-primitive types except <code>String</code> must implement the
348: * {@link PropertyType} interface and a static method
349: * <tt>valueOf(String)</tt>. <BR>
350: * Note that null and empty string are treated in exactly the same manner
351: * which means that null values are converted into an empty string before
352: * the work continues. <BR>
353: * The conversion happens in the following order: <BR>
354: * <UL>
355: * <LI> if the given object is a <code>String</code> instance it is
356: * returned immediately.
357: * <LI> else if the given object is a <code>Double</code> we assume that
358: * the property class is a primitive number and create an instance using the
359: * matching
360: *
361: * <pre>
362: * xValue()
363: * </pre>
364: *
365: * method of class <code>Double</code>. For example an integer will be
366: * converted using
367: *
368: * <pre>
369: * new Integer(((Double) orgValue).intValue())
370: * </pre>.
371: * <LI> else we convert the given object into the right instance using the
372: * <code>public static valueOf(String)</code> method of the configured
373: * handler class for this property class.
374: * </UL>
375: * If an error occurs during the conversion a
376: * <code>PropertyConversionException</code> is thrown.
377: *
378: * @param propertyClass
379: * a <code>Class</code> value
380: * @param orgValue
381: * a <code>String</code> value
382: * @return an <code>Object</code> value
383: * @exception PropertyConversionException
384: * if an error occurs
385: * @see PropertyType
386: */
387: private Object valueOf(Object orgValue) throws TypeException {
388: Class propertyClass = getPropertyClass();
389:
390: if (propertyClass.equals(String.class)) { // most of the properties
391: // will be strings ...
392: return ((orgValue == null) ? "" : orgValue.toString());
393: }
394:
395: // for output only:
396: final String propertyClassName = propertyClass.getName();
397:
398: if (propertyClass.isPrimitive()) {
399: if (propertyClass.equals(Void.TYPE)
400: || propertyClass.equals(Character.TYPE)) {
401: log.error("valueOf() - invalid property-class '"
402: + propertyClassName + "'!");
403: throw new TypeException("invalid property-class '"
404: + propertyClassName + "'!");
405: }
406: }
407:
408: Object result = null;
409:
410: if (orgValue instanceof Double) {
411: if (log.isDebugEnabled()) {
412: log
413: .debug("valueOf() - detected numeric property with class '"
414: + propertyClassName
415: + "'! Expecting value as 'Double' ...");
416: }
417:
418: Double value = (Double) orgValue;
419:
420: if (propertyClass == Integer.TYPE)
421: result = new Integer(value.intValue());
422: else if (propertyClass == Float.TYPE)
423: result = new Float(value.floatValue());
424: else if (propertyClass == Double.TYPE)
425: result = value;
426: else if (propertyClass == Long.TYPE)
427: result = new Long(value.longValue());
428: else if (propertyClass == Short.TYPE)
429: result = new Short(value.shortValue());
430: else if (propertyClass == Byte.TYPE)
431: result = new Byte(value.byteValue());
432: else {
433: log
434: .error("valueOf() - could not convert property with class '"
435: + propertyClassName
436: + "' into a numeric Object!");
437: throw new TypeException(
438: "Could not convert property with class '"
439: + propertyClassName
440: + "' into a numeric Object!");
441: }
442: } // else it MUST be a String ....
443: else {
444: String value = (orgValue == null) ? "" : orgValue
445: .toString();
446: if (propertyClass == ComponentPath.class) {
447: try {
448: if (value.length() == 0) // better performance, many
449: // paths will be empty
450: result = ComponentPath.EMPTY_PATH;
451: else
452: result = ComponentPath.valueOf(value);
453: } catch (PropertyException e) {
454: throw new TypeException(e.getMessage());
455: }
456: } else if (propertyClass == Boolean.TYPE) {
457: result = Boolean.valueOf(value);
458: } else {
459: log
460: .error("valueOf() - could not convert property with class '"
461: + propertyClassName + "' from String!");
462: throw new TypeException(
463: "Could not convert property with class '"
464: + propertyClassName + "' from String!");
465: }
466: }
467:
468: if (log.isDebugEnabled()) {
469: log.debug("valueOf() - converted '" + orgValue
470: + "' (class " + orgValue.getClass().getName()
471: + ") into object of " + result.getClass()
472: + " with value '" + result + "'.");
473: }
474: return result;
475: }
476:
477: private Object convertValue(Object orgValue) throws TypeException {
478: if (orgValue == null) {
479: return null;
480: }
481:
482: Class propertyClass = getPropertyClass();
483:
484: // for output only:
485: final String propertyClassName = propertyClass.getName();
486:
487: if (propertyClass.isPrimitive()) {
488:
489: if (propertyClass.equals(Void.TYPE)
490: || propertyClass.equals(Character.TYPE)) {
491: log.error("valueOf() - invalid property-class '"
492: + propertyClassName + "'!");
493: throw new TypeException("invalid property-class '"
494: + propertyClassName + "'!");
495: }
496: }
497:
498: Object result = null;
499: if (orgValue instanceof Number) {
500: if (log.isDebugEnabled()) {
501: log
502: .debug("valueOf() - detected numeric property with class '"
503: + propertyClassName
504: + "'! Expecting value as 'Double' ...");
505: }
506:
507: Number value = (Number) orgValue;
508:
509: if (propertyClass == Integer.TYPE)
510: result = new Double(value.doubleValue());
511: else if (propertyClass == Float.TYPE)
512: result = new Double(value.doubleValue());
513: else if (propertyClass == Double.TYPE)
514: result = value;
515: else if (propertyClass == Long.TYPE)
516: result = new Double(value.doubleValue());
517: else if (propertyClass == Short.TYPE)
518: result = new Double(value.doubleValue());
519: else if (propertyClass == Byte.TYPE)
520: result = new Double(value.doubleValue());
521: else {
522: log
523: .error("valueOf() - could not convert property with class '"
524: + propertyClassName
525: + "' into a numeric Object!");
526: throw new TypeException(
527: "Could not convert property with class '"
528: + propertyClassName
529: + "' into a numeric Object!");
530: }
531: } // else it MUST be a String ....
532: else {
533: if (propertyClass == String.class) {
534: return orgValue.toString();
535: } else if (propertyClass == ComponentPath.class) {
536: result = orgValue.toString();
537: } else if (propertyClass == Boolean.TYPE) {
538: result = orgValue.toString();
539: } else {
540: log
541: .error("valueOf() - could not convert property with class '"
542: + propertyClassName + "' from String!");
543: throw new TypeException(
544: "Could not convert property with class '"
545: + propertyClassName + "' from String!");
546: }
547: }
548:
549: if (log.isDebugEnabled()) {
550: log.debug("valueOf() - converted '" + orgValue
551: + "' (class " + orgValue.getClass().getName()
552: + ") into object of " + result.getClass()
553: + " with value '" + result + "'.");
554: }
555: return result;
556: }
557:
558: private Object convertStringValue(String orgValue)
559: throws TypeException {
560: if (orgValue == null) {
561: return null;
562: }
563:
564: Class propertyClass = getPropertyClass();
565:
566: // for output only:
567: final String propertyClassName = propertyClass.getName();
568:
569: if (propertyClass.isPrimitive()) {
570: if (propertyClass.equals(Void.TYPE)
571: || propertyClass.equals(Character.TYPE)) {
572: log.error("valueOf() - invalid property-class '"
573: + propertyClassName + "'!");
574: throw new TypeException("invalid property-class '"
575: + propertyClassName + "'!");
576: }
577: }
578:
579: Object result = null;
580: if (propertyClass == String.class) {
581: result = orgValue;
582: } else if (propertyClass == ComponentPath.class) {
583: result = orgValue;
584: } else if (propertyClass == Boolean.TYPE) {
585: result = orgValue;
586: } else {
587: try {
588: result = new Double(orgValue);
589: } catch (NumberFormatException e) {
590: log
591: .error("valueOf() - invalid value for numeric property: "
592: + orgValue);
593: throw new TypeException(
594: "Invalid value for numeric property: "
595: + orgValue);
596: }
597: }
598:
599: if (log.isDebugEnabled()) {
600: log.debug("valueOf() - converted '" + orgValue
601: + "' (class " + orgValue.getClass().getName()
602: + ") into object of " + result.getClass()
603: + " with value '" + result + "'.");
604: }
605: return result;
606: }
607: }
|