c5579fe8c5e1cf4a71eeec149d83c7d23be79ff4
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 if (content
!= null) {
38 for (Data data
: content
) {
39 if (data
.getName().equals("N")) {
41 } else if (data
.getName().equals("FN")) {
45 if (!data
.getName().equals("VERSION")) {
53 datas
.add(new Data(null, "N", "", null));
56 datas
.add(new Data(null, "FN", "", null));
63 * Return the number of {@link Data} present in this {@link Contact}.
65 * @return the number of {@link Data}s
72 * Return the {@link Data} at index <i>index</i>.
75 * the index of the {@link Data} to find
77 * @return the {@link Data}
79 * @throws IndexOutOfBoundsException
80 * if the index is < 0 or >= {@link Contact#size()}
82 public Data
get(int index
) {
83 return datas
.get(index
);
87 * Add a new {@link Data} in this {@link Contact}.
92 public void add(Data data
) {
99 * Remove the given {@link Data} from its this {@link Contact} if it is in.
101 * @return TRUE in case of success
103 public boolean remove(Data data
) {
104 if (datas
.remove(data
)) {
113 * Return the preferred Data field with the given name, or NULL if none.
116 * the name to look for
117 * @return the Data field, or NULL
119 public Data
getPreferredData(String name
) {
121 for (Data data
: getData(name
)) {
124 for (int index
= 0; index
< data
.size(); index
++) {
125 TypeInfo type
= data
.get(index
);
126 if (type
.getName().equals("TYPE")
127 && type
.getValue().equals("pref")) {
137 * Return the value of the preferred data field with this name, or NULL if
138 * none (you cannot differentiate a NULL value and no value).
141 * the name to look for
142 * @return the value (which can be NULL), or NULL
144 public String
getPreferredDataValue(String name
) {
145 Data data
= getPreferredData(name
);
146 if (data
!= null && data
.getValue() != null)
147 return data
.getValue().trim();
152 * Get the Data fields that share the given name.
155 * the name to ook for
156 * @return a list of Data fields with this name
158 public List
<Data
> getData(String name
) {
159 List
<Data
> found
= new LinkedList
<Data
>();
161 for (Data data
: datas
) {
162 if (data
.getName().equals(name
))
170 * Return a {@link String} representation of this contact.
173 * the {@link Format} to use
174 * @param startingBKey
175 * the starting BKey or -1 for no BKeys
176 * @return the {@link String} representation
178 public String
toString(Format format
, int startingBKey
) {
180 return Parser
.toString(this, format
, startingBKey
);
184 * Return a {@link String} representation of this contact formated
185 * accordingly to the given format.
187 * The format is basically a list of field names separated by a pipe and
188 * optionally parametrised. The parameters allows you to:
190 * <li>@x: show only a present/not present info</li>
191 * <li>@n: limit the size to a fixed value 'n'</li>
192 * <li>@+: expand the size of this field as much as possible</li>
195 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
200 * @return the {@link String} representation
202 public String
toString(String format
) {
203 return toString(format
, "|", null, -1, true, false);
207 * Return a {@link String} representation of this contact formated
208 * accordingly to the given format.
210 * The format is basically a list of field names separated by a pipe and
211 * optionally parametrised. The parameters allows you to:
213 * <li>@x: (the 'x' is the letter 'x') show only a present/not present info</li>
214 * <li>@n: limit the size to a fixed value 'n'</li>
215 * <li>@+: expand the size of this field as much as possible</li>
218 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
223 * the separator {@link String} to use between fields
225 * the {@link String} to use for left and right padding
227 * a fixed width or -1 for "as long as needed"
230 * allow Uniode or only ASCII characters
232 * @return the {@link String} representation
234 public String
toString(String format
, String separator
, String padding
,
235 int width
, boolean unicode
, boolean removeAccents
) {
236 StringBuilder builder
= new StringBuilder();
238 for (String str
: toStringArray(format
, separator
, padding
, width
,
243 return builder
.toString();
247 * Return a {@link String} representation of this contact formated
248 * accordingly to the given format, part by part.
250 * The format is basically a list of field names separated by a pipe and
251 * optionally parametrised. The parameters allows you to:
253 * <li>@x: show only a present/not present info</li>
254 * <li>@n: limit the size to a fixed value 'n'</li>
255 * <li>@+: expand the size of this field as much as possible</li>
258 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
263 * the separator {@link String} to use between fields
265 * the {@link String} to use for left and right padding
267 * a fixed width or -1 for "as long as needed"
270 * allow Uniode or only ASCII characters
272 * @return the {@link String} representation
274 public String
[] toStringArray(String format
, String separator
,
275 String padding
, int width
, boolean unicode
) {
277 int numOfFields
= format
.split("\\|").length
;
278 if (separator
!= null)
279 width
-= (numOfFields
- 1) * separator
.length();
281 width
-= (numOfFields
) * (2 * padding
.length());
287 List
<String
> str
= new LinkedList
<String
>();
289 boolean first
= true;
290 for (String s
: toStringArray(format
, width
, unicode
)) {
296 str
.add(padding
+ s
+ padding
);
303 return str
.toArray(new String
[] {});
307 * Return a {@link String} representation of this contact formated
308 * accordingly to the given format, part by part.
310 * The format is basically a list of field names separated by a pipe and
311 * optionally parametrised. The parameters allows you to:
313 * <li>@x: show only a present/not present info</li>
314 * <li>@n: limit the size to a fixed value 'n'</li>
315 * <li>@+: expand the size of this field as much as possible</li>
318 * Example: "N@10|FN@20|NICK@+|PHOTO@x"
323 * a fixed width or -1 for "as long as needed"
325 * allow Uniode or only ASCII characters
327 * @return the {@link String} representation
329 public String
[] toStringArray(String format
, int width
, boolean unicode
) {
330 List
<String
> str
= new LinkedList
<String
>();
332 String
[] formatFields
= format
.split("\\|");
333 String
[] values
= new String
[formatFields
.length
];
334 Boolean
[] expandedFields
= new Boolean
[formatFields
.length
];
335 Boolean
[] fixedsizeFields
= new Boolean
[formatFields
.length
];
336 int numOfFieldsToExpand
= 0;
340 for (int i
= 0; i
< formatFields
.length
; i
++) {
344 return str
.toArray(new String
[] {});
347 for (int i
= 0; i
< formatFields
.length
; i
++) {
348 String field
= formatFields
[i
];
351 boolean binary
= false;
352 boolean expand
= false;
354 if (field
.contains("@")) {
355 String
[] opts
= field
.split("@");
358 for (int io
= 1; io
< opts
.length
; io
++) {
359 String opt
= opts
[io
];
360 if (opt
.equals("x")) {
362 } else if (opt
.equals("+")) {
364 numOfFieldsToExpand
++;
367 size
= Integer
.parseInt(opt
);
368 } catch (Exception e
) {
374 String value
= getPreferredDataValue(field
);
378 value
= StringUtils
.sanitize(value
, unicode
);
382 value
= StringUtils
.padString(value
, size
);
385 expandedFields
[i
] = expand
;
386 fixedsizeFields
[i
] = (size
> -1);
389 if (value
!= null && !value
.equals(""))
396 totalSize
+= value
.length();
400 if (width
> -1 && totalSize
> width
) {
401 int toDo
= totalSize
- width
;
402 for (int i
= fixedsizeFields
.length
- 1; toDo
> 0 && i
>= 0; i
--) {
403 if (!fixedsizeFields
[i
]) {
404 int valueLength
= values
[i
].length();
405 if (valueLength
> 0) {
406 if (valueLength
>= toDo
) {
407 values
[i
] = values
[i
].substring(0, valueLength
418 totalSize
= width
+ toDo
;
421 if (width
> -1 && numOfFieldsToExpand
> 0) {
422 int availablePadding
= width
- totalSize
;
424 if (availablePadding
> 0) {
425 int padPerItem
= availablePadding
/ numOfFieldsToExpand
;
426 int remainder
= availablePadding
% numOfFieldsToExpand
;
428 for (int i
= 0; i
< values
.length
; i
++) {
429 if (expandedFields
[i
]) {
431 values
[i
] = values
[i
]
432 + StringUtils
.padString("", remainder
);
435 if (padPerItem
> 0) {
436 values
[i
] = values
[i
]
437 + StringUtils
.padString("", padPerItem
);
447 for (int i
= 0; i
< values
.length
; i
++) {
448 currentSize
+= addToList(str
, values
[i
], currentSize
, width
);
451 return str
.toArray(new String
[] {});
455 * Update the information from this contact with the information in the
456 * given contact. Non present fields will be removed, new fields will be
457 * added, BKey'ed fields will be completed with the binary information known
461 * the contact with the newer information and optional BKeys
463 public void updateFrom(Contact vc
) {
466 List
<Data
> newDatas
= new LinkedList
<Data
>(vc
.datas
);
467 for (int i
= 0; i
< newDatas
.size(); i
++) {
468 Data data
= newDatas
.get(i
);
469 int bkey
= Parser
.getBKey(data
);
471 if (binaries
.containsKey(bkey
)) {
472 newDatas
.set(i
, binaries
.get(bkey
));
477 this.datas
= newDatas
;
478 this.nextBKey
= vc
.nextBKey
;
485 * Delete this {@link Contact} from its parent {@link Card} if any.
487 * @return TRUE in case of success
489 public boolean delete() {
490 if (parent
!= null) {
491 return parent
.remove(this);
498 * Check if this {@link Contact} has unsaved changes.
500 * @return TRUE if it has
502 public boolean isDirty() {
507 * Return a {@link String} representation of this contact, in vCard 2.1,
510 * @return the {@link String} representation
513 public String
toString() {
514 return toString(Format
.VCard21
, -1);
518 * Mark all the binary fields with a BKey number.
521 * force the marking, and reset all the numbers.
523 protected void updateBKeys(boolean force
) {
525 binaries
= new HashMap
<Integer
, Data
>();
529 if (binaries
== null) {
530 binaries
= new HashMap
<Integer
, Data
>();
533 for (Data data
: datas
) {
534 if (data
.isBinary() && (data
.getB64Key() <= 0 || force
)) {
535 binaries
.put(nextBKey
, data
);
536 data
.resetB64Key(nextBKey
++);
542 * Notify that this element has unsaved changes, and notify its parent of
545 protected void setDirty() {
547 if (this.parent
!= null)
548 this.parent
.setDirty();
552 * Notify this element <i>and all its descendants</i> that it is in pristine
553 * state (as opposed to dirty).
557 for (Data data
: datas
) {
563 * Set the parent of this {@link Contact} <i>and all its descendants</i>.
568 void setParent(Card parent
) {
569 this.parent
= parent
;
570 for (Data data
: datas
) {
571 data
.setParent(this);
576 * Add a {@link String} to the given {@link List}, but make sure it does not
577 * exceed the maximum size, and truncate it if needed to fit.
585 static private int addToList(List
<String
> list
, String add
,
586 int currentSize
, int maxSize
) {
587 if (add
== null || add
.length() == 0) {
594 if (currentSize
< maxSize
) {
595 if (currentSize
+ add
.length() >= maxSize
) {
596 add
= add
.substring(0, maxSize
- currentSize
);