001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.metadata;
012:
013: import java.util.*;
014: import java.lang.reflect.Modifier;
015: import java.lang.reflect.Field;
016: import java.lang.reflect.Method;
017: import java.math.BigDecimal;
018: import java.math.BigInteger;
019: import java.io.IOException;
020: import java.io.DataOutputStream;
021: import java.io.ByteArrayOutputStream;
022: import java.security.MessageDigest;
023: import java.security.DigestOutputStream;
024: import java.security.NoSuchAlgorithmException;
025:
026: import com.versant.core.common.BindingSupportImpl;
027:
028: /**
029: * This knows how to analyze types to detirmine default persistence
030: * information e.g. persistence-modifier, default-fetch-group and so on.
031: * This is used by the runtime meta data building code and the workbench.
032: *
033: * @see MetaDataBuilder
034: */
035: public final class MetaDataUtils {
036:
037: /**
038: * Are we running on jdk 1.5 or higher?
039: */
040: private static boolean isJDK_1_5 = false;
041:
042: static {
043: try {
044: Class.forName("java.lang.StringBuilder");
045: isJDK_1_5 = true;
046: } catch (Exception e) {
047: // ignore
048: }
049: }
050:
051: /**
052: * This is all the class types that are mutable. These must be cloned
053: * if copies is maintained.
054: */
055: private final HashSet MUTABLE_TYPES = new HashSet();
056:
057: /**
058: * Fields with any of these modifiers may not be persistent.
059: */
060: private static final int PM_NONE_FIELD_MODS = Modifier.STATIC
061: | Modifier.FINAL;
062:
063: /**
064: * Fields with types in this set are persistent by default.
065: */
066: protected final HashSet PM_PERSISTENT_TYPES = new HashSet();
067:
068: /**
069: * Field types in this set default to the default fetch group.
070: */
071: protected final HashSet DFG_TYPES = new HashSet();
072:
073: /**
074: * Arrays of types in this set are embedded by default.
075: */
076: protected final HashSet EMBEDDED_ARRAY_TYPES = new HashSet();
077:
078: protected static Set BASIC_TYPES = new HashSet();
079:
080: private Set classIdSet = new HashSet();
081: private MessageDigest messageDigest;
082: private DataOutputStream hashOut;
083:
084: protected HashSet storeTypes = new HashSet(17); // of Class
085:
086: // types made persistent through externalization
087: protected HashSet externalizedTypes = new HashSet(17); // of String class name
088:
089: public MetaDataUtils() {
090: Class[] mTypes = new Class[] { java.util.Date.class,
091:
092: java.util.List.class, java.util.ArrayList.class,
093:
094: java.util.LinkedList.class, java.util.Vector.class,
095:
096: java.util.Collection.class, java.util.Set.class,
097: java.util.HashSet.class, java.util.TreeSet.class,
098: java.util.SortedSet.class, java.util.SortedMap.class,
099:
100: java.util.Map.class, java.util.HashMap.class,
101: java.util.TreeMap.class, java.util.Hashtable.class, };
102:
103: for (int i = 0; i < mTypes.length; i++) {
104: MUTABLE_TYPES.add(mTypes[i]);
105: }
106: Class[] dfg = new Class[] { Boolean.TYPE, Byte.TYPE,
107: Short.TYPE, Integer.TYPE, Long.TYPE, Character.TYPE,
108: Float.TYPE, Double.TYPE, Boolean.class, Byte.class,
109: Short.class, Integer.class, Long.class,
110: Character.class, Float.class, Double.class,
111: String.class, BigDecimal.class, BigInteger.class,
112: Date.class,
113:
114: };
115: Class[] t = new Class[] { Locale.class, ArrayList.class,
116:
117: HashMap.class, HashSet.class, Hashtable.class,
118: LinkedList.class, TreeMap.class, TreeSet.class,
119: SortedSet.class, SortedMap.class, Vector.class,
120: Collection.class, Set.class, List.class, Map.class, };
121:
122: for (int i = dfg.length - 1; i >= 0; i--) {
123: DFG_TYPES.add(dfg[i]);
124: PM_PERSISTENT_TYPES.add(dfg[i]);
125: }
126: for (int i = t.length - 1; i >= 0; i--) {
127: PM_PERSISTENT_TYPES.add(t[i]);
128: }
129: Class[] emb = new Class[] { Boolean.TYPE, Byte.TYPE,
130: Short.TYPE, Integer.TYPE, Long.TYPE, Character.TYPE,
131: Float.TYPE, Double.TYPE, Boolean.class, Byte.class,
132: Short.class, Integer.class, Long.class,
133: Character.class, Float.class, Double.class,
134:
135: };
136: for (int i = emb.length - 1; i >= 0; i--) {
137: if (emb[i].isPrimitive())
138: EMBEDDED_ARRAY_TYPES.add(emb[i]);
139: }
140:
141: Class[] basicTypes = new Class[] { Boolean.TYPE, Byte.TYPE,
142: Short.TYPE, Integer.TYPE, Long.TYPE, Character.TYPE,
143: Float.TYPE, Double.TYPE, Boolean.class, Byte.class,
144: Short.class, Integer.class, Long.class,
145: Character.class, Float.class, Double.class,
146: byte[].class, char[].class, Byte[].class,
147: Character[].class, String.class, BigDecimal.class,
148: BigInteger.class, Date.class, Calendar.class,
149: java.sql.Date.class, java.sql.Time.class,
150: java.sql.Timestamp.class, };
151: for (int i = 0; i < basicTypes.length; i++) {
152: BASIC_TYPES.add(basicTypes[i]);
153: }
154:
155: ByteArrayOutputStream devnull = new ByteArrayOutputStream(512);
156: try {
157: messageDigest = MessageDigest.getInstance("SHA");
158: DigestOutputStream mdo = new DigestOutputStream(devnull,
159: messageDigest);
160: hashOut = new DataOutputStream(mdo);
161: } catch (NoSuchAlgorithmException complain) {
162: throw BindingSupportImpl.getInstance().security(
163: complain.getMessage());
164: }
165: }
166:
167: /**
168: * Clear the set of classId's generated so far and any other statefull
169: * information.
170: */
171: public void clear() {
172: classIdSet.clear();
173: storeTypes.clear();
174: externalizedTypes.clear();
175: }
176:
177: /**
178: * Is type a mutable type e.g. java.util.Date etc?
179: */
180: public boolean isMutableType(Class type) {
181: return MUTABLE_TYPES.contains(type);
182: }
183:
184: /**
185: * Do the modifiers indicate a field can be persistent?
186: */
187: public boolean isPersistentModifiers(int modifiers) {
188: return (modifiers & PM_NONE_FIELD_MODS) == 0;
189: }
190:
191: /**
192: * Do the modifiers indicate a field that is persistent by default?
193: */
194: public boolean isDefaultPersistentModifiers(int modifiers) {
195: return isPersistentModifiers(modifiers)
196: && !Modifier.isTransient(modifiers);
197: }
198:
199: /**
200: * Is a field of type that can be persistent?
201: *
202: * @param type Type to check
203: * @param classMap Map with all persistent classes as keys
204: */
205: public boolean isPersistentType(Class type, Map classMap) {
206: return isDefaultPersistentType(type, classMap)
207: || type == Object.class || type.isInterface()
208: || (type.getComponentType() != null);
209: }
210:
211: /**
212: * Is a field of a type that can only be persisted through externalization?
213: */
214: public boolean isPersistableOnlyUsingExternalization(Class type,
215: Map classMap) {
216: boolean et = externalizedTypes.contains(type);
217: if (et)
218: externalizedTypes.remove(type);
219: boolean ans = !isPersistentType(type, classMap);
220: if (et)
221: externalizedTypes.add(type);
222: return ans;
223: }
224:
225: /**
226: * Is a field of type that should be persistent by default?
227: *
228: * @param type Type to check
229: * @param classMap Map with all persistent classes as keys
230: */
231: public boolean isDefaultPersistentType(Class type, Map classMap) {
232:
233: if (PM_PERSISTENT_TYPES.contains(type)
234: || classMap.containsKey(type) || isTypeRegistered(type)
235: || externalizedTypes.contains(type)) {
236: return true;
237: }
238: type = type.getComponentType();
239: if (type == null)
240: return false;
241: return PM_PERSISTENT_TYPES.contains(type)
242: || classMap.containsKey(type) || isTypeRegistered(type);
243: }
244:
245: /**
246: * Can a field of type with modifiers be considered persistent?
247: *
248: * @param type Type to check
249: * @param modifiers Field modifiers
250: * @param classMap Map with all persistent classes as keys
251: */
252: public boolean isPersistentField(Class type, int modifiers,
253: Map classMap) {
254: return isPersistentModifiers(modifiers)
255: && isPersistentType(type, classMap);
256: }
257:
258: /**
259: * Is the field of of those added by the enhancer?
260: */
261: public boolean isEnhancerAddedField(String fieldName) {
262: return
263:
264: fieldName.startsWith("jdo");
265: }
266:
267: /**
268: * Should f be considered persistent by default?
269: */
270: public boolean isDefaultPersistentField(ClassMetaData.FieldInfo f,
271: Map classMap) {
272: return !isEnhancerAddedField(f.getName())
273: && isDefaultPersistentModifiers(f.getModifiers())
274: && isDefaultPersistentType(f.getType(), classMap);
275: }
276:
277: /**
278: * Should f be considered persistent by default?
279: */
280: public boolean isDefaultPersistentField(Field f, Map classMap) {
281: return !isEnhancerAddedField(f.getName())
282: && isDefaultPersistentModifiers(f.getModifiers())
283: && isDefaultPersistentType(f.getType(), classMap);
284: }
285:
286: /**
287: * Can f be persisted?
288: */
289: public boolean isPersistableField(Field f, Map classMap) {
290: return !isEnhancerAddedField(f.getName())
291: && isPersistentModifiers(f.getModifiers());
292: }
293:
294: /**
295: * Should this field be part of the default fetch group by default?
296: */
297: public boolean isDefaultFetchGroupType(Class type) {
298: return DFG_TYPES.contains(type) || isTypeRegistered(type)
299: || externalizedTypes.contains(type);
300: }
301:
302: /**
303: * Should a field of type be embedded by default?
304: */
305: public boolean isEmbeddedType(Class type) {
306: if (DFG_TYPES.contains(type)
307: || externalizedTypes.contains(type)) {
308: return true;
309: }
310: type = type.getComponentType();
311: return type != null && EMBEDDED_ARRAY_TYPES.contains(type);
312: }
313:
314: /**
315: * What category does a field with the supplied attributes belong to?
316: *
317: * @param persistenceModifier Transactional fields are CATEGORY_TRANSACTIONAL
318: * @param type Type of field
319: * @param classMap Map with all persistent classes as keys
320: * @return One of the MDStatics.CATEGORY_xxx constants
321: * @see MDStatics
322: */
323: public int getFieldCategory(int persistenceModifier, Class type,
324: Map classMap) {
325: switch (persistenceModifier) {
326:
327: case MDStatics.PERSISTENCE_MODIFIER_PERSISTENT:
328: if (externalizedTypes.contains(type)) {
329: return MDStatics.CATEGORY_EXTERNALIZED;
330: } else if (type.getComponentType() != null) {
331: return MDStatics.CATEGORY_ARRAY;
332: } else if (classMap.containsKey(type)) {
333: if (type.isInterface())
334: return MDStatics.CATEGORY_POLYREF;
335: return MDStatics.CATEGORY_REF;
336: } else if (Collection.class.isAssignableFrom(type)) {
337: return MDStatics.CATEGORY_COLLECTION;
338:
339: } else if (Map.class.isAssignableFrom(type)) {
340: return MDStatics.CATEGORY_MAP;
341: } else if (type.isInterface() || type == Object.class) {
342: return MDStatics.CATEGORY_POLYREF;
343: } else if (!Map.class.isAssignableFrom(type)
344: && !Collection.class.isAssignableFrom(type)
345: && !isPersistentType(type, classMap)) {
346: return MDStatics.CATEGORY_EXTERNALIZED;
347: }
348: return MDStatics.CATEGORY_SIMPLE;
349:
350: case MDStatics.PERSISTENCE_MODIFIER_TRANSACTIONAL:
351: return MDStatics.CATEGORY_TRANSACTIONAL;
352:
353: case MDStatics.PERSISTENCE_MODIFIER_NONE:
354: return MDStatics.CATEGORY_NONE;
355: }
356: throw BindingSupportImpl.getInstance()
357: .internal(
358: "Bad persistence-modifier code: "
359: + persistenceModifier);
360: }
361:
362: /**
363: * Generate the classId for the class. This is a positive int computed
364: * from a hash of the class name with duplicates resolved by incrementing
365: * the id.
366: */
367: public int generateClassId(String qname) {
368: int classId = computeClassId(qname);
369: for (; classIdSet.contains(new Integer(classId)); classId = (classId + 1) & 0x7FFFFFFF) {
370: ;
371: }
372: classIdSet.add(new Integer(classId));
373: return classId;
374: }
375:
376: private int computeClassId(String className) {
377: int hash = 0;
378: try {
379: hashOut.writeUTF(className);
380: /* Compute the hash value for this class name.
381: * Use only the first 64 bits of the hash.
382: */
383: hashOut.flush();
384: byte hasharray[] = messageDigest.digest();
385: int len = hasharray.length;
386: if (len > 8)
387: len = 8;
388: for (int i = 0; i < len; i++) {
389: hash += (hasharray[i] & 255) << (i * 4);
390: }
391: hash &= 0x7FFFFFFF; // make sure it is always positive
392: } catch (IOException ignore) {
393: /* can't happen, but be deterministic anyway. */
394: hash = -1;
395: }
396: return hash;
397: }
398:
399: /**
400: * Register a store specific persistent type.
401: */
402: public void registerStoreType(Class type) {
403: storeTypes.add(type);
404: }
405:
406: /**
407: * Has a store specific persistent type been registered?
408: */
409: public boolean isTypeRegistered(Class type) {
410: return storeTypes.contains(type);
411: }
412:
413: /**
414: * Get the element type for a Collection if possible using the JDK 1.5
415: * generics API or null if not possible.
416: */
417: public static Class getGenericElementType(Field field) {
418: return getType(field, 0);
419: }
420:
421: /**
422: * Get the key type for a Map if possible using the JDK 1.5
423: * generics API or null if not possible.
424: */
425: public static Class getGenericKeyType(Field field) {
426: return getType(field, 0);
427: }
428:
429: /**
430: * Get the value type for a Map if possible using the JDK 1.5 generics
431: * API or null if not possible.
432: */
433: public static Class getGenericValueType(Field field) {
434: return getType(field, 1);
435: }
436:
437: /**
438: * Get the class type of Collections and Maps with jdk1.5 generics if at all
439: * possible
440: */
441: private static Class getType(Field field, int index) {
442: if (isJDK_1_5) {
443: // do reflection on the jdk1.5 reflection methods
444: Class clazz = null;
445: try {
446: Method methodGetGenericType = field.getClass()
447: .getMethod("getGenericType", new Class[] {});
448: if (methodGetGenericType == null)
449: return null;
450: Object type = methodGetGenericType.invoke(field,
451: new Object[] {});
452: if (type == null)
453: return null;
454: Method methodActualTypeArguments = type.getClass()
455: .getMethod("getActualTypeArguments",
456: new Class[] {});
457: if (methodActualTypeArguments == null)
458: return null;
459: Object typeArray = methodActualTypeArguments.invoke(
460: type, new Object[] {});
461: if (typeArray == null)
462: return null;
463: Object[] types = (Object[]) typeArray;
464: clazz = (Class) types[index];
465: } catch (Exception e) {
466: /*hide it all*/
467: }
468: return clazz;
469: } else {
470: return null;
471: }
472: }
473:
474: /**
475: * Register a type that is persisted using an externalizer.
476: */
477: public void registerExternalizedType(Class t) {
478: externalizedTypes.add(t);
479: }
480:
481: /**
482: * If this is a Basic type as defined by ejb3 spec.
483: */
484: public boolean isBasicType(Class t) {
485: return BASIC_TYPES.contains(t);
486: }
487:
488: }
|