/* * This file is part of the HyperGraphDB source distribution. This is copyrighted * software. For permitted uses, licensing options and redistribution, please see * the LicensingInformation file at the root level of the distribution. * * Copyright (c) 2005-2010 Kobrix Software, Inc. All rights reserved. */ package org.hypergraphdb.type; import com.googlecode.openbeans.PropertyDescriptor; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Collection; import java.util.Iterator; import java.util.Map; import org.hypergraphdb.HGException; import org.hypergraphdb.HGGraphHolder; import org.hypergraphdb.HGHandle; import org.hypergraphdb.HGHandleHolder; import org.hypergraphdb.HGTypeHolder; import org.hypergraphdb.HyperGraph; import org.hypergraphdb.HGTypeSystem; import org.hypergraphdb.annotation.AtomReference; import org.hypergraphdb.annotation.HGIgnore; import org.hypergraphdb.atom.AtomProjection; import org.hypergraphdb.atom.HGAtomRef; import org.hypergraphdb.type.javaprimitive.EnumType; public class DefaultJavaTypeMapper implements JavaTypeMapper { private HyperGraph graph; private HGAtomType defineComposite(HGTypeSystem typeSystem, Map<String, PropertyDescriptor> propertiesMap) { HGAbstractCompositeType compositeType = new HGAbstractCompositeType(); for (Iterator<PropertyDescriptor> i = propertiesMap.values().iterator(); i.hasNext();) { PropertyDescriptor desc = i.next(); // // Accept only properties that are both readable and writeable! // if (desc.getReadMethod() == null || desc.getWriteMethod() == null) continue; // // Retrieve or recursively create a new type for the nested bean. // Class<?> propType = desc.getPropertyType(); if (propType.isPrimitive()) propType = BonesOfBeans.wrapperEquivalentOf(propType); HGHandle propTypeHandle = typeSystem.getTypeHandle(propType); if (propTypeHandle == null) throw new HGException("Unable to get HyperGraph type for Java class " + propType.getName() + ": make sure it's default or 'link' constructible."); compositeType.addProjection(new HGAbstractCompositeType.Projection( desc.getName(), propTypeHandle)); } return compositeType; } private HGAtomRef.Mode getReferenceMode(Class<?> javaClass, PropertyDescriptor desc) { // // Retrieve or recursively create a new type for the nested // bean. // try { Field field = JavaTypeFactory.findDeclaredField(javaClass, desc.getName()); if (field == null) return null; AtomReference ann = (AtomReference)field.getAnnotation(AtomReference.class); if (ann == null) return null; String s = ann.value(); if ("hard".equals(s)) return HGAtomRef.Mode.hard; else if ("symbolic".equals(s)) return HGAtomRef.Mode.symbolic; else if ("floating".equals(s)) return HGAtomRef.Mode.floating; else throw new HGException("Wrong annotation value '" + s + "' for field '" + field.getName() + "' of class '" + javaClass.getName() + "', must be one of \"hard\", \"symbolic\" or \"floating\"."); } catch (Throwable ex) { // Perhaps issue a warning here if people are misspelling // unintentionally? Proper spelling is only useful for // annotation, so a warning/error should be really issued if // we find an annotation for a field that we can't make // use of? return null; } } public static boolean includeProperty(Class<?> javaClass, PropertyDescriptor desc) { Method reader = desc.getReadMethod(); Method writer = desc.getWriteMethod(); if (reader == null || writer == null) return false; if (reader.getAnnotation(HGIgnore.class) != null || writer.getAnnotation(HGIgnore.class) != null) return false; if (desc.getName().equals("atomHandle") && HGHandleHolder.class.isAssignableFrom(javaClass)) return false; if (desc.getName().equals("atomType") && HGTypeHolder.class.isAssignableFrom(javaClass)) return false; if (desc.getName().equals("hyperGraph") && HGGraphHolder.class.isAssignableFrom(javaClass)) return false; Field field = JavaTypeFactory.findDeclaredField(javaClass, desc.getName()); if (field != null && (field.getAnnotation(HGIgnore.class) != null || (field.getModifiers() & Modifier.TRANSIENT) != 0)) return false; return true; } @SuppressWarnings("unchecked") public HGAtomType defineHGType(Class<?> javaClass, HGHandle typeHandle) { HGTypeSystem typeSystem = graph.getTypeSystem(); if (javaClass == null) throw new NullPointerException( "JavaTypeFactory.getHGType: null beanClass parameter."); if (javaClass.isEnum()) return new EnumType((Class<Enum<?>>)javaClass); if (javaClass.isArray()) return new ArrayType(javaClass.getComponentType()); Map<String, PropertyDescriptor> descriptors = BonesOfBeans .getAllPropertyDescriptors(javaClass); boolean is_record = false; // // Determine whether the Java class has a "record" aspect to it: that // is, // whether there is at least one property that is both readable and // writeable. // for (PropertyDescriptor d : descriptors.values()) { if (d.getReadMethod() != null && d.getWriteMethod() != null && includeProperty(javaClass, d)) { is_record = true; break; } } boolean is_abstract = JavaTypeFactory.isAbstract(javaClass); boolean is_default_constructible = JavaTypeFactory.isDefaultConstructible(javaClass); boolean is_link = JavaTypeFactory.isLink(javaClass); boolean is_serializable = java.io.Serializable.class.isAssignableFrom(javaClass); is_record = is_record && (is_default_constructible || is_link); if (is_abstract) { if (!is_record) return new HGAbstractType(); else return defineComposite(typeSystem, descriptors); } else if (Map.class.isAssignableFrom(javaClass) && is_default_constructible) { return new MapType(new GenericObjectFactory<Map<Object, Object>>( (Class<Map<Object, Object>>)javaClass)); } else if (Collection.class.isAssignableFrom(javaClass) && is_default_constructible) { return new CollectionType( new GenericObjectFactory<Collection<Object>>((Class<Collection<Object>>)javaClass)); } else if (is_record) { RecordType recordType = new RecordType(); for (Iterator<PropertyDescriptor> i = descriptors.values().iterator(); i.hasNext();) { PropertyDescriptor desc = i.next(); // // Accept only properties that are both readable and writeable! // if (!includeProperty(javaClass, desc)) continue; Class<?> propType = desc.getPropertyType(); if (propType.isPrimitive()) propType = BonesOfBeans.wrapperEquivalentOf(propType); HGHandle valueTypeHandle = typeSystem.getTypeHandle(propType); if (valueTypeHandle == null) throw new HGException("Unable to get HyperGraph type for Java class " + propType.getName() + ": make sure it's default or 'link' constructible."); HGHandle slotHandle = JavaTypeFactory.getSlotHandle(graph, desc.getName(), valueTypeHandle); Slot slot = graph.get(slotHandle); recordType.addSlot(slotHandle); HGAtomRef.Mode refMode = getReferenceMode(javaClass, desc); if (refMode != null) typeSystem.getHyperGraph().add(new AtomProjection(typeHandle, slot.getLabel(), slot.getValueType(), refMode)); } return recordType; } // Should we check for serializable here? // Doesn't seem right since being a bean and/or map and/or collection // doesn't exclude // a class from being serializable and having important state that is // simply not being // exposed. // // Another option is some sort of empty type. Instances don't hold any // persistent data, but // they exist and are typed nevertheless. // // Serialization is important for some standard classes that are not // default constructible, such // as the new (since Java 5) java.util.EnumMap. // else if (is_serializable) return typeSystem.getAtomType(java.io.Serializable.class); else if (is_default_constructible || is_link) { // Nothing much more we can do...., perhaps some other default, // perhaps not. return new RecordType(); } else return null; } public HGAtomType getJavaBinding(HGHandle typeHandle, HGAtomType hgType, Class<?> javaClass) { if (hgType instanceof RecordType) { RecordType recType = (RecordType)hgType; recType.setThisHandle(typeHandle); return new JavaBeanBinding(typeHandle, recType, javaClass); } else if (hgType instanceof HGAbstractCompositeType) return new JavaAbstractBinding(typeHandle, (HGCompositeType)hgType, javaClass); else if (hgType instanceof HGAbstractType) return new JavaInterfaceBinding(typeHandle, hgType, javaClass); else return hgType; } public void setHyperGraph(HyperGraph graph) { this.graph = graph; } }