4b4446de67c7ab0b9d4fed2650024a7274913421
1 package be
.nikiroo
.jvcard
;
4 import java
.io
.IOException
;
5 import java
.util
.ArrayList
;
6 import java
.util
.HashMap
;
7 import java
.util
.LinkedList
;
10 import java
.util
.UUID
;
12 import be
.nikiroo
.jvcard
.parsers
.Format
;
13 import be
.nikiroo
.jvcard
.parsers
.Parser
;
14 import be
.nikiroo
.utils
.StringUtils
;
17 * A contact is the information that represent a contact person or organisation.
19 * Each {@link Data} inside can be binary encoded or not: if it is binary
20 * encoded, it has an active BKey number (not -1) associated to it (of value 0
21 * if still not sorted, or unique for the whole {@link Contact} if already
26 public class Contact
extends BaseClass
<Data
> {
27 private int nextBKey
= 1;
28 private Map
<Integer
, Data
> binaries
;
31 * Create a new Contact from the given information. Note that the BKeys data
35 * the information about the contact
37 public Contact(List
<Data
> content
) {
43 * Return the preferred Data field with the given name, the first one if
44 * none is preferred, or NULL if none at all.
47 * the name to look for
49 * @return the {@link Data} field, or NULL
51 public Data
getPreferredData(String name
) {
53 int ipref
= Integer
.MAX_VALUE
;
54 for (Data data
: getData(name
)) {
58 if (data
.getPreferred() < ipref
)
66 * Return the value of the preferred data field with this name, or NULL if
67 * none (you cannot differentiate a NULL value and no value with this method
68 * -- for that, check {@link Contact#getPreferredData(String)}).
71 * the name to look for
72 * @return the value (which can be NULL), or NULL
74 public String
getPreferredDataValue(String name
) {
75 Data data
= getPreferredData(name
);
76 if (data
!= null && data
.getValue() != null)
77 return data
.getValue().trim();
82 * Get the Data fields that share the given name.
85 * the name to look for
86 * @return a list of Data fields with this name
88 public List
<Data
> getData(String name
) {
89 List
<Data
> found
= new LinkedList
<Data
>();
91 for (Data data
: this) {
92 if (data
.getName().equals(name
))
100 * Return a {@link String} representation of this contact formated
101 * accordingly to the given format.
104 * The format is basically a list of field names separated by a pipe and
105 * optionally parametrised with the 'at' (@) symbol. The parameters allows
108 * <li>@x: show only a present/not present info</li>
109 * <li>@n: limit the size to a fixed value 'n'</li>
110 * <li>@+: expand the size of this field as much as possible</li>
115 * In case of lists or multiple-fields values, you can select a specific
116 * list or field with:
118 * <li>FIELD@(0): select the first value in a list</li>
119 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
124 * You can also add a fixed text if it starts with a simple-quote (').
128 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
134 * the separator {@link String} to use between fields
136 * @return the {@link String} representation
138 public String
toString(String format
, String separator
) {
139 return toString(format
, separator
, null, -1, true);
143 * Return a {@link String} representation of this contact formated
144 * accordingly to the given format.
147 * The format is basically a list of field names separated by a pipe and
148 * optionally parametrised. The parameters allows you to:
150 * <li>@x: show only a present/not present info</li>
151 * <li>@n: limit the size to a fixed value 'n'</li>
152 * <li>@+: expand the size of this field as much as possible</li>
157 * In case of lists or multiple-fields values, you can select a specific
158 * list or field with:
160 * <li>FIELD@(0): select the first value in a list</li>
161 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
166 * You can also add a fixed text if it starts with a simple-quote (').
170 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
176 * the separator {@link String} to use between fields
178 * the {@link String} to use for left and right padding
180 * a fixed width or -1 for "as long as needed"
182 * allow Unicode or only ASCII characters
184 * @return the {@link String} representation
186 public String
toString(String format
, String separator
, String padding
,
187 int width
, boolean unicode
) {
188 StringBuilder builder
= new StringBuilder();
190 for (String str
: toStringArray(format
, separator
, padding
, width
,
195 return builder
.toString();
199 * Return a {@link String} representation of this contact formated
200 * accordingly to the given format, part by part.
203 * The format is basically a list of field names separated by a pipe and
204 * optionally parametrised. The parameters allows you to:
206 * <li>@x: show only a present/not present info</li>
207 * <li>@n: limit the size to a fixed value 'n'</li>
208 * <li>@+: expand the size of this field as much as possible</li>
213 * In case of lists or multiple-fields values, you can select a specific
214 * list or field with:
216 * <li>FIELD@(0): select the first value in a list</li>
217 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
222 * You can also add a fixed text if it starts with a simple-quote (').
226 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
232 * the separator {@link String} to use between fields
234 * the {@link String} to use for left and right padding
236 * a fixed width or -1 for "as long as needed"
239 * allow Uniode or only ASCII characters
241 * @return the {@link String} representation
243 public String
[] toStringArray(String format
, String separator
,
244 String padding
, int width
, boolean unicode
) {
246 int numOfFields
= format
.split("\\|").length
;
247 if (separator
!= null)
248 width
-= (numOfFields
- 1) * separator
.length();
250 width
-= (numOfFields
) * (2 * padding
.length());
256 List
<String
> str
= new LinkedList
<String
>();
258 boolean first
= true;
259 for (String s
: toStringArray(format
, width
, unicode
)) {
265 str
.add(padding
+ s
+ padding
);
272 return str
.toArray(new String
[] {});
276 * Return a {@link String} representation of this contact formated
277 * accordingly to the given format, part by part.
280 * The format is basically a list of field names separated by a pipe and
281 * optionally parametrised. The parameters allows you to:
283 * <li>@x: show only a present/not present info</li>
284 * <li>@n: limit the size to a fixed value 'n'</li>
285 * <li>@+: expand the size of this field as much as possible</li>
290 * In case of lists or multiple-fields values, you can select a specific
291 * list or field with:
293 * <li>FIELD@(0): select the first value in a list</li>
294 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
299 * You can also add a fixed text if it starts with a simple-quote (').
303 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
309 * a fixed width or -1 for "as long as needed"
311 * allow Uniode or only ASCII characters
313 * @return the {@link String} representation
315 public String
[] toStringArray(String format
, int width
, boolean unicode
) {
316 List
<String
> str
= new LinkedList
<String
>();
318 String
[] formatFields
= format
.split("\\|");
319 String
[] values
= new String
[formatFields
.length
];
320 Boolean
[] expandedFields
= new Boolean
[formatFields
.length
];
321 Boolean
[] fixedsizeFields
= new Boolean
[formatFields
.length
];
322 int numOfFieldsToExpand
= 0;
326 for (int i
= 0; i
< formatFields
.length
; i
++) {
330 return str
.toArray(new String
[] {});
333 for (int i
= 0; i
< formatFields
.length
; i
++) {
334 String field
= formatFields
[i
];
337 boolean binary
= false;
338 boolean expand
= false;
342 if (field
.length() > 0 && field
.charAt(0) != '\''
343 && field
.contains("@")) {
344 String
[] opts
= field
.split("@");
347 for (int io
= 1; io
< opts
.length
; io
++) {
348 String opt
= opts
[io
];
349 if (opt
.equals("x")) {
351 } else if (opt
.equals("+")) {
353 numOfFieldsToExpand
++;
354 } else if (opt
.length() > 0 && opt
.charAt(0) == '(') {
356 opt
= opt
.substring(1, opt
.length() - 1);
357 valueNum
= Integer
.parseInt(opt
);
358 } catch (Exception e
) {
360 } else if (opt
.length() > 0 && opt
.charAt(0) == '[') {
362 opt
= opt
.substring(1, opt
.length() - 1);
363 fieldNum
= Integer
.parseInt(opt
);
364 } catch (Exception e
) {
368 size
= Integer
.parseInt(opt
);
369 } catch (NumberFormatException e
) {
376 if (field
.length() > 0 && field
.charAt(0) == '\'') {
377 value
= field
.substring(1);
378 } else if (valueNum
>= 0) {
379 List
<String
> vv
= getPreferredData(field
).getValues();
380 if (valueNum
< vv
.size()) {
381 value
= vv
.get(valueNum
);
383 } else if (fieldNum
>= 0) {
384 List
<String
> ff
= getPreferredData(field
).getFields();
385 if (fieldNum
< ff
.size()) {
386 value
= ff
.get(fieldNum
);
389 // we don't need the *data* in binary mode...
391 value
= getData(field
).size() > 0 ?
"x" : null;
393 value
= getPreferredDataValue(field
);
399 value
= StringUtils
.sanitize(value
, unicode
);
403 value
= StringUtils
.padString(value
, size
);
406 expandedFields
[i
] = expand
;
407 fixedsizeFields
[i
] = (size
> -1);
410 if (value
!= null && !value
.equals(""))
417 totalSize
+= value
.length();
421 if (width
> -1 && totalSize
> width
) {
422 int toDo
= totalSize
- width
;
423 for (int i
= fixedsizeFields
.length
- 1; toDo
> 0 && i
>= 0; i
--) {
424 if (!fixedsizeFields
[i
]) {
425 int valueLength
= values
[i
].length();
426 if (valueLength
> 0) {
427 if (valueLength
>= toDo
) {
428 values
[i
] = values
[i
].substring(0, valueLength
439 totalSize
= width
+ toDo
;
442 if (width
> -1 && numOfFieldsToExpand
> 0) {
443 int availablePadding
= width
- totalSize
;
445 if (availablePadding
> 0) {
446 int padPerItem
= availablePadding
/ numOfFieldsToExpand
;
447 int remainder
= availablePadding
% numOfFieldsToExpand
;
449 for (int i
= 0; i
< values
.length
; i
++) {
450 if (expandedFields
[i
]) {
452 values
[i
] = values
[i
]
453 + StringUtils
.padString("", remainder
);
456 if (padPerItem
> 0) {
457 values
[i
] = values
[i
]
458 + StringUtils
.padString("", padPerItem
);
468 for (int i
= 0; i
< values
.length
; i
++) {
469 currentSize
+= addToList(str
, values
[i
], currentSize
, width
);
472 return str
.toArray(new String
[] {});
476 * Update the information from this contact with the information in the
477 * given contact. Non present fields will be removed, new fields will be
478 * added, BKey'ed fields will be completed with the binary information known
482 * the contact with the newer information and optional BKeys
484 public void updateFrom(Contact vc
) {
487 List
<Data
> newDatas
= new LinkedList
<Data
>(vc
);
488 for (int i
= 0; i
< newDatas
.size(); i
++) {
489 Data data
= newDatas
.get(i
);
490 int bkey
= Parser
.getBKey(data
);
492 if (binaries
.containsKey(bkey
)) {
493 newDatas
.set(i
, binaries
.get(bkey
));
498 replaceListContent(newDatas
);
499 this.nextBKey
= vc
.nextBKey
;
503 public String
getId() {
504 return "" + getPreferredDataValue("UID");
508 public String
getState() {
513 * Return a simple {@link String} representation of this contact without
516 * @return the {@link String} representation
519 public String
toString() {
520 return "[Contact: " + getPreferredDataValue("FN") + "]";
524 * Mark all the binary fields with a BKey number.
527 * force the marking, and reset all the numbers.
529 protected void updateBKeys(boolean force
) {
531 binaries
= new HashMap
<Integer
, Data
>();
535 if (binaries
== null) {
536 binaries
= new HashMap
<Integer
, Data
>();
539 for (Data data
: this) {
540 if (data
.isBinary() && (data
.getB64Key() <= 0 || force
)) {
541 binaries
.put(nextBKey
, data
);
542 data
.resetB64Key(nextBKey
++);
548 * Load the data from the given {@link File} under the given {@link Format}.
551 * the {@link File} to load from
553 * the {@link Format} to load as
555 * @return the list of elements
556 * @throws IOException
557 * in case of IO error
559 static private List
<Data
> load(List
<Data
> content
) {
560 List
<Data
> datas
= new ArrayList
<Data
>();
565 if (content
!= null) {
566 for (Data data
: content
) {
567 if (data
.getName().equals("N")) {
569 } else if (data
.getName().equals("FN")) {
571 } else if (data
.getName().equals("UID")) {
575 if (!data
.getName().equals("VERSION")) {
582 if (!n
) // required since vCard 3.0, supported in 2.1
583 datas
.add(new Data(null, "N", "", null));
584 if (!fn
) // not required anymore but still supported in 4.0
585 datas
.add(new Data(null, "FN", "", null));
586 if (!uid
) // supported by vCard, required by this program
587 datas
.add(new Data(null, "UID", UUID
.randomUUID().toString(), null));
593 * Add a {@link String} to the given {@link List}, but make sure it does not
594 * exceed the maximum size, and truncate it if needed to fit.
596 * Will always add one and only one {@link String} (potentially empty) at
597 * the end of <tt>list</tt>.
602 * the {@link String} to (either fully or partially) add
604 * the current total size (managed outside of this method)
606 * the maximum size that cannot be exceeded (or -1 for
607 * "no maximum") -- if the maximum size would be exceeded by
608 * adding this {@link String}, only a part of it will be added;
609 * if the maximum size is already reached or exceeded (should not
610 * happen because of this method), an empty {@link String} will
613 * @return the number of characters added (the size of the last
614 * {@link String} in <tt>list</tt>)
616 static private int addToList(List
<String
> list
, String add
,
617 int currentSize
, int maxSize
) {
618 if (add
== null || add
.length() == 0) {
625 if (currentSize
< maxSize
) {
626 if (currentSize
+ add
.length() >= maxSize
) {
627 add
= add
.substring(0, maxSize
- currentSize
);