001: /*
002: $Header: /cvsroot/xorm/xorm/src/org/xorm/ClassMapping.java,v 1.43 2004/05/30 08:45:52 wbiggs Exp $
003:
004: This file is part of XORM.
005:
006: XORM is free software; you can redistribute it and/or modify
007: it under the terms of the GNU General Public License as published by
008: the Free Software Foundation; either version 2 of the License, or
009: (at your option) any later version.
010:
011: XORM is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU General Public License for more details.
015:
016: You should have received a copy of the GNU General Public License
017: along with XORM; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package org.xorm;
021:
022: import java.beans.BeanInfo;
023: import java.beans.IntrospectionException;
024: import java.beans.Introspector;
025: import java.beans.PropertyDescriptor;
026: import java.lang.reflect.Method;
027: import java.lang.reflect.Modifier;
028: import java.math.BigDecimal;
029: import java.math.BigInteger;
030: import java.sql.Time;
031: import java.sql.Timestamp;
032: import java.util.ArrayList;
033: import java.util.Iterator;
034: import java.util.Collection;
035: import java.util.Date;
036: import java.util.HashMap;
037: import java.util.HashSet;
038: import java.util.Locale;
039: import java.util.Map;
040: import java.util.Set;
041: import java.util.Vector;
042:
043: import javax.jdo.JDOFatalUserException;
044: import javax.jdo.JDOUserException;
045: import javax.jdo.spi.JDOImplHelper;
046:
047: import org.xorm.datastore.Table;
048: import org.xorm.datastore.Column;
049: import org.xorm.datastore.DataFetchGroup;
050:
051: import org.xorm.util.FieldDescriptor;
052: import org.xorm.util.jdoxml.JDOClass;
053: import org.xorm.util.jdoxml.JDOField;
054:
055: /**
056: * A ClassMapping provides the mapping from a Java type (class or interface)
057: * to a datastore type. Each field of a Class can map to a Column in
058: * a Table, or may be mapped as a RelationshipMapping.
059: *
060: * A ClassMapping is also the repository for default fetch group
061: * information.
062: */
063: public class ClassMapping implements I15d, Cloneable {
064: private ModelMapping modelMapping;
065: Class clazz;
066: private Table table;
067: private Class datastoreIdentityType;
068:
069: private Map fieldToColumn = new HashMap();
070: private Map methodToProperty = new HashMap();
071: private Map relationships = new HashMap();
072: private Map inverses = new HashMap();
073: private Set managedFields = new HashSet(); // contains String field names
074: private Collection fields; // contains FieldDescriptors
075: private Set mappedFields = new HashSet();
076: private DataFetchGroup defaultFetchGroup = new DataFetchGroup();
077:
078: /** Creates a shallow clone. */
079: public Object clone() {
080: try {
081: return super .clone();
082: } catch (CloneNotSupportedException e) {
083: // ignored
084: }
085: return null;
086: }
087:
088: /**
089: * Returns true if the class parameter is not a primitive type,
090: * a wrapper class for a primitive type, or java.util.Date,
091: * java.lang.String, java.util.Locale, java.math.BigDecimal,
092: * or java.math.BigInteger.
093: */
094: public static boolean isUserType(Class clazz) {
095: if (clazz.isArray()) {
096: clazz = clazz.getComponentType();
097: }
098: return (!clazz.isPrimitive() && !clazz.equals(Boolean.class)
099: && !clazz.equals(Character.class)
100: && !clazz.equals(Double.class)
101: && !clazz.equals(Float.class)
102: && !clazz.equals(Integer.class)
103: && !clazz.equals(Long.class)
104: && !clazz.equals(Short.class)
105: && !clazz.equals(String.class)
106: && !clazz.equals(BigDecimal.class)
107: && !clazz.equals(BigInteger.class)
108: && !clazz.equals(Date.class)
109: && !clazz.equals(Timestamp.class)
110: && !clazz.equals(Time.class) && !clazz
111: .equals(Locale.class));
112: }
113:
114: /**
115: * Creates a new ClassMapping that maps the given Class. The
116: * class will be introspected and assigned relationships for all
117: * properties that are of a user-defined (non-collection) type.
118: */
119: public ClassMapping(ModelMapping modelMapping, Class clazz) {
120: this .clazz = clazz;
121: this .modelMapping = modelMapping;
122: if (clazz.isInterface()
123: || ((clazz.getModifiers() & Modifier.ABSTRACT) != 0)) {
124: fields = FieldDescriptor.getFieldDescriptors(clazz);
125: } else {
126: JDOImplHelper helper = JDOImplHelper.getInstance();
127: String[] names = helper.getFieldNames(clazz);
128: Class[] types = helper.getFieldTypes(clazz);
129: fields = new ArrayList();
130: for (int i = 0; i < names.length; i++) {
131: fields.add(new FieldDescriptor(names[i], types[i]));
132: }
133: }
134: init();
135: }
136:
137: /**
138: *@see #ClassMapping(ModelMapping, Class)
139: */
140: public ClassMapping(ModelMapping modelMapping, Class clazz,
141: JDOClass jdoClass) {
142: this .clazz = clazz;
143: this .modelMapping = modelMapping;
144: fields = loadFieldDescriptors(clazz, jdoClass);
145: init();
146: }
147:
148: /**
149: * Common code for both constructors.
150: */
151: private void init() {
152: defaultFetchGroup = new DataFetchGroup();
153: Iterator i = fields.iterator();
154: while (i.hasNext()) {
155: FieldDescriptor fd = (FieldDescriptor) i.next();
156: // This is a kluge for now, maybe require explicit config on this?
157: if (isUserType(fd.type)) {
158: RelationshipMapping implicit = new RelationshipMapping();
159: RelationshipMapping.Endpoint end1 = new RelationshipMapping.Endpoint();
160:
161: end1.setElementClass(fd.type);
162: implicit.setSource(end1);
163: relationships.put(fd.name, implicit);
164: }
165:
166: Method read = fd.readMethod;
167: if (read != null) {
168: methodToProperty.put(read.getName(), fd);
169: }
170: Method write = fd.writeMethod;
171: if (write != null) {
172: methodToProperty.put(write.getName(), fd);
173: }
174: }
175: }
176:
177: /**
178: * Defines the table that will be used to map the class.
179: * If table is null, defaults will be applied to create a mapping for
180: * the table. This will include a datastore identity column called
181: * "xorm_pk"; other column names will be the same as the field name.
182: */
183: public void setTable(Table table) {
184: boolean fakeTable = false;
185: Column pk;
186: if (table == null) {
187: fakeTable = true;
188: table = new Table(clazz.getName());
189: pk = new Column(table, "xorm_pk");
190: table.addColumn(pk);
191: table.setPrimaryKey(pk);
192: Iterator i = fields.iterator();
193: while (i.hasNext()) {
194: FieldDescriptor fd = (FieldDescriptor) i.next();
195: Column c = new Column(table, fd.name);
196: table.addColumn(c);
197: fieldToColumn.put(fd.name, c);
198: }
199: } else {
200: pk = table.getPrimaryKey();
201: if (pk == null) {
202: throw new JDOFatalUserException(I18N.msg(
203: "E_mapping_no_pk", table.getName()));
204: }
205: }
206: defaultFetchGroup.addColumn(pk);
207: this .table = table;
208: }
209:
210: /**
211: * Get the map of relationships. The keys are Strings and the values
212: * are RelationshipMapping objects. A relationship is defined as a
213: * -to-one or -to-many relationship to another first class user object.
214: */
215: public Map getRelationships() {
216: return relationships;
217: }
218:
219: public DataFetchGroup getDefaultFetchGroup() {
220: return defaultFetchGroup;
221: }
222:
223: /**
224: * Gets the set of FieldDescriptors that have column mappings defined
225: * in the JDO metadata file.
226: */
227: public Set getMappedFieldDescriptors() {
228: return mappedFields;
229: }
230:
231: /**
232: * Gets the field descriptors found by introspection on the class
233: * or interface. This includes primitive types, user types, and
234: * collection references. The returned items are instances of
235: * org.xorm.FieldDescriptor. Not all fields found by introspection
236: * are required to be mapped.
237: */
238: public Collection getFieldDescriptors() {
239: return fields;
240: }
241:
242: /**
243: * Returns the set of managed field names. This set is the union of
244: * fields that are mapped and fields that are defined as relationships.
245: */
246: public Set getManagedFields() {
247: return managedFields;
248: }
249:
250: public String getInverse(String field) {
251: return (String) inverses.get(field);
252: }
253:
254: public void setInverse(String localField, String inverseField) {
255: inverses.put(localField, inverseField);
256: }
257:
258: /**
259: * Retrieves the field associated with a get or set method.
260: * Returns null if there is no mapping.
261: */
262: public String getFieldForMethod(Method method) {
263: String methodName = method.getName();
264: FieldDescriptor fd = (FieldDescriptor) methodToProperty
265: .get(methodName);
266: if (fd == null)
267: return null;
268: return fd.name;
269: }
270:
271: /**
272: * Convenience method that gets the Column mapped to a particular
273: * get or set method. Returns null for unmapped methods.
274: */
275: public Column getColumnForMethod(Method method) {
276: return getColumn(getFieldForMethod(method));
277: }
278:
279: /** Get the table the class is mapped to. */
280: public Table getTable() {
281: return table;
282: }
283:
284: /** Get the class that this mapping references. */
285: public Class getMappedClass() {
286: return clazz;
287: }
288:
289: /** Get the Column mapped to the given field. */
290: public Column getColumn(String field) {
291: if ("this".equals(field)) {
292: return table.getPrimaryKey();
293: }
294: return (Column) fieldToColumn.get(field);
295: }
296:
297: /**
298: * Set the Column mapped to the given field.
299: *
300: * @exception JDOUserException if the field does not exist, or if
301: * the field has a set() method but the column is marked as read-only.
302: */
303: public void setColumn(String field, Column column,
304: Boolean inDefaultFetchGroup) {
305: FieldDescriptor fd = getFieldDescriptor(field);
306: if (column.isReadOnly() && (fd.writeMethod != null)) {
307: throw new JDOUserException(I18N.msg("E_setter_ro_column",
308: new Object[] { column.getName(), clazz.getName(),
309: field }));
310: }
311: mappedFields.add(fd);
312: fieldToColumn.put(field, column);
313: managedFields.add(field);
314:
315: // The column selections for the default fetch group follow
316: // the JDO defaults -- all non-user-defined types are included
317: // unless specifically set to default-fetch-group="false".
318: // For user-defined types (First-Class Objects), the column itself
319: // is included by default (the foreign key, that is), but the
320: // target object is not resolved unless default-fetch-group="true"
321: if (!Boolean.FALSE.equals(inDefaultFetchGroup)) {
322: defaultFetchGroup.addColumn(column);
323: if (isUserType(fd.type)
324: && Boolean.TRUE.equals(inDefaultFetchGroup)) {
325: ClassMapping target = modelMapping
326: .getClassMapping(fd.type);
327: defaultFetchGroup.addSubgroup(column, target
328: .getDefaultFetchGroup());
329: }
330: }
331: }
332:
333: /**
334: * Define a to-many relationship on a field.
335: *
336: * @exception JDOUserException if the field does not exist
337: */
338: public void setRelationship(String field,
339: RelationshipMapping relation) {
340: FieldDescriptor fd = getFieldDescriptor(field); // assertion
341: relationships.put(field, relation);
342: managedFields.add(field);
343: }
344:
345: /**
346: * Retrieves the FieldDescriptor associated with the given field
347: * name by iterating through the collection of all FieldDescriptors
348: * for this class.
349: *
350: * @exception JDOUserException if the named field does not exist.
351: */
352: public FieldDescriptor getFieldDescriptor(String field) {
353: Iterator i = fields.iterator();
354: while (i.hasNext()) {
355: FieldDescriptor fd = (FieldDescriptor) i.next();
356: if (fd.name.equals(field))
357: return fd;
358: }
359: throw new JDOUserException(I18N.msg("E_no_field", new Object[] {
360: field, clazz.getName() }));
361: }
362:
363: /**
364: * Get the relationship mapped to a field. If no relationship
365: * exists, returns null.
366: */
367: public RelationshipMapping getRelationship(String field) {
368: return (RelationshipMapping) relationships.get(field);
369: }
370:
371: /**
372: * Convenience method to go from a get/set method to the
373: * associated relationship mapping.
374: */
375: public RelationshipMapping getRelationshipForMethod(Method method) {
376: String methodName = method.getName();
377: FieldDescriptor fd = (FieldDescriptor) methodToProperty
378: .get(methodName);
379: if (fd == null)
380: return null;
381: return (RelationshipMapping) relationships.get(fd.name);
382: }
383:
384: /** Returns the Java type of the field. */
385: public Class getFieldType(String field) {
386: return getRelationship(field).getSource().getElementClass();
387: }
388:
389: /**
390: * Returns the Class specified as the identity-type for a class mapping,
391: * or null if none was specified.
392: */
393: public Class getDatastoreIdentityType() {
394: return datastoreIdentityType;
395: }
396:
397: public void setDatastoreIdentityType(Class datastoreIdentityType) {
398: this .datastoreIdentityType = datastoreIdentityType;
399: }
400:
401: /**
402: * Returns a Collection of all FieldDescriptors that are configured as
403: * persistent. The Class argument may be an interface or class. Declared
404: * interfaces will be used too.
405: * Only fields configured in JDO mapping file are taken into account.
406: * @author radek radzimir@polbox.com - 12.08.2003
407: * @return a Collection<FieldDescriptor>
408: * @see findLocalFieldDescriptors(Class, Map)
409: */
410: private Collection loadFieldDescriptors(Class clazz,
411: JDOClass jdoClass) {
412: //save properties to a map
413: Map propMap = new HashMap();
414: try {
415: //analyse the class or interface
416: BeanInfo bi;
417: if (clazz.isInterface()) {
418: bi = Introspector.getBeanInfo(clazz);
419: } else {
420: bi = Introspector.getBeanInfo(clazz, Object.class);
421: }
422: PropertyDescriptor[] pd = bi.getPropertyDescriptors();
423: for (int i = 0; i < pd.length; i++) {
424: PropertyDescriptor descriptor = pd[i];
425: propMap.put(descriptor.getName(), descriptor);
426: }
427: //analyse interfaces
428: Class[] interfaces = clazz.getInterfaces();
429: for (int k = 0; k < interfaces.length; k++) {
430: bi = Introspector.getBeanInfo(interfaces[k]);
431: pd = bi.getPropertyDescriptors();
432: for (int i = 0; i < pd.length; i++) {
433: PropertyDescriptor descriptor = pd[i];
434: String key = descriptor.getName();
435: if (!propMap.containsKey(key)) {
436: propMap.put(key, descriptor);
437: }
438: }
439: }
440: } catch (IntrospectionException ex) {
441: throw new JDOFatalUserException(
442: "Error while introspecting class "
443: + clazz.getName(), ex);
444: }
445:
446: //iterate over configured properties
447: Collection descriptorsColl = new Vector();
448: Iterator fieldsIter = jdoClass.getFields().iterator();
449: while (fieldsIter.hasNext()) {
450: JDOField jdoField = (JDOField) fieldsIter.next();
451: String name = jdoField.getName();
452: //find getter abd setter
453: PropertyDescriptor descriptor = (PropertyDescriptor) propMap
454: .get(name);
455: if (descriptor == null) {
456: throw new JDOFatalUserException(I18N.msg(
457: "E_unknown_property", name, clazz.getName()));
458: }
459: FieldDescriptor fd = new FieldDescriptor(descriptor);
460: descriptorsColl.add(fd);
461: }
462: return descriptorsColl;
463: }
464:
465: }
|