X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjvcard%2FContact.java;h=d75d3380326a8bfde6c245f959dd6ff916b1f07f;hb=7671a2499e6f0d6c8e0765b36c18c1e89bc457c5;hp=f2a8b01b5ac4be470383f2b29934fde412bbeca8;hpb=9c8baf0c360173b864683176c567757429c4fb12;p=jvcard.git diff --git a/src/be/nikiroo/jvcard/Contact.java b/src/be/nikiroo/jvcard/Contact.java index f2a8b01..d75d338 100644 --- a/src/be/nikiroo/jvcard/Contact.java +++ b/src/be/nikiroo/jvcard/Contact.java @@ -1,12 +1,17 @@ package be.nikiroo.jvcard; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.UUID; import be.nikiroo.jvcard.parsers.Format; import be.nikiroo.jvcard.parsers.Parser; +import be.nikiroo.jvcard.resources.StringUtils; /** * A contact is the information that represent a contact person or organisation. @@ -14,12 +19,9 @@ import be.nikiroo.jvcard.parsers.Parser; * @author niki * */ -public class Contact { - private List datas; +public class Contact extends BaseClass { private int nextBKey = 1; private Map binaries; - private boolean dirty; - private Card parent; /** * Create a new Contact from the given information. Note that the BKeys data @@ -29,63 +31,31 @@ public class Contact { * the information about the contact */ public Contact(List content) { - this.datas = new LinkedList(); - - boolean fn = false; - boolean n = false; - for (Data data : content) { - if (data.getName().equals("N")) { - n = true; - } else if (data.getName().equals("FN")) { - fn = true; - } - - if (!data.getName().equals("VERSION")) { - datas.add(data); - } - } - - // required fields: - if (!n) { - datas.add(new Data(null, "N", "", null)); - } - if (!fn) { - datas.add(new Data(null, "FN", "", null)); - } - + super(load(content)); updateBKeys(true); } /** - * Return the informations (note: this is the actual list, be careful). - * - * @return the list of data anout this contact - */ - public List getContent() { - return datas; - } - - /** - * Return the preferred Data field with the given name, or NULL if none. + * Return the preferred Data field with the given name, the first one if + * none is preferred, or NULL if none at all. * * @param name * the name to look for - * @return the Data field, or NULL + * + * @return the {@link Data} field, or NULL */ public Data getPreferredData(String name) { - Data first = null; + Data pref = null; + int ipref = Integer.MAX_VALUE; for (Data data : getData(name)) { - if (first == null) - first = data; - for (TypeInfo type : data.getTypes()) { - if (type.getName().equals("TYPE") - && type.getValue().equals("pref")) { - return data; - } - } + if (pref == null) + pref = data; + + if (data.getPreferred() < ipref) + pref = data; } - return first; + return pref; } /** @@ -113,7 +83,7 @@ public class Contact { public List getData(String name) { List found = new LinkedList(); - for (Data data : datas) { + for (Data data : this) { if (data.getName().equals(name)) found.add(data); } @@ -122,23 +92,53 @@ public class Contact { } /** - * Return a {@link String} representation of this contact. + * Return a {@link String} representation of this contact formated + * accordingly to the given format. + * + *

+ * The format is basically a list of field names separated by a pipe and + * optionally parametrised with the 'at' (@) symbol. The parameters allows + * you to: + *

    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ *

+ * + *

+ * In case of lists or multiple-fields values, you can select a specific + * list or field with: + *

    + *
  • FIELD@(0): select the first value in a list
  • + *
  • FIELD@[1]: select the second field in a multiple-fields value
  • + *
+ *

+ * + *

+ * You can also add a fixed text if it starts with a simple-quote ('). + *

+ * + *

+ * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x" + *

* * @param format - * the {@link Format} to use - * @param startingBKey - * the starting BKey or -1 for no BKeys + * the format to use + * @param separator + * the separator {@link String} to use between fields + * * @return the {@link String} representation */ - public String toString(Format format, int startingBKey) { - updateBKeys(false); - return Parser.toString(this, format, startingBKey); + public String toString(String format, String separator) { + return toString(format, separator, null, -1, true, false); } /** * Return a {@link String} representation of this contact formated * accordingly to the given format. * + *

* The format is basically a list of field names separated by a pipe and * optionally parametrised. The parameters allows you to: *

    @@ -146,71 +146,169 @@ public class Contact { *
  • @n: limit the size to a fixed value 'n'
  • *
  • @+: expand the size of this field as much as possible
  • *
+ *

* - * Example: "N@10|FN@20|NICK@+|PHOTO@x" + *

+ * In case of lists or multiple-fields values, you can select a specific + * list or field with: + *

    + *
  • FIELD@(0): select the first value in a list
  • + *
  • FIELD@[1]: select the second field in a multiple-fields value
  • + *
+ *

+ * + *

+ * You can also add a fixed text if it starts with a simple-quote ('). + *

+ * + *

+ * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x" + *

* * @param format * the format to use * @param separator * the separator {@link String} to use between fields + * @param padding + * the {@link String} to use for left and right padding * @param width * a fixed width or -1 for "as long as needed" * + * @param unicode + * allow Uniode or only ASCII characters + * * @return the {@link String} representation */ - public String toString(String format, String separator, int width) { + public String toString(String format, String separator, String padding, + int width, boolean unicode, boolean removeAccents) { StringBuilder builder = new StringBuilder(); - String[] formatFields = format.split("\\|"); - if (width > -1 && separator != null && separator.length() > 0 - && formatFields.length > 1) { - int swidth = (formatFields.length - 1) * separator.length(); - if (swidth >= width) { - int num = width / separator.length(); - int remainder = width % separator.length(); - - if (remainder > 0) - num++; - - while (builder.length() < width) { - if (builder.length() + separator.length() <= width) - builder.append(separator); - else - builder.append(separator - .substring(0, (builder.length() + separator - .length()) - - width)); - } + for (String str : toStringArray(format, separator, padding, width, + unicode)) { + builder.append(str); + } - return builder.toString(); - } + return builder.toString(); + } - width -= swidth; + /** + * Return a {@link String} representation of this contact formated + * accordingly to the given format, part by part. + * + *

+ * The format is basically a list of field names separated by a pipe and + * optionally parametrised. The parameters allows you to: + *

    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ *

+ * + *

+ * In case of lists or multiple-fields values, you can select a specific + * list or field with: + *

    + *
  • FIELD@(0): select the first value in a list
  • + *
  • FIELD@[1]: select the second field in a multiple-fields value
  • + *
+ *

+ * + *

+ * You can also add a fixed text if it starts with a simple-quote ('). + *

+ * + *

+ * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x" + *

+ * + * @param format + * the format to use + * @param separator + * the separator {@link String} to use between fields + * @param padding + * the {@link String} to use for left and right padding + * @param width + * a fixed width or -1 for "as long as needed" + * + * @param unicode + * allow Uniode or only ASCII characters + * + * @return the {@link String} representation + */ + public String[] toStringArray(String format, String separator, + String padding, int width, boolean unicode) { + if (width > -1) { + int numOfFields = format.split("\\|").length; + if (separator != null) + width -= (numOfFields - 1) * separator.length(); + if (padding != null) + width -= (numOfFields) * (2 * padding.length()); + + if (width < 0) + width = 0; } - for (String str : toStringArray(format, width)) { - builder.append(str); + List str = new LinkedList(); + + boolean first = true; + for (String s : toStringArray(format, width, unicode)) { + if (!first) { + str.add(separator); + } + + if (padding != null) + str.add(padding + s + padding); + else + str.add(s); + + first = false; } - return builder.toString(); + return str.toArray(new String[] {}); } /** * Return a {@link String} representation of this contact formated * accordingly to the given format, part by part. * + *

* The format is basically a list of field names separated by a pipe and - * optionally parametrised. See {@link Contact#toString} for more - * information about the format. + * optionally parametrised. The parameters allows you to: + *

    + *
  • @x: show only a present/not present info
  • + *
  • @n: limit the size to a fixed value 'n'
  • + *
  • @+: expand the size of this field as much as possible
  • + *
+ *

+ * + *

+ * In case of lists or multiple-fields values, you can select a specific + * list or field with: + *

    + *
  • FIELD@(0): select the first value in a list
  • + *
  • FIELD@[1]: select the second field in a multiple-fields value
  • + *
+ *

+ * + *

+ * You can also add a fixed text if it starts with a simple-quote ('). + *

+ * + *

+ * Example: "'Contact: |N@10|FN@20|NICK@+|PHOTO@x" + *

* * @param format * the format to use * @param width * a fixed width or -1 for "as long as needed" - * + * @param unicode + * allow Uniode or only ASCII characters + * * @return the {@link String} representation */ - public String[] toStringArray(String format, int width) { + public String[] toStringArray(String format, int width, boolean unicode) { List str = new LinkedList(); String[] formatFields = format.split("\\|"); @@ -221,6 +319,10 @@ public class Contact { int totalSize = 0; if (width == 0) { + for (int i = 0; i < formatFields.length; i++) { + str.add(""); + } + return str.toArray(new String[] {}); } @@ -230,8 +332,11 @@ public class Contact { int size = -1; boolean binary = false; boolean expand = false; + int fieldNum = -1; + int valueNum = -1; - if (field.contains("@")) { + if (field.length() > 0 && field.charAt(0) != '\'' + && field.contains("@")) { String[] opts = field.split("@"); if (opts.length > 0) field = opts[0]; @@ -242,21 +347,56 @@ public class Contact { } else if (opt.equals("+")) { expand = true; numOfFieldsToExpand++; + } else if (opt.length() > 0 && opt.charAt(0) == '(') { + try { + opt = opt.substring(1, opt.length() - 1); + valueNum = Integer.parseInt(opt); + } catch (Exception e) { + } + } else if (opt.length() > 0 && opt.charAt(0) == '[') { + try { + opt = opt.substring(1, opt.length() - 1); + fieldNum = Integer.parseInt(opt); + } catch (Exception e) { + } } else { try { size = Integer.parseInt(opt); - } catch (Exception e) { + } catch (NumberFormatException e) { } } } } - String value = getPreferredDataValue(field); - if (value == null) + String value = null; + if (field.length() > 0 && field.charAt(0) == '\'') { + value = field.substring(1); + } else if (valueNum >= 0) { + List vv = getPreferredData(field).getValues(); + if (valueNum < vv.size()) { + value = vv.get(valueNum); + } + } else if (fieldNum >= 0) { + List ff = getPreferredData(field).getFields(); + if (fieldNum < ff.size()) { + value = ff.get(fieldNum); + } + } else { + // we don't need the *data* in binary mode... + if (binary) + value = getData(field).size() > 0 ? "x" : null; + else + value = getPreferredDataValue(field); + } + + if (value == null) { value = ""; + } else { + value = StringUtils.sanitize(value, unicode); + } if (size > -1) { - value = fixedString(value, size); + value = StringUtils.padString(value, size); } expandedFields[i] = expand; @@ -306,14 +446,12 @@ public class Contact { if (expandedFields[i]) { if (remainder > 0) { values[i] = values[i] - + new String(new char[remainder]).replace( - '\0', ' '); + + StringUtils.padString("", remainder); remainder = 0; } if (padPerItem > 0) { values[i] = values[i] - + new String(new char[padPerItem]).replace( - '\0', ' '); + + StringUtils.padString("", padPerItem); } } } @@ -330,74 +468,6 @@ public class Contact { return str.toArray(new String[] {}); } - /** - * Fix the size of the given {@link String} either with space-padding or by - * shortening it. - * - * @param string - * the {@link String} to fix - * @param size - * the size of the resulting {@link String} - * - * @return the fixed {@link String} of size size - */ - static private String fixedString(String string, int size) { - int length = string.length(); - - if (length > size) - string = string.substring(0, size); - else if (length < size) - string = string - + new String(new char[size - length]).replace('\0', ' '); - - return string; - } - - /** - * Add a {@link String} to the given {@link List}, but make sure it does not - * exceed the maximum size, and truncate it if needed to fit. - * - * @param list - * @param add - * @param currentSize - * @param maxSize - * @return - */ - static private int addToList(List list, String add, - int currentSize, int maxSize) { - if (add == null || add.length() == 0) { - if (add != null) - list.add(add); - return 0; - } - - if (maxSize > -1) { - if (currentSize < maxSize) { - if (currentSize + add.length() >= maxSize) { - add = add.substring(0, maxSize - currentSize); - } - } else { - add = ""; - } - } - - if (add.length() > 0) { - list.add(add); - } - - return add.length(); - } - - /** - * Return a {@link String} representation of this contact, in vCard 2.1, - * without BKeys. - * - * @return the {@link String} representation - */ - public String toString() { - return toString(Format.VCard21, -1); - } - /** * Update the information from this contact with the information in the * given contact. Non present fields will be removed, new fields will be @@ -410,7 +480,7 @@ public class Contact { public void updateFrom(Contact vc) { updateBKeys(false); - List newDatas = new LinkedList(vc.datas); + List newDatas = new LinkedList(vc); for (int i = 0; i < newDatas.size(); i++) { Data data = newDatas.get(i); int bkey = Parser.getBKey(data); @@ -421,11 +491,29 @@ public class Contact { } } - this.datas = newDatas; + replaceListContent(newDatas); this.nextBKey = vc.nextBKey; + } - setParent(parent); - setDirty(); + @Override + public String getId() { + return "" + getPreferredDataValue("UID"); + } + + @Override + public String getState() { + return getId(); + } + + /** + * Return a {@link String} representation of this contact, in vCard 2.1, + * without BKeys. + * + * @return the {@link String} representation + */ + @Override + public String toString() { + return "[Contact: " + getPreferredDataValue("FN") + "]"; } /** @@ -444,7 +532,7 @@ public class Contact { binaries = new HashMap(); } - for (Data data : datas) { + for (Data data : this) { if (data.isBinary() && (data.getB64Key() <= 0 || force)) { binaries.put(nextBKey, data); data.resetB64Key(nextBKey++); @@ -452,24 +540,80 @@ public class Contact { } } - public boolean isDirty() { - return dirty; + /** + * Load the data from the given {@link File} under the given {@link Format}. + * + * @param file + * the {@link File} to load from + * @param format + * the {@link Format} to load as + * + * @return the list of elements + * @throws IOException + * in case of IO error + */ + static private List load(List content) { + List datas = new ArrayList(); + + boolean fn = false; + boolean n = false; + boolean uid = false; + if (content != null) { + for (Data data : content) { + if (data.getName().equals("N")) { + n = true; + } else if (data.getName().equals("FN")) { + fn = true; + } else if (data.getName().equals("UID")) { + uid = true; + } + + if (!data.getName().equals("VERSION")) { + datas.add(data); + } + } + } + + // required fields: + if (!n) // required since vCard 3.0, supported in 2.1 + datas.add(new Data(null, "N", "", null)); + if (!fn) // not required anymore but still supported in 4.0 + datas.add(new Data(null, "FN", "", null)); + if (!uid) // supported by vCard, required by this program + datas.add(new Data(null, "UID", UUID.randomUUID().toString(), null)); + + return datas; } /** - * Notify that this element has unsaved changes, and notify its parent of - * the same if any. + * Add a {@link String} to the given {@link List}, but make sure it does not + * exceed the maximum size, and truncate it if needed to fit. + * + * @param list + * @param add + * @param currentSize + * @param maxSize + * @return */ - protected void setDirty() { - this.dirty = true; - if (this.parent != null) - this.parent.setDirty(); - } + static private int addToList(List list, String add, + int currentSize, int maxSize) { + if (add == null || add.length() == 0) { + if (add != null) + list.add(add); + return 0; + } - public void setParent(Card parent) { - this.parent = parent; - for (Data data : datas) { - data.setParent(this); + if (maxSize > -1) { + if (currentSize < maxSize) { + if (currentSize + add.length() >= maxSize) { + add = add.substring(0, maxSize - currentSize); + } + } else { + add = ""; + } } + + list.add(add); + return add.length(); } }