Refresh data on "Back", allow configuration of View + border
[jvcard.git] / src / be / nikiroo / jvcard / Contact.java
CommitLineData
a3b510ab
NR
1package be.nikiroo.jvcard;
2
26d2bd05
NR
3import java.io.File;
4import java.io.IOException;
5import java.util.ArrayList;
a3b510ab
NR
6import java.util.HashMap;
7import java.util.LinkedList;
8import java.util.List;
9import java.util.Map;
e253bd50 10import java.util.UUID;
a3b510ab
NR
11
12import be.nikiroo.jvcard.parsers.Format;
13import be.nikiroo.jvcard.parsers.Parser;
7da41ecd 14import be.nikiroo.jvcard.resources.StringUtils;
a3b510ab
NR
15
16/**
17 * A contact is the information that represent a contact person or organisation.
18 *
19 * @author niki
20 *
21 */
26d2bd05 22public class Contact extends BaseClass<Data> {
a3b510ab
NR
23 private int nextBKey = 1;
24 private Map<Integer, Data> binaries;
a3b510ab
NR
25
26 /**
27 * Create a new Contact from the given information. Note that the BKeys data
28 * will be reset.
29 *
30 * @param content
31 * the information about the contact
32 */
33 public Contact(List<Data> content) {
26d2bd05 34 super(load(content));
a3b510ab
NR
35 updateBKeys(true);
36 }
37
a3b510ab
NR
38 /**
39 * Return the preferred Data field with the given name, or NULL if none.
40 *
41 * @param name
42 * the name to look for
43 * @return the Data field, or NULL
44 */
45 public Data getPreferredData(String name) {
46 Data first = null;
47 for (Data data : getData(name)) {
48 if (first == null)
49 first = data;
3634193b
NR
50
51 if (data.isPreferred())
52 return data;
a3b510ab
NR
53 }
54
55 return first;
56 }
57
58 /**
59 * Return the value of the preferred data field with this name, or NULL if
60 * none (you cannot differentiate a NULL value and no value).
61 *
62 * @param name
63 * the name to look for
64 * @return the value (which can be NULL), or NULL
65 */
66 public String getPreferredDataValue(String name) {
67 Data data = getPreferredData(name);
68 if (data != null && data.getValue() != null)
69 return data.getValue().trim();
70 return null;
71 }
72
73 /**
74 * Get the Data fields that share the given name.
75 *
76 * @param name
77 * the name to ook for
78 * @return a list of Data fields with this name
79 */
80 public List<Data> getData(String name) {
81 List<Data> found = new LinkedList<Data>();
82
26d2bd05 83 for (Data data : this) {
a3b510ab
NR
84 if (data.getName().equals(name))
85 found.add(data);
86 }
87
88 return found;
89 }
90
91 /**
92 * Return a {@link String} representation of this contact.
93 *
94 * @param format
95 * the {@link Format} to use
96 * @param startingBKey
97 * the starting BKey or -1 for no BKeys
98 * @return the {@link String} representation
99 */
100 public String toString(Format format, int startingBKey) {
101 updateBKeys(false);
cf77cb35
NR
102
103 StringBuilder builder = new StringBuilder();
104 for (String line : Parser.toStrings(this, format, startingBKey)) {
105 builder.append(line);
106 builder.append("\r\n");
107 }
108
109 return builder.toString();
a3b510ab
NR
110 }
111
0b0b2b0f
NR
112 /**
113 * Return a {@link String} representation of this contact formated
114 * accordingly to the given format.
115 *
116 * The format is basically a list of field names separated by a pipe and
117 * optionally parametrised. The parameters allows you to:
118 * <ul>
119 * <li>@x: show only a present/not present info</li>
120 * <li>@n: limit the size to a fixed value 'n'</li>
121 * <li>@+: expand the size of this field as much as possible</li>
122 * </ul>
123 *
124 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
125 *
126 * @param format
127 * the format to use
128 *
129 * @return the {@link String} representation
130 */
131 public String toString(String format) {
296a0b75 132 return toString(format, "|", null, -1, true, false);
0b0b2b0f
NR
133 }
134
a3b510ab
NR
135 /**
136 * Return a {@link String} representation of this contact formated
137 * accordingly to the given format.
138 *
139 * The format is basically a list of field names separated by a pipe and
140 * optionally parametrised. The parameters allows you to:
141 * <ul>
d56a0ad4 142 * <li>@x: (the 'x' is the letter 'x') show only a present/not present info</li>
a3b510ab
NR
143 * <li>@n: limit the size to a fixed value 'n'</li>
144 * <li>@+: expand the size of this field as much as possible</li>
145 * </ul>
146 *
147 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
148 *
149 * @param format
150 * the format to use
151 * @param separator
152 * the separator {@link String} to use between fields
0b0b2b0f
NR
153 * @param padding
154 * the {@link String} to use for left and right padding
a3b510ab
NR
155 * @param width
156 * a fixed width or -1 for "as long as needed"
157 *
296a0b75
NR
158 * @param unicode
159 * allow Uniode or only ASCII characters
160 *
a3b510ab
NR
161 * @return the {@link String} representation
162 */
0b0b2b0f 163 public String toString(String format, String separator, String padding,
296a0b75 164 int width, boolean unicode, boolean removeAccents) {
9c8baf0c 165 StringBuilder builder = new StringBuilder();
a3b510ab 166
296a0b75
NR
167 for (String str : toStringArray(format, separator, padding, width,
168 unicode)) {
0b0b2b0f
NR
169 builder.append(str);
170 }
a3b510ab 171
0b0b2b0f
NR
172 return builder.toString();
173 }
a3b510ab 174
0b0b2b0f
NR
175 /**
176 * Return a {@link String} representation of this contact formated
177 * accordingly to the given format, part by part.
178 *
179 * The format is basically a list of field names separated by a pipe and
180 * optionally parametrised. The parameters allows you to:
181 * <ul>
182 * <li>@x: show only a present/not present info</li>
183 * <li>@n: limit the size to a fixed value 'n'</li>
184 * <li>@+: expand the size of this field as much as possible</li>
185 * </ul>
186 *
187 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
188 *
189 * @param format
190 * the format to use
191 * @param separator
192 * the separator {@link String} to use between fields
193 * @param padding
194 * the {@link String} to use for left and right padding
195 * @param width
196 * a fixed width or -1 for "as long as needed"
197 *
296a0b75
NR
198 * @param unicode
199 * allow Uniode or only ASCII characters
200 *
0b0b2b0f
NR
201 * @return the {@link String} representation
202 */
203 public String[] toStringArray(String format, String separator,
296a0b75 204 String padding, int width, boolean unicode) {
0b0b2b0f
NR
205 if (width > -1) {
206 int numOfFields = format.split("\\|").length;
207 if (separator != null)
208 width -= (numOfFields - 1) * separator.length();
209 if (padding != null)
210 width -= (numOfFields) * (2 * padding.length());
211
212 if (width < 0)
213 width = 0;
a3b510ab
NR
214 }
215
0b0b2b0f
NR
216 List<String> str = new LinkedList<String>();
217
218 boolean first = true;
296a0b75 219 for (String s : toStringArray(format, width, unicode)) {
0b0b2b0f
NR
220 if (!first) {
221 str.add(separator);
222 }
223
224 if (padding != null)
225 str.add(padding + s + padding);
226 else
227 str.add(s);
228
229 first = false;
9c8baf0c
NR
230 }
231
0b0b2b0f 232 return str.toArray(new String[] {});
9c8baf0c
NR
233 }
234
235 /**
236 * Return a {@link String} representation of this contact formated
237 * accordingly to the given format, part by part.
238 *
239 * The format is basically a list of field names separated by a pipe and
0b0b2b0f
NR
240 * optionally parametrised. The parameters allows you to:
241 * <ul>
242 * <li>@x: show only a present/not present info</li>
243 * <li>@n: limit the size to a fixed value 'n'</li>
244 * <li>@+: expand the size of this field as much as possible</li>
245 * </ul>
246 *
247 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
9c8baf0c
NR
248 *
249 * @param format
250 * the format to use
251 * @param width
252 * a fixed width or -1 for "as long as needed"
296a0b75
NR
253 * @param unicode
254 * allow Uniode or only ASCII characters
255 *
9c8baf0c
NR
256 * @return the {@link String} representation
257 */
296a0b75 258 public String[] toStringArray(String format, int width, boolean unicode) {
9c8baf0c
NR
259 List<String> str = new LinkedList<String>();
260
261 String[] formatFields = format.split("\\|");
262 String[] values = new String[formatFields.length];
263 Boolean[] expandedFields = new Boolean[formatFields.length];
264 Boolean[] fixedsizeFields = new Boolean[formatFields.length];
265 int numOfFieldsToExpand = 0;
266 int totalSize = 0;
267
268 if (width == 0) {
0b0b2b0f
NR
269 for (int i = 0; i < formatFields.length; i++) {
270 str.add("");
271 }
bcb54330 272
9c8baf0c
NR
273 return str.toArray(new String[] {});
274 }
275
a3b510ab
NR
276 for (int i = 0; i < formatFields.length; i++) {
277 String field = formatFields[i];
278
279 int size = -1;
280 boolean binary = false;
281 boolean expand = false;
282
283 if (field.contains("@")) {
284 String[] opts = field.split("@");
285 if (opts.length > 0)
286 field = opts[0];
287 for (int io = 1; io < opts.length; io++) {
288 String opt = opts[io];
289 if (opt.equals("x")) {
290 binary = true;
291 } else if (opt.equals("+")) {
292 expand = true;
293 numOfFieldsToExpand++;
294 } else {
295 try {
296 size = Integer.parseInt(opt);
297 } catch (Exception e) {
298 }
299 }
300 }
301 }
302
303 String value = getPreferredDataValue(field);
296a0b75 304 if (value == null) {
a3b510ab 305 value = "";
296a0b75
NR
306 } else {
307 value = StringUtils.sanitize(value, unicode);
308 }
a3b510ab
NR
309
310 if (size > -1) {
296a0b75 311 value = StringUtils.padString(value, size);
a3b510ab
NR
312 }
313
314 expandedFields[i] = expand;
315 fixedsizeFields[i] = (size > -1);
316
317 if (binary) {
318 if (value != null && !value.equals(""))
319 values[i] = "x";
320 else
321 values[i] = " ";
322 totalSize++;
323 } else {
324 values[i] = value;
325 totalSize += value.length();
326 }
327 }
9c8baf0c 328
a3b510ab
NR
329 if (width > -1 && totalSize > width) {
330 int toDo = totalSize - width;
331 for (int i = fixedsizeFields.length - 1; toDo > 0 && i >= 0; i--) {
332 if (!fixedsizeFields[i]) {
333 int valueLength = values[i].length();
334 if (valueLength > 0) {
335 if (valueLength >= toDo) {
336 values[i] = values[i].substring(0, valueLength
337 - toDo);
338 toDo = 0;
339 } else {
340 values[i] = "";
341 toDo -= valueLength;
342 }
343 }
344 }
345 }
346
347 totalSize = width + toDo;
348 }
9c8baf0c 349
a3b510ab
NR
350 if (width > -1 && numOfFieldsToExpand > 0) {
351 int availablePadding = width - totalSize;
352
353 if (availablePadding > 0) {
354 int padPerItem = availablePadding / numOfFieldsToExpand;
355 int remainder = availablePadding % numOfFieldsToExpand;
356
357 for (int i = 0; i < values.length; i++) {
358 if (expandedFields[i]) {
359 if (remainder > 0) {
296a0b75
NR
360 values[i] = values[i]
361 + StringUtils.padString("", remainder);
a3b510ab
NR
362 remainder = 0;
363 }
364 if (padPerItem > 0) {
296a0b75
NR
365 values[i] = values[i]
366 + StringUtils.padString("", padPerItem);
a3b510ab
NR
367 }
368 }
369 }
370
371 totalSize = width;
372 }
373 }
a3b510ab 374
9c8baf0c
NR
375 int currentSize = 0;
376 for (int i = 0; i < values.length; i++) {
377 currentSize += addToList(str, values[i], currentSize, width);
a3b510ab
NR
378 }
379
9c8baf0c 380 return str.toArray(new String[] {});
a3b510ab
NR
381 }
382
a3b510ab
NR
383 /**
384 * Update the information from this contact with the information in the
385 * given contact. Non present fields will be removed, new fields will be
386 * added, BKey'ed fields will be completed with the binary information known
387 * by this contact.
388 *
389 * @param vc
390 * the contact with the newer information and optional BKeys
391 */
392 public void updateFrom(Contact vc) {
393 updateBKeys(false);
394
26d2bd05 395 List<Data> newDatas = new LinkedList<Data>(vc);
a3b510ab
NR
396 for (int i = 0; i < newDatas.size(); i++) {
397 Data data = newDatas.get(i);
398 int bkey = Parser.getBKey(data);
399 if (bkey >= 0) {
400 if (binaries.containsKey(bkey)) {
401 newDatas.set(i, binaries.get(bkey));
402 }
403 }
404 }
405
26d2bd05 406 replaceListContent(newDatas);
a3b510ab 407 this.nextBKey = vc.nextBKey;
78e4af97
NR
408 }
409
e253bd50
NR
410 @Override
411 public String getId() {
412 return "" + getPreferredDataValue("UID");
413 }
414
415 @Override
416 public String getState() {
e4444b0b 417 return getId();
e253bd50
NR
418 }
419
78e4af97
NR
420 /**
421 * Return a {@link String} representation of this contact, in vCard 2.1,
422 * without BKeys.
423 *
424 * @return the {@link String} representation
425 */
426 @Override
427 public String toString() {
428 return toString(Format.VCard21, -1);
429 }
430
a3b510ab
NR
431 /**
432 * Mark all the binary fields with a BKey number.
433 *
434 * @param force
435 * force the marking, and reset all the numbers.
436 */
437 protected void updateBKeys(boolean force) {
438 if (force) {
439 binaries = new HashMap<Integer, Data>();
440 nextBKey = 1;
441 }
442
443 if (binaries == null) {
444 binaries = new HashMap<Integer, Data>();
445 }
446
26d2bd05 447 for (Data data : this) {
a3b510ab
NR
448 if (data.isBinary() && (data.getB64Key() <= 0 || force)) {
449 binaries.put(nextBKey, data);
450 data.resetB64Key(nextBKey++);
451 }
452 }
453 }
454
a3b510ab 455 /**
26d2bd05
NR
456 * Load the data from the given {@link File} under the given {@link Format}.
457 *
458 * @param file
459 * the {@link File} to load from
460 * @param format
461 * the {@link Format} to load as
462 *
463 * @return the list of elements
464 * @throws IOException
465 * in case of IO error
a3b510ab 466 */
26d2bd05
NR
467 static private List<Data> load(List<Data> content) {
468 List<Data> datas = new ArrayList<Data>();
a3b510ab 469
26d2bd05
NR
470 boolean fn = false;
471 boolean n = false;
e253bd50 472 boolean uid = false;
26d2bd05
NR
473 if (content != null) {
474 for (Data data : content) {
475 if (data.getName().equals("N")) {
476 n = true;
477 } else if (data.getName().equals("FN")) {
478 fn = true;
e253bd50
NR
479 } else if (data.getName().equals("UID")) {
480 uid = true;
26d2bd05
NR
481 }
482
483 if (!data.getName().equals("VERSION")) {
484 datas.add(data);
485 }
486 }
78e4af97 487 }
78e4af97 488
26d2bd05 489 // required fields:
e253bd50 490 if (!n) // required since vCard 3.0, supported in 2.1
26d2bd05 491 datas.add(new Data(null, "N", "", null));
e253bd50 492 if (!fn) // not required anymore but still supported in 4.0
26d2bd05 493 datas.add(new Data(null, "FN", "", null));
e253bd50
NR
494 if (!uid) // supported by vCard, required by this program
495 datas.add(new Data(null, "UID", UUID.randomUUID().toString(), null));
26d2bd05
NR
496
497 return datas;
a3b510ab 498 }
296a0b75 499
bcb54330 500 /**
78e4af97
NR
501 * Add a {@link String} to the given {@link List}, but make sure it does not
502 * exceed the maximum size, and truncate it if needed to fit.
bcb54330 503 *
78e4af97
NR
504 * @param list
505 * @param add
506 * @param currentSize
507 * @param maxSize
508 * @return
bcb54330 509 */
78e4af97
NR
510 static private int addToList(List<String> list, String add,
511 int currentSize, int maxSize) {
512 if (add == null || add.length() == 0) {
513 if (add != null)
514 list.add(add);
515 return 0;
516 }
517
518 if (maxSize > -1) {
519 if (currentSize < maxSize) {
520 if (currentSize + add.length() >= maxSize) {
521 add = add.substring(0, maxSize - currentSize);
bcb54330 522 }
78e4af97
NR
523 } else {
524 add = "";
bcb54330
NR
525 }
526 }
527
78e4af97
NR
528 list.add(add);
529 return add.length();
bcb54330 530 }
a3b510ab 531}