1package im.conversations.android.xmpp.model.data;
2
3import android.util.Log;
4import com.google.common.base.CaseFormat;
5import com.google.common.collect.Collections2;
6import com.google.common.collect.Iterables;
7import eu.siacs.conversations.Config;
8import im.conversations.android.annotation.XmlElement;
9import im.conversations.android.xmpp.model.Extension;
10import java.util.Collection;
11import java.util.Map;
12
13@XmlElement(name = "x")
14public class Data extends Extension {
15
16 private static final String FORM_TYPE = "FORM_TYPE";
17 private static final String FIELD_TYPE_HIDDEN = "hidden";
18 private static final String FORM_TYPE_SUBMIT = "submit";
19
20 public Data() {
21 super(Data.class);
22 }
23
24 public String getFormType() {
25 final var fields = this.getExtensions(Field.class);
26 final var formTypeField = Iterables.find(fields, f -> FORM_TYPE.equals(f.getFieldName()));
27 return Iterables.getFirst(formTypeField.getValues(), null);
28 }
29
30 public Collection<Field> getFields() {
31 return Collections2.filter(
32 this.getExtensions(Field.class), f -> !FORM_TYPE.equals(f.getFieldName()));
33 }
34
35 public Field getFieldByName(final String name) {
36 return Iterables.find(getFields(), f -> name.equals(f.getFieldName()), null);
37 }
38
39 public String getValue(final String name) {
40 final var field = getFieldByName(name);
41 return field == null ? null : field.getValue();
42 }
43
44 private void addField(final String name, final Object value) {
45 addField(name, value, null);
46 }
47
48 private void addField(final String name, final Object value, final String type) {
49 if (value == null) {
50 throw new IllegalArgumentException("Null values are not supported on data fields");
51 }
52 final var field = this.addExtension(new Field());
53 field.setFieldName(name);
54 if (type != null) {
55 field.setType(type);
56 }
57 if (value instanceof Collection<?> collection) {
58 Log.d(Config.LOGTAG, "submitting collection: " + collection);
59 for (final Object subValue : collection) {
60 if (subValue == null) {
61 Log.d(Config.LOGTAG, "null value in the values for " + name);
62 } else if (subValue instanceof String s) {
63 final var valueExtension = field.addExtension(new Value());
64 valueExtension.setContent(s);
65 } else {
66 throw new IllegalArgumentException(
67 String.format(
68 "%s is not a supported field value",
69 subValue.getClass().getSimpleName()));
70 }
71 }
72 } else {
73 final var valueExtension = field.addExtension(new Value());
74 if (value instanceof String s) {
75 valueExtension.setContent(s);
76 } else if (value instanceof Enum<?> e) {
77 valueExtension.setContent(
78 CaseFormat.UPPER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, e.toString()));
79 } else if (value instanceof Integer i) {
80 valueExtension.setContent(String.valueOf(i));
81 } else if (value instanceof Boolean b) {
82 valueExtension.setContent(Boolean.TRUE.equals(b) ? "1" : "0");
83 } else {
84 throw new IllegalArgumentException(
85 String.format(
86 "%s is not a supported field value",
87 value.getClass().getSimpleName()));
88 }
89 }
90 }
91
92 private void setFormType(final String formType) {
93 this.addField(FORM_TYPE, formType, FIELD_TYPE_HIDDEN);
94 }
95
96 public static Data of(final String formType, final Map<String, Object> values) {
97 final var data = new Data();
98 data.setType(FORM_TYPE_SUBMIT);
99 data.setFormType(formType);
100 for (final Map.Entry<String, Object> entry : values.entrySet()) {
101 data.addField(entry.getKey(), entry.getValue());
102 }
103 return data;
104 }
105
106 public Data submit(final Map<String, Object> values) {
107 final String formType = this.getFormType();
108 final var submit = new Data();
109 submit.setType(FORM_TYPE_SUBMIT);
110 if (formType != null) {
111 submit.setFormType(formType);
112 }
113 for (final Field existingField : this.getFields()) {
114 final var fieldName = existingField.getFieldName();
115 final Object submittedValue = values.get(fieldName);
116 if (submittedValue != null) {
117 Log.d(Config.LOGTAG, "submitting value " + fieldName + ": " + submittedValue);
118 submit.addField(fieldName, submittedValue);
119 } else {
120 Log.d(Config.LOGTAG, "staying with default for: " + fieldName);
121 submit.addExtension(existingField);
122 }
123 }
124 return submit;
125 }
126
127 private void setType(final String type) {
128 this.setAttribute("type", type);
129 }
130}