package de.jpaw.bonaparte.core; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.ImmutableMap; import de.jpaw.bonaparte.pojos.meta.FoldingStrategy; import de.jpaw.bonaparte.pojos.meta.ObjectReference; import de.jpaw.bonaparte.pojos.meta.ParsedFoldingComponent; /** Delegates most output to the delegateComposer, but uses a permutation/selection of fields for the object output. */ public class FoldingComposer<E extends Exception> extends DelegatingBaseComposer<E> { private static final Logger LOGGER = LoggerFactory.getLogger(FoldingComposer.class); public static final Map<Class<? extends BonaCustom>, List<String>> EMPTY_MAPPING = ImmutableMap.of(); private final Map<Class<? extends BonaCustom>, List<String>> mapping; private final Map<Class<? extends BonaCustom>, List<ParsedFoldingComponent>> parsedMapping; private final FoldingStrategy errorStrategy; private final List<String> bonaPortableMapping; private final List<String> bonaCustomMapping; public FoldingComposer(MessageComposer<E> delegateComposer, Map<Class<? extends BonaCustom>, List<String>> mapping, FoldingStrategy errorStrategy) { super(delegateComposer); this.mapping = mapping; this.parsedMapping = new HashMap<Class<? extends BonaCustom>, List<ParsedFoldingComponent>>(32); this.errorStrategy = errorStrategy; this.bonaPortableMapping = mapping.get(BonaPortable.class); this.bonaCustomMapping = mapping.get(BonaCustom.class); } /** Convenience method to write a list of fieldnames to some output. */ public static <X extends Exception> void writeFieldsToDelegate(MessageComposer<X> writer, BonaCustom obj, List<String> fieldnames) throws X { Map<Class<? extends BonaCustom>, List<String>> mapping = Collections.<Class<? extends BonaCustom>, List<String>>singletonMap(BonaPortable.class, fieldnames); new FoldingComposer<X>(writer, mapping, FoldingStrategy.FORWARD_OBJECTS).writeRecord(obj); } @Override public void writeSuperclassSeparator() throws E { // delegateComposer.writeSuperclassSeparator(); // the folded structure is flat } private List<ParsedFoldingComponent> createParsedFieldList(ObjectReference di, BonaCustom obj, Class <? extends BonaCustom> objClass) throws E { // get the original mapping... // if only one mapping entry has been provided, and that is for a BonaCustom in general, this is straightforward. List<String> fieldList = mapping.get(objClass); if (fieldList == null) { switch (errorStrategy) { case SKIP_UNMAPPED: return null; case FULL_OUTPUT: delegateComposer.startObject(di, obj); obj.serializeSub(this); // this or delegateComposer? delegateComposer.terminateObject(di, obj); return null; case TRY_SUPERCLASS: case SUPERCLASS_OR_FULL: case FORWARD_OBJECTS: Class <?> superclass; while ((superclass = objClass.getSuperclass()) != null) { if (BonaCustom.class.isAssignableFrom(superclass)) { objClass = (Class<? extends BonaCustom>)superclass; fieldList = mapping.get(objClass); if (fieldList != null) { LOGGER.debug("Mapping for class {} found at superclass {}", obj.getClass().getCanonicalName(), objClass.getCanonicalName()); break; } } else { if (bonaPortableMapping != null) { fieldList = bonaPortableMapping; } else if (bonaCustomMapping != null) { fieldList = bonaCustomMapping; } else { if (errorStrategy != FoldingStrategy.TRY_SUPERCLASS) { // all others default to "full output" delegateComposer.startObject(di, obj); obj.serializeSub(this); // this or delegateComposer? delegateComposer.terminateObject(di, obj); } return null; // skip, no mapping found even with recursion } break; } } } } // if fieldList is still null, then we must have reached the top class (superclass == null) if (fieldList == null) fieldList = bonaPortableMapping; // fallback tpo avoid NPE below... // fieldList is not null now. // parse it List<ParsedFoldingComponent> pl = new ArrayList<ParsedFoldingComponent>(fieldList.size()); for (String f: fieldList) { // create an entry in pl pl.add(createRecursiveFoldingComponent(f)); } parsedMapping.put(obj.getClass(), pl); LOGGER.debug("Created parsed mapping for class {}", obj.getClass().getCanonicalName()); return pl; } public static ParsedFoldingComponent createRecursiveFoldingComponent(String f) { ParsedFoldingComponent pfc = new ParsedFoldingComponent(); int dotIndex = f.indexOf('.'); if (dotIndex < 0) { pfc.setFieldname(f); pfc.setComponent(null); } else { pfc.setFieldname(f.substring(0, dotIndex)); pfc.setComponent(createRecursiveFoldingComponent(f.substring(dotIndex+1))); } // parse possible indexes, numeric or alphanumeric pfc.setIndex(-1); // default: all nont-existing dotIndex = pfc.getFieldname().indexOf('['); if (dotIndex >= 0) { String indexStr = pfc.getFieldname().substring(dotIndex+1); pfc.setFieldname(pfc.getFieldname().substring(0, dotIndex)); dotIndex = indexStr.indexOf(']'); if (dotIndex != indexStr.length()-1) { LOGGER.error("Unparseable index for field {}: [{}], ignored", pfc.getFieldname(), indexStr); return pfc; } indexStr = indexStr.substring(0, dotIndex); pfc.setAlphaIndex(indexStr); // try to parse a numeric index if (Character.isDigit(indexStr.charAt(0))) { try { pfc.setIndex(Integer.parseInt(indexStr)); } catch (Exception e) { LOGGER.error("Cannot parse numeric index for field {}: [{}], ignored", pfc.getFieldname(), indexStr); return pfc; } } } // pfc.setNumDescends(-1); return pfc; } @Override public void addField(ObjectReference di, BonaCustom obj) throws E { if (obj == null) { writeNull(di); } else { if (errorStrategy == FoldingStrategy.FORWARD_OBJECTS && di != StaticMeta.OUTER_BONAPORTABLE) { // purpose is to output objects as they are delegateComposer.addField(di, obj); return; } // only write the fields selectively // first, optionally create a cached mapping Class <? extends BonaCustom> objClass = obj.getClass(); List<ParsedFoldingComponent> parsedFieldList = parsedMapping.get(objClass); if (parsedFieldList == null) { parsedFieldList = createParsedFieldList(di, obj, objClass); if (parsedFieldList == null) return; } // some fieldList has been found if we end up here // now perform the output, used the parsed list startObject(di, obj); for (ParsedFoldingComponent pfc: parsedFieldList) obj.foldedOutput(this, pfc); terminateObject(di, obj); } } }