Commit | Line | Data |
---|---|---|
a3b510ab NR |
1 | package be.nikiroo.jvcard; |
2 | ||
26d2bd05 NR |
3 | import java.io.File; |
4 | import java.io.IOException; | |
5 | import java.util.ArrayList; | |
a3b510ab NR |
6 | import java.util.HashMap; |
7 | import java.util.LinkedList; | |
8 | import java.util.List; | |
9 | import java.util.Map; | |
10 | ||
11 | import be.nikiroo.jvcard.parsers.Format; | |
12 | import be.nikiroo.jvcard.parsers.Parser; | |
296a0b75 | 13 | import 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 | 21 | public 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 | } |