package de.jpaw.bonaparte.android; import java.math.BigDecimal; import java.math.BigInteger; import java.text.DecimalFormat; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; import org.joda.time.Instant; import org.joda.time.LocalDate; import org.joda.time.LocalDateTime; import org.joda.time.LocalTime; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import android.graphics.BitmapFactory; import android.view.View; import android.widget.Button; import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; import de.jpaw.bonaparte.core.AbstractMessageComposer; import de.jpaw.bonaparte.core.BonaCustom; import de.jpaw.bonaparte.core.BonaparteJsonEscaper; import de.jpaw.bonaparte.enums.BonaNonTokenizableEnum; import de.jpaw.bonaparte.enums.BonaTokenizableEnum; import de.jpaw.bonaparte.pojos.meta.AlphanumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.BasicNumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.BinaryElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.EnumDataItem; import de.jpaw.bonaparte.pojos.meta.FieldDefinition; import de.jpaw.bonaparte.pojos.meta.MiscElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.NumericElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.ObjectReference; import de.jpaw.bonaparte.pojos.meta.TemporalElementaryDataItem; import de.jpaw.bonaparte.pojos.meta.XEnumDataItem; import de.jpaw.enums.XEnum; import de.jpaw.util.ByteArray; /** Composer which is designed to work as a delegate for a foldingComposer. * Therefore object output itself is not supported. */ abstract public class LinearLayoutComposer extends AbstractMessageComposer<RuntimeException> implements AndroidViewComposer, View.OnClickListener { static private final Logger LOG = LoggerFactory.getLogger(LinearLayoutComposer.class); protected int rownum = -1; protected int column = 0; protected AndroidObjectClickListener onClickListener; protected Map<Integer,BonaCustom> buttonPayload; // this stores the object for an ID protected boolean expandObjects = true; // set to false if we sit behind a folding composer static protected final int ROW_FACTOR = 10000; // for the ID calculation, take the column + ROW_FACTOR * row protected boolean binaryAsBitmap = false; protected static final String [] BIGDECIMAL_FORMATS = { "#0", "#0.#", "#0.##", "#0.###", "#0.####", "#0.#####", "#0.######", "#0.#######", "#0.########", "#0.#########", "#0.##########", "#0.###########", "#0.############", "#0.#############", "#0.##############", "#0.###############", "#0.################", "#0.#################", "#0.##################" }; // methods required in superclass abstract CheckBox needCheckBox(FieldDefinition di); abstract TextView needTextView(FieldDefinition di); abstract ImageView needImageView(FieldDefinition di); abstract Button needButton(FieldDefinition di); abstract View needAny(FieldDefinition di); // creates a new composer, requires a subsequent newView() to set the rowWidget public LinearLayoutComposer() { } public void setExpandObjects(boolean doIt) { expandObjects = doIt; } public void setBinaryAsBitmap(boolean doIt) { binaryAsBitmap = doIt; } public int getId() { return column + ROW_FACTOR * rownum; } public int getIdFromRowAndColumn(int row, int column) { return column + ROW_FACTOR * row; } public void setOnClickListener(AndroidObjectClickListener onClickListener) { this.onClickListener = onClickListener; if (onClickListener == null) { buttonPayload = null; } else { buttonPayload = new HashMap<Integer,BonaCustom>(); } } // called if a button (to show an object) has been clicked. @Override public void onClick(View v) { if (onClickListener != null && buttonPayload != null) { int id = v.getId(); BonaCustom obj = buttonPayload.get(id); onClickListener.onClick(v, obj, id / ROW_FACTOR, id % ROW_FACTOR); } } /************************************************************************************************** * Serialization goes here **************************************************************************************************/ @Override public void writeNull(FieldDefinition di) { needAny(di); } @Override public void writeNullCollection(FieldDefinition di) { } @Override public void startTransmission() { rownum = -1; } @Override public void terminateTransmission() { } @Override public void terminateRecord() { } @Override public void writeSuperclassSeparator() { } @Override public void startRecord() { ++rownum; } private void newTextView(FieldDefinition di, String s) { TextView tv = needTextView(di); tv.setId(getId()); tv.setText(s != null ? s : ""); } // field type specific output functions // character @Override public void addField(MiscElementaryDataItem di, char c) { newTextView(di, String.valueOf(c)); } // ascii only (unicode uses different method) @Override public void addField(AlphanumericElementaryDataItem di, String s) { newTextView(di, s); } private void outputBigDecimal(BasicNumericElementaryDataItem di, BigDecimal n) { int digs = di.getDecimalDigits(); DecimalFormat df = new DecimalFormat(BIGDECIMAL_FORMATS[digs]); df.setMaximumFractionDigits(digs); df.setMinimumFractionDigits(digs); df.setGroupingUsed(true); newTextView(di, df.format(n)); } // decimal @Override public void addField(NumericElementaryDataItem di, BigDecimal n) { if (n != null) { outputBigDecimal(di, n); } else { writeNull(di); } } // byte @Override public void addField(BasicNumericElementaryDataItem di, byte n) { if (di.getDecimalDigits() > 0) outputBigDecimal(di, BigDecimal.valueOf(n, di.getDecimalDigits())); else newTextView(di, Byte.toString(n)); } // short @Override public void addField(BasicNumericElementaryDataItem di, short n) { if (di.getDecimalDigits() > 0) outputBigDecimal(di, BigDecimal.valueOf(n, di.getDecimalDigits())); else newTextView(di, Short.toString(n)); } // integer @Override public void addField(BasicNumericElementaryDataItem di, int n) { if (di.getDecimalDigits() > 0) outputBigDecimal(di, BigDecimal.valueOf(n, di.getDecimalDigits())); else newTextView(di, Integer.toString(n)); } // long @Override public void addField(BasicNumericElementaryDataItem di, long n) { if (di.getDecimalDigits() > 0) outputBigDecimal(di, BigDecimal.valueOf(n, di.getDecimalDigits())); else newTextView(di, Long.toString(n)); } // int(n) @Override public void addField(BasicNumericElementaryDataItem di, BigInteger n) { if (n != null) { newTextView(di, n.toString()); } else { writeNull(di); } } // boolean @Override public void addField(MiscElementaryDataItem di, boolean b) { needCheckBox(di).setChecked(b); } // float @Override public void addField(BasicNumericElementaryDataItem di, float f) { newTextView(di, Float.toString(f)); } // double @Override public void addField(BasicNumericElementaryDataItem di, double d) { newTextView(di, Double.toString(d)); } // UUID @Override public void addField(MiscElementaryDataItem di, UUID n) { if (n != null) { newTextView(di, n.toString()); } else { writeNull(di); } } // ByteArray: initial quick & dirty implementation @Override public void addField(BinaryElementaryDataItem di, ByteArray b) { if (b != null) { if (binaryAsBitmap) { byte [] rawData = b.getBytes(); needImageView(di).setImageBitmap(BitmapFactory.decodeByteArray(rawData, 0, rawData.length)); } else { newTextView(di, "(Binary)"); } } else { writeNull(di); } } // raw @Override public void addField(BinaryElementaryDataItem di, byte[] b) { if (b != null) { if (binaryAsBitmap) { needImageView(di).setImageBitmap(BitmapFactory.decodeByteArray(b, 0, b.length)); } else { newTextView(di, "(Binary)"); } } else { writeNull(di); } } // converters for DAY und TIMESTAMP @Override public void addField(TemporalElementaryDataItem di, LocalDate t) { if (t != null) { newTextView(di, t.toString()); } else { writeNull(di); } } @Override public void addField(TemporalElementaryDataItem di, LocalDateTime t) { if (t != null) { newTextView(di, t.toString()); } else { writeNull(di); } } @Override public void addField(TemporalElementaryDataItem di, LocalTime t) { if (t != null) { newTextView(di, t.toString()); } else { writeNull(di); } } @Override public void addField(TemporalElementaryDataItem di, Instant t) { if (t != null) { newTextView(di, t.toString()); } else { writeNull(di); } } @Override public void startMap(FieldDefinition di, int currentMembers) { } @Override public void startArray(FieldDefinition di, int currentMembers, int sizeOfElement) { } @Override public void terminateArray() { } @Override public void terminateMap() { } @Override public void startObject(ObjectReference di, BonaCustom obj) { } @Override public void terminateObject(ObjectReference di, BonaCustom obj) { } /** Adding objects will lead to column misalignment if the objects itself are null. */ @Override public void addField(ObjectReference di, BonaCustom obj) { LOG.info("adding OBJECT in row {}, expand = {}", rownum, expandObjects); if (expandObjects) { if (obj != null) { obj.serializeSub(this); } } else { // this is a single column. Display as a button, with onClickListener to set a callback Button b = needButton(di); if (buttonPayload != null) buttonPayload.put(b.getId(), obj); LOG.info(" button id = {}, object is {}", b.getId(), obj == null ? "(null)" : obj.ret$PQON()); if (obj == null) { b.setText("null"); b.setEnabled(false); } else { b.setText("..."); b.setEnabled(true); } } } // enum with numeric expansion: delegate to Null/Int @Override public void addEnum(EnumDataItem di, BasicNumericElementaryDataItem ord, BonaNonTokenizableEnum n) { if (n == null) { writeNull(ord); } else { addField(ord, n.ordinal()); } } // enum with alphanumeric expansion: delegate to Null/String @Override public void addEnum(EnumDataItem di, AlphanumericElementaryDataItem token, BonaTokenizableEnum n) { if (n == null) { writeNull(token); } else { addField(token, n.getToken()); } } // xenum with alphanumeric expansion: delegate to Null/String @Override public void addEnum(XEnumDataItem di, AlphanumericElementaryDataItem token, XEnum<?> n) { if (n == null) { writeNull(token); } else { addField(token, n.getToken()); } } @Override public boolean addExternal(ObjectReference di, Object obj) { if (obj != null) { newTextView(di, obj.toString()); } else { writeNull(di); } return true; // for the UI display, use the string representation by default } @Override public void addField(ObjectReference di, Map<String, Object> obj) { if (obj == null) writeNull(di); else newTextView(di, BonaparteJsonEscaper.asJson(obj)); } @Override public void addField(ObjectReference di, List<Object> obj) { if (obj == null) writeNull(di); else newTextView(di, BonaparteJsonEscaper.asJson(obj)); } @Override public void addField(ObjectReference di, Object obj) { if (obj == null) writeNull(di); else newTextView(di, BonaparteJsonEscaper.asJson(obj)); } }