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
.jvcard
.resources
.StringUtils
;
17 * A contact is the information that represent a contact person or organisation.
22 public class Contact
extends BaseClass
<Data
> {
23 private int nextBKey
= 1;
24 private Map
<Integer
, Data
> binaries
;
27 * Create a new Contact from the given information. Note that the BKeys data
31 * the information about the contact
33 public Contact(List
<Data
> content
) {
39 * Return the preferred Data field with the given name, the first one if
40 * none is preferred, or NULL if none at all.
43 * the name to look for
45 * @return the {@link Data} field, or NULL
47 public Data
getPreferredData(String name
) {
49 int ipref
= Integer
.MAX_VALUE
;
50 for (Data data
: getData(name
)) {
54 if (data
.getPreferred() < ipref
)
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).
66 * the name to look for
67 * @return the value (which can be NULL), or NULL
69 public String
getPreferredDataValue(String name
) {
70 Data data
= getPreferredData(name
);
71 if (data
!= null && data
.getValue() != null)
72 return data
.getValue().trim();
77 * Get the Data fields that share the given name.
81 * @return a list of Data fields with this name
83 public List
<Data
> getData(String name
) {
84 List
<Data
> found
= new LinkedList
<Data
>();
86 for (Data data
: this) {
87 if (data
.getName().equals(name
))
95 * Return a {@link String} representation of this contact formated
96 * accordingly to the given format.
99 * The format is basically a list of field names separated by a pipe and
100 * optionally parametrised with the 'at' (@) symbol. The parameters allows
103 * <li>@x: show only a present/not present info</li>
104 * <li>@n: limit the size to a fixed value 'n'</li>
105 * <li>@+: expand the size of this field as much as possible</li>
110 * In case of lists or multiple-fields values, you can select a specific
111 * list or field with:
113 * <li>FIELD@(0): select the first value in a list</li>
114 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
119 * You can also add a fixed text if it starts with a simple-quote (').
123 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
129 * the separator {@link String} to use between fields
131 * @return the {@link String} representation
133 public String
toString(String format
, String separator
) {
134 return toString(format
, separator
, null, -1, true, false);
138 * Return a {@link String} representation of this contact formated
139 * accordingly to the given format.
142 * The format is basically a list of field names separated by a pipe and
143 * optionally parametrised. The parameters allows you to:
145 * <li>@x: show only a present/not present info</li>
146 * <li>@n: limit the size to a fixed value 'n'</li>
147 * <li>@+: expand the size of this field as much as possible</li>
152 * In case of lists or multiple-fields values, you can select a specific
153 * list or field with:
155 * <li>FIELD@(0): select the first value in a list</li>
156 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
161 * You can also add a fixed text if it starts with a simple-quote (').
165 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
171 * the separator {@link String} to use between fields
173 * the {@link String} to use for left and right padding
175 * a fixed width or -1 for "as long as needed"
178 * allow Uniode or only ASCII characters
180 * @return the {@link String} representation
182 public String
toString(String format
, String separator
, String padding
,
183 int width
, boolean unicode
, boolean removeAccents
) {
184 StringBuilder builder
= new StringBuilder();
186 for (String str
: toStringArray(format
, separator
, padding
, width
,
191 return builder
.toString();
195 * Return a {@link String} representation of this contact formated
196 * accordingly to the given format, part by part.
199 * The format is basically a list of field names separated by a pipe and
200 * optionally parametrised. The parameters allows you to:
202 * <li>@x: show only a present/not present info</li>
203 * <li>@n: limit the size to a fixed value 'n'</li>
204 * <li>@+: expand the size of this field as much as possible</li>
209 * In case of lists or multiple-fields values, you can select a specific
210 * list or field with:
212 * <li>FIELD@(0): select the first value in a list</li>
213 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
218 * You can also add a fixed text if it starts with a simple-quote (').
222 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
228 * the separator {@link String} to use between fields
230 * the {@link String} to use for left and right padding
232 * a fixed width or -1 for "as long as needed"
235 * allow Uniode or only ASCII characters
237 * @return the {@link String} representation
239 public String
[] toStringArray(String format
, String separator
,
240 String padding
, int width
, boolean unicode
) {
242 int numOfFields
= format
.split("\\|").length
;
243 if (separator
!= null)
244 width
-= (numOfFields
- 1) * separator
.length();
246 width
-= (numOfFields
) * (2 * padding
.length());
252 List
<String
> str
= new LinkedList
<String
>();
254 boolean first
= true;
255 for (String s
: toStringArray(format
, width
, unicode
)) {
261 str
.add(padding
+ s
+ padding
);
268 return str
.toArray(new String
[] {});
272 * Return a {@link String} representation of this contact formated
273 * accordingly to the given format, part by part.
276 * The format is basically a list of field names separated by a pipe and
277 * optionally parametrised. The parameters allows you to:
279 * <li>@x: show only a present/not present info</li>
280 * <li>@n: limit the size to a fixed value 'n'</li>
281 * <li>@+: expand the size of this field as much as possible</li>
286 * In case of lists or multiple-fields values, you can select a specific
287 * list or field with:
289 * <li>FIELD@(0): select the first value in a list</li>
290 * <li>FIELD@[1]: select the second field in a multiple-fields value</li>
295 * You can also add a fixed text if it starts with a simple-quote (').
299 * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x"
305 * a fixed width or -1 for "as long as needed"
307 * allow Uniode or only ASCII characters
309 * @return the {@link String} representation
311 public String
[] toStringArray(String format
, int width
, boolean unicode
) {
312 List
<String
> str
= new LinkedList
<String
>();
314 String
[] formatFields
= format
.split("\\|");
315 String
[] values
= new String
[formatFields
.length
];
316 Boolean
[] expandedFields
= new Boolean
[formatFields
.length
];
317 Boolean
[] fixedsizeFields
= new Boolean
[formatFields
.length
];
318 int numOfFieldsToExpand
= 0;
322 for (int i
= 0; i
< formatFields
.length
; i
++) {
326 return str
.toArray(new String
[] {});
329 for (int i
= 0; i
< formatFields
.length
; i
++) {
330 String field
= formatFields
[i
];
333 boolean binary
= false;
334 boolean expand
= false;
338 if (field
.length() > 0 && field
.charAt(0) != '\''
339 && field
.contains("@")) {
340 String
[] opts
= field
.split("@");
343 for (int io
= 1; io
< opts
.length
; io
++) {
344 String opt
= opts
[io
];
345 if (opt
.equals("x")) {
347 } else if (opt
.equals("+")) {
349 numOfFieldsToExpand
++;
350 } else if (opt
.length() > 0 && opt
.charAt(0) == '(') {
352 opt
= opt
.substring(1, opt
.length() - 1);
353 valueNum
= Integer
.parseInt(opt
);
354 } catch (Exception e
) {
356 } else if (opt
.length() > 0 && opt
.charAt(0) == '[') {
358 opt
= opt
.substring(1, opt
.length() - 1);
359 fieldNum
= Integer
.parseInt(opt
);
360 } catch (Exception e
) {
364 size
= Integer
.parseInt(opt
);
365 } catch (NumberFormatException e
) {
372 if (field
.length() > 0 && field
.charAt(0) == '\'') {
373 value
= field
.substring(1);
374 } else if (valueNum
>= 0) {
375 List
<String
> vv
= getPreferredData(field
).getValues();
376 if (valueNum
< vv
.size()) {
377 value
= vv
.get(valueNum
);
379 } else if (fieldNum
>= 0) {
380 List
<String
> ff
= getPreferredData(field
).getFields();
381 if (fieldNum
< ff
.size()) {
382 value
= ff
.get(fieldNum
);
385 // we don't need the *data* in binary mode...
387 value
= getData(field
).size() > 0 ?
"x" : null;
389 value
= getPreferredDataValue(field
);
395 value
= StringUtils
.sanitize(value
, unicode
);
399 value
= StringUtils
.padString(value
, size
);
402 expandedFields
[i
] = expand
;
403 fixedsizeFields
[i
] = (size
> -1);
406 if (value
!= null && !value
.equals(""))
413 totalSize
+= value
.length();
417 if (width
> -1 && totalSize
> width
) {
418 int toDo
= totalSize
- width
;
419 for (int i
= fixedsizeFields
.length
- 1; toDo
> 0 && i
>= 0; i
--) {
420 if (!fixedsizeFields
[i
]) {
421 int valueLength
= values
[i
].length();
422 if (valueLength
> 0) {
423 if (valueLength
>= toDo
) {
424 values
[i
] = values
[i
].substring(0, valueLength
435 totalSize
= width
+ toDo
;
438 if (width
> -1 && numOfFieldsToExpand
> 0) {
439 int availablePadding
= width
- totalSize
;
441 if (availablePadding
> 0) {
442 int padPerItem
= availablePadding
/ numOfFieldsToExpand
;
443 int remainder
= availablePadding
% numOfFieldsToExpand
;
445 for (int i
= 0; i
< values
.length
; i
++) {
446 if (expandedFields
[i
]) {
448 values
[i
] = values
[i
]
449 + StringUtils
.padString("", remainder
);
452 if (padPerItem
> 0) {
453 values
[i
] = values
[i
]
454 + StringUtils
.padString("", padPerItem
);
464 for (int i
= 0; i
< values
.length
; i
++) {
465 currentSize
+= addToList(str
, values
[i
], currentSize
, width
);
468 return str
.toArray(new String
[] {});
472 * Update the information from this contact with the information in the
473 * given contact. Non present fields will be removed, new fields will be
474 * added, BKey'ed fields will be completed with the binary information known
478 * the contact with the newer information and optional BKeys
480 public void updateFrom(Contact vc
) {
483 List
<Data
> newDatas
= new LinkedList
<Data
>(vc
);
484 for (int i
= 0; i
< newDatas
.size(); i
++) {
485 Data data
= newDatas
.get(i
);
486 int bkey
= Parser
.getBKey(data
);
488 if (binaries
.containsKey(bkey
)) {
489 newDatas
.set(i
, binaries
.get(bkey
));
494 replaceListContent(newDatas
);
495 this.nextBKey
= vc
.nextBKey
;
499 public String
getId() {
500 return "" + getPreferredDataValue("UID");
504 public String
getState() {
509 * Return a {@link String} representation of this contact, in vCard 2.1,
512 * @return the {@link String} representation
515 public String
toString() {
516 return "[Contact: " + getPreferredDataValue("FN") + "]";
520 * Mark all the binary fields with a BKey number.
523 * force the marking, and reset all the numbers.
525 protected void updateBKeys(boolean force
) {
527 binaries
= new HashMap
<Integer
, Data
>();
531 if (binaries
== null) {
532 binaries
= new HashMap
<Integer
, Data
>();
535 for (Data data
: this) {
536 if (data
.isBinary() && (data
.getB64Key() <= 0 || force
)) {
537 binaries
.put(nextBKey
, data
);
538 data
.resetB64Key(nextBKey
++);
544 * Load the data from the given {@link File} under the given {@link Format}.
547 * the {@link File} to load from
549 * the {@link Format} to load as
551 * @return the list of elements
552 * @throws IOException
553 * in case of IO error
555 static private List
<Data
> load(List
<Data
> content
) {
556 List
<Data
> datas
= new ArrayList
<Data
>();
561 if (content
!= null) {
562 for (Data data
: content
) {
563 if (data
.getName().equals("N")) {
565 } else if (data
.getName().equals("FN")) {
567 } else if (data
.getName().equals("UID")) {
571 if (!data
.getName().equals("VERSION")) {
578 if (!n
) // required since vCard 3.0, supported in 2.1
579 datas
.add(new Data(null, "N", "", null));
580 if (!fn
) // not required anymore but still supported in 4.0
581 datas
.add(new Data(null, "FN", "", null));
582 if (!uid
) // supported by vCard, required by this program
583 datas
.add(new Data(null, "UID", UUID
.randomUUID().toString(), null));
589 * Add a {@link String} to the given {@link List}, but make sure it does not
590 * exceed the maximum size, and truncate it if needed to fit.
598 static private int addToList(List
<String
> list
, String add
,
599 int currentSize
, int maxSize
) {
600 if (add
== null || add
.length() == 0) {
607 if (currentSize
< maxSize
) {
608 if (currentSize
+ add
.length() >= maxSize
) {
609 add
= add
.substring(0, maxSize
- currentSize
);