package de.jpaw.bonaparte.util; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import de.jpaw.bonaparte.core.BonaPortable; import de.jpaw.bonaparte.core.BonaPortableClass; import de.jpaw.bonaparte.core.DataAndMeta; import de.jpaw.bonaparte.core.FoldingComposer; import de.jpaw.bonaparte.core.ListComposer; import de.jpaw.bonaparte.core.ListMetaComposer; import de.jpaw.bonaparte.pojos.meta.ClassDefinition; import de.jpaw.bonaparte.pojos.meta.ExternalClassDefinition; import de.jpaw.bonaparte.pojos.meta.FieldDefinition; import de.jpaw.bonaparte.pojos.meta.ObjectReference; import de.jpaw.bonaparte.pojos.meta.ParsedFoldingComponent; /** Utility method to get a fields from BonaPortables via pathname. */ public class FieldGetter { private static final Logger LOG = LoggerFactory.getLogger(FieldGetter.class); // configuration constant final private static boolean DEFAULT_AUTOSKIP_ADAPTERS = true; // do not return adapter classes but their contents /** Retrieves the class instance for the previously static methods, the BClass.getInstance, for a given class. * Throws an exception if the provided class is not a BonaPortable or the BClass cannot be obtained. */ public static BonaPortableClass<?> getBClass(Class<?> bonaPortableClass) { if (!BonaPortable.class.isAssignableFrom(bonaPortableClass)) { LOG.error(bonaPortableClass.getCanonicalName() + " is not a BonaPortable"); throw new IllegalArgumentException(); } try { // new method since bonaparte-3.2.4 Method method = bonaPortableClass.getMethod("class$BonaPortableClass"); return (BonaPortableClass<?>)method.invoke(null); // removed more complicated lookup of 3.2.3 // Class<?> [] innerClasses = bonaPortableClass.getDeclaredClasses(); // for (int i = 0; i < innerClasses.length; ++i) { // if (BonaPortableClass.class.isAssignableFrom(innerClasses[i])) { // Method method = innerClasses[i].getMethod("getInstance"); // return (BonaPortableClass<?>)method.invoke(null); // } // } // LOG.error(bonaPortableClass.getCanonicalName() + " does not define an inner BonaPortable class"); // throw new IllegalArgumentException(); } catch (Exception e) { LOG.error(bonaPortableClass.getCanonicalName() + " failed to return its BonaPortableClass via reflection: ", e); throw new RuntimeException(e); } } public static List<Object> getFields(BonaPortable obj, List<String> fieldnames) { return getFieldsOrObjects(obj, fieldnames, false, !DEFAULT_AUTOSKIP_ADAPTERS); } /** Get a single field, reusing the get multiple implementation. */ public static Object getField(BonaPortable obj, String fieldname) { List<Object> target = getFields(obj, Collections.singletonList(fieldname)); // the list with the result return (target.size() == 0) ? null : target.get(0); } public static List<Object> getFieldsOrObjects(BonaPortable obj, List<String> fieldnames) { return getFieldsOrObjects(obj, fieldnames, true, !DEFAULT_AUTOSKIP_ADAPTERS); } public static List<Object> getFieldsOrObjects(BonaPortable obj, List<String> fieldnames, boolean keepObjects, boolean keepExternals) { if (obj == null || fieldnames == null) return null; // step 1: construct the output buffer List<Object> target = new ArrayList<Object>(fieldnames.size()); // the list to write the field into // step 2: create and chain the message composers ListComposer writer = new ListComposer(target, false, keepObjects, keepExternals); FoldingComposer.writeFieldsToDelegate(writer, obj, fieldnames); return target; } /** Get a single field, reusing the get multiple implementation. */ public static Object getFieldOrObj(BonaPortable obj, String fieldname) { List<Object> target = getFieldsOrObjects(obj, Collections.singletonList(fieldname)); // the list with the result return (target.size() == 0) ? null : target.get(0); } /** Get a single field, alternate implementation, going directly to ListComposer (should be faster, less object allocations). */ public static Object getSingleField(BonaPortable obj, String fieldname) { ParsedFoldingComponent pfc = FoldingComposer.createRecursiveFoldingComponent(fieldname); List<Object> target = new ArrayList<Object>(1); ListComposer delegate = new ListComposer(target, false, true, !DEFAULT_AUTOSKIP_ADAPTERS); obj.foldedOutput(delegate, pfc); return (target.size() == 0) ? null : target.get(0); } /** Get a single field, alternate implementation, going directly to ListComposer (should be faster, less object allocations). */ public static DataAndMeta getSingleFieldWithMeta(BonaPortable obj, String fieldname) { ParsedFoldingComponent pfc = FoldingComposer.createRecursiveFoldingComponent(fieldname); List<DataAndMeta> target = new ArrayList<DataAndMeta>(1); ListMetaComposer delegate = new ListMetaComposer(target, false, false, !DEFAULT_AUTOSKIP_ADAPTERS); obj.foldedOutput(delegate, pfc); return (target.size() == 0) ? null : target.get(0); } /** Finds a field in a class or its parent. Returns null if not found. */ public static FieldDefinition lookupField(ClassDefinition cls, String name) { while (cls != null) { // find in this class for (FieldDefinition f : cls.getFields()) if (f.getName().equals(name)) return f; // not found here, maybe in parent? cls = cls.getParentMeta(); } // no more parent return null; } /** Returns the first field defined in a superclass or this class, or null, if neither this class nor a superclass defines a field. */ public static FieldDefinition getFirstField(ClassDefinition cls) { FieldDefinition f = cls.getParentMeta() == null ? null : getFirstField(cls.getParentMeta()); if (f != null) return f; // found a field in a parent if (cls.getFields().size() == 0) return null; return cls.getFields().get(0); } /** Retrieves just the meta data. Throws an exception if the path is invalid. * This is a static path resolver, pathnames must reference valid paths for any valid data. */ public static FieldDefinition getFieldDefinitionForPathname(ClassDefinition cls, String pathname) throws UtilException { return getFieldDefinitionForPathname(cls, pathname, DEFAULT_AUTOSKIP_ADAPTERS); } /** Retrieves just the meta data. Throws an exception if the path is invalid. * This is a static path resolver, pathnames must reference valid paths for any valid data. */ public static FieldDefinition getFieldDefinitionForPathname(ClassDefinition cls, String pathname, final boolean autoSkipAdapters) throws UtilException { // if the pathname contains no dot, the only containing object is the root for (;;) { String currentElement; final int lastDot = pathname.indexOf('.'); if (lastDot < 0) { // this was the last component currentElement = pathname; } else { currentElement = pathname.substring(0, lastDot); pathname = pathname.substring(lastDot+1); } // exclude any array or map index final int bracketPos = currentElement.indexOf('['); if (bracketPos >= 0) currentElement = currentElement.substring(0, bracketPos); // now look up the name in the field list FieldDefinition fld = lookupField(cls, currentElement); if (fld == null) throw new UtilException(UtilException.PATH_COMPONENT_NOT_FOUND, currentElement + " in " + cls.ret$PQON()); // check for adapters... ObjectReference oRef; if (autoSkipAdapters) { for (;;) { oRef = fld instanceof ObjectReference ? (ObjectReference)fld : null; if (oRef != null && oRef.getLowerBound() != null && oRef.getLowerBound() instanceof ExternalClassDefinition) { ExternalClassDefinition extRef = (ExternalClassDefinition)oRef.getLowerBound(); if (extRef.getIsSingleField()) { // forward fld to the first fld of the adapter... fld = getFirstField(extRef); if (fld == null) throw new UtilException(UtilException.ADAPTER_WITHOUT_FIELDS, extRef.getClassRef().getCanonicalName()); continue; // iterative check for adapters, in case of nested adapters... } } break; // any condition not fulfilled: stop iterating } } else { oRef = fld instanceof ObjectReference ? (ObjectReference)fld : null; // just a one time evaluation } if (lastDot < 0) // this was the result return fld; // otherwise, must descend further. For that, fld must be a class reference if (oRef == null) throw new UtilException(UtilException.DESCEND_TO_NON_REFERENCE, currentElement + " in " + cls.ret$PQON()); if (oRef.getLowerBound() == null) throw new UtilException(UtilException.DESCEND_TO_GENERIC_OBJECT, currentElement + " in " + cls.ret$PQON()); cls = oRef.getSecondaryLowerBound() == null ? oRef.getLowerBound() : oRef.getSecondaryLowerBound(); // continue... } } }