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