1 package be
.nikiroo
.jvcard
;
3 import java
.util
.HashMap
;
4 import java
.util
.LinkedList
;
8 import be
.nikiroo
.jvcard
.parsers
.Format
;
9 import be
.nikiroo
.jvcard
.parsers
.Parser
;
10 import be
.nikiroo
.jvcard
.tui
.StringUtils
;
13 * A contact is the information that represent a contact person or organisation.
18 public class Contact
{
19 private List
<Data
> datas
;
20 private int nextBKey
= 1;
21 private Map
<Integer
, Data
> binaries
;
22 private boolean dirty
;
26 * Create a new Contact from the given information. Note that the BKeys data
30 * the information about the contact
32 public Contact(List
<Data
> content
) {
33 this.datas
= new LinkedList
<Data
>();
37 for (Data data
: content
) {
38 if (data
.getName().equals("N")) {
40 } else if (data
.getName().equals("FN")) {
44 if (!data
.getName().equals("VERSION")) {
51 datas
.add(new Data(null, "N", "", null));
54 datas
.add(new Data(null, "FN", "", null));
61 * Return the number of {@link Data} present in this {@link Contact}.
63 * @return the number of {@link Data}s
70 * Return the {@link Data} at index <i>index</i>.
73 * the index of the {@link Data} to find
75 * @return the {@link Data}
77 * @throws IndexOutOfBoundsException
78 * if the index is < 0 or >= {@link Contact#size()}
80 public Data
get(int index
) {
81 return datas
.get(index
);
85 * Return the preferred Data field with the given name, or NULL if none.
88 * the name to look for
89 * @return the Data field, or NULL
91 public Data
getPreferredData(String name
) {
93 for (Data data
: getData(name
)) {
96 for (int index
= 0; index
< data
.size(); index
++) {
97 TypeInfo type
= data
.get(index
);
98 if (type
.getName().equals("TYPE")
99 && type
.getValue().equals("pref")) {
109 * Return the value of the preferred data field with this name, or NULL if
110 * none (you cannot differentiate a NULL value and no value).
113 * the name to look for
114 * @return the value (which can be NULL), or NULL
116 public String
getPreferredDataValue(String name
) {
117 Data data
= getPreferredData(name
);
118 if (data
!= null && data
.getValue() != null)
119 return data
.getValue().trim();
124 * Get the Data fields that share the given name.
127 * the name to ook for
128 * @return a list of Data fields with this name
130 public List
<Data
> getData(String name
) {
131 List
<Data
> found
= new LinkedList
<Data
>();
133 for (Data data
: datas
) {
134 if (data
.getName().equals(name
))
142 * Return a {@link String} representation of this contact.
145 * the {@link Format} to use
146 * @param startingBKey
147 * the starting BKey or -1 for no BKeys
148 * @return the {@link String} representation
150 public String
toString(Format format
, int startingBKey
) {
152 return Parser
.toString(this, format
, startingBKey
);
156 * Return a {@link String} representation of this contact formated
157 * accordingly to the given format.
159 * The format is basically a list of field names separated by a pipe and
160 * optionally parametrised. The parameters allows you to:
162 * <li>@x: show only a present/not present info</li>
163 * <li>@n: limit the size to a fixed value 'n'</li>
164 * <li>@+: expand the size of this field as much as possible</li>
167 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
172 * @return the {@link String} representation
174 public String
toString(String format
) {
175 return toString(format
, "|", null, -1, true, false);
179 * Return a {@link String} representation of this contact formated
180 * accordingly to the given format.
182 * The format is basically a list of field names separated by a pipe and
183 * optionally parametrised. The parameters allows you to:
185 * <li>@x: show only a present/not present info</li>
186 * <li>@n: limit the size to a fixed value 'n'</li>
187 * <li>@+: expand the size of this field as much as possible</li>
190 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
195 * the separator {@link String} to use between fields
197 * the {@link String} to use for left and right padding
199 * a fixed width or -1 for "as long as needed"
202 * allow Uniode or only ASCII characters
204 * @return the {@link String} representation
206 public String
toString(String format
, String separator
, String padding
,
207 int width
, boolean unicode
, boolean removeAccents
) {
208 StringBuilder builder
= new StringBuilder();
210 for (String str
: toStringArray(format
, separator
, padding
, width
,
215 return builder
.toString();
219 * Return a {@link String} representation of this contact formated
220 * accordingly to the given format, part by part.
222 * The format is basically a list of field names separated by a pipe and
223 * optionally parametrised. The parameters allows you to:
225 * <li>@x: show only a present/not present info</li>
226 * <li>@n: limit the size to a fixed value 'n'</li>
227 * <li>@+: expand the size of this field as much as possible</li>
230 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
235 * the separator {@link String} to use between fields
237 * the {@link String} to use for left and right padding
239 * a fixed width or -1 for "as long as needed"
242 * allow Uniode or only ASCII characters
244 * @return the {@link String} representation
246 public String
[] toStringArray(String format
, String separator
,
247 String padding
, int width
, boolean unicode
) {
249 int numOfFields
= format
.split("\\|").length
;
250 if (separator
!= null)
251 width
-= (numOfFields
- 1) * separator
.length();
253 width
-= (numOfFields
) * (2 * padding
.length());
259 List
<String
> str
= new LinkedList
<String
>();
261 boolean first
= true;
262 for (String s
: toStringArray(format
, width
, unicode
)) {
268 str
.add(padding
+ s
+ padding
);
275 return str
.toArray(new String
[] {});
279 * Return a {@link String} representation of this contact formated
280 * accordingly to the given format, part by part.
282 * The format is basically a list of field names separated by a pipe and
283 * optionally parametrised. The parameters allows you to:
285 * <li>@x: show only a present/not present info</li>
286 * <li>@n: limit the size to a fixed value 'n'</li>
287 * <li>@+: expand the size of this field as much as possible</li>
290 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
295 * a fixed width or -1 for "as long as needed"
297 * allow Uniode or only ASCII characters
299 * @return the {@link String} representation
301 public String
[] toStringArray(String format
, int width
, boolean unicode
) {
302 List
<String
> str
= new LinkedList
<String
>();
304 String
[] formatFields
= format
.split("\\|");
305 String
[] values
= new String
[formatFields
.length
];
306 Boolean
[] expandedFields
= new Boolean
[formatFields
.length
];
307 Boolean
[] fixedsizeFields
= new Boolean
[formatFields
.length
];
308 int numOfFieldsToExpand
= 0;
312 for (int i
= 0; i
< formatFields
.length
; i
++) {
316 return str
.toArray(new String
[] {});
319 for (int i
= 0; i
< formatFields
.length
; i
++) {
320 String field
= formatFields
[i
];
323 boolean binary
= false;
324 boolean expand
= false;
326 if (field
.contains("@")) {
327 String
[] opts
= field
.split("@");
330 for (int io
= 1; io
< opts
.length
; io
++) {
331 String opt
= opts
[io
];
332 if (opt
.equals("x")) {
334 } else if (opt
.equals("+")) {
336 numOfFieldsToExpand
++;
339 size
= Integer
.parseInt(opt
);
340 } catch (Exception e
) {
346 String value
= getPreferredDataValue(field
);
350 value
= StringUtils
.sanitize(value
, unicode
);
354 value
= StringUtils
.padString(value
, size
);
357 expandedFields
[i
] = expand
;
358 fixedsizeFields
[i
] = (size
> -1);
361 if (value
!= null && !value
.equals(""))
368 totalSize
+= value
.length();
372 if (width
> -1 && totalSize
> width
) {
373 int toDo
= totalSize
- width
;
374 for (int i
= fixedsizeFields
.length
- 1; toDo
> 0 && i
>= 0; i
--) {
375 if (!fixedsizeFields
[i
]) {
376 int valueLength
= values
[i
].length();
377 if (valueLength
> 0) {
378 if (valueLength
>= toDo
) {
379 values
[i
] = values
[i
].substring(0, valueLength
390 totalSize
= width
+ toDo
;
393 if (width
> -1 && numOfFieldsToExpand
> 0) {
394 int availablePadding
= width
- totalSize
;
396 if (availablePadding
> 0) {
397 int padPerItem
= availablePadding
/ numOfFieldsToExpand
;
398 int remainder
= availablePadding
% numOfFieldsToExpand
;
400 for (int i
= 0; i
< values
.length
; i
++) {
401 if (expandedFields
[i
]) {
403 values
[i
] = values
[i
]
404 + StringUtils
.padString("", remainder
);
407 if (padPerItem
> 0) {
408 values
[i
] = values
[i
]
409 + StringUtils
.padString("", padPerItem
);
419 for (int i
= 0; i
< values
.length
; i
++) {
420 currentSize
+= addToList(str
, values
[i
], currentSize
, width
);
423 return str
.toArray(new String
[] {});
427 * Update the information from this contact with the information in the
428 * given contact. Non present fields will be removed, new fields will be
429 * added, BKey'ed fields will be completed with the binary information known
433 * the contact with the newer information and optional BKeys
435 public void updateFrom(Contact vc
) {
438 List
<Data
> newDatas
= new LinkedList
<Data
>(vc
.datas
);
439 for (int i
= 0; i
< newDatas
.size(); i
++) {
440 Data data
= newDatas
.get(i
);
441 int bkey
= Parser
.getBKey(data
);
443 if (binaries
.containsKey(bkey
)) {
444 newDatas
.set(i
, binaries
.get(bkey
));
449 this.datas
= newDatas
;
450 this.nextBKey
= vc
.nextBKey
;
457 * Delete this {@link Contact} from its parent {@link Card} if any.
459 * @return TRUE in case of success
461 public boolean delete() {
462 if (parent
!= null) {
463 List
<Contact
> list
= parent
.getContactsList();
464 for (int i
= 0; i
< list
.size(); i
++) {
465 if (list
.get(i
) == this) {
477 * Check if this {@link Contact} has unsaved changes.
479 * @return TRUE if it has
481 public boolean isDirty() {
486 * Return a {@link String} representation of this contact, in vCard 2.1,
489 * @return the {@link String} representation
492 public String
toString() {
493 return toString(Format
.VCard21
, -1);
497 * Mark all the binary fields with a BKey number.
500 * force the marking, and reset all the numbers.
502 protected void updateBKeys(boolean force
) {
504 binaries
= new HashMap
<Integer
, Data
>();
508 if (binaries
== null) {
509 binaries
= new HashMap
<Integer
, Data
>();
512 for (Data data
: datas
) {
513 if (data
.isBinary() && (data
.getB64Key() <= 0 || force
)) {
514 binaries
.put(nextBKey
, data
);
515 data
.resetB64Key(nextBKey
++);
521 * Notify that this element has unsaved changes, and notify its parent of
524 protected void setDirty() {
526 if (this.parent
!= null)
527 this.parent
.setDirty();
531 * Notify this element <i>and all its descendants</i> that it is in pristine
532 * state (as opposed to dirty).
536 for (Data data
: datas
) {
542 * Set the parent of this {@link Contact} <i>and all its descendants</i>.
547 void setParent(Card parent
) {
548 this.parent
= parent
;
549 for (Data data
: datas
) {
550 data
.setParent(this);
555 * Add a {@link String} to the given {@link List}, but make sure it does not
556 * exceed the maximum size, and truncate it if needed to fit.
564 static private int addToList(List
<String
> list
, String add
,
565 int currentSize
, int maxSize
) {
566 if (add
== null || add
.length() == 0) {
573 if (currentSize
< maxSize
) {
574 if (currentSize
+ add
.length() >= maxSize
) {
575 add
= add
.substring(0, maxSize
- currentSize
);