import java.util.List;
import java.util.ListIterator;
+import be.nikiroo.jvcard.tui.StringUtils;
+
/**
* This class is basically a List with a parent and a "dirty" state check. It
* sends all commands down to the initial list, but will mark itself and its
equ = false;
} else {
// they represent the same item
- if (!((BaseClass) here).isEquals(there)) {
+ if (!((BaseClass) here).isEquals(there, false)) {
if (from != null)
from.add(here);
if (to != null)
* @param other
* the other instance
*
+ * @param contentOnly
+ * do not check the state of the object itslef, only its content
+ *
* @return TRUE if they are equivalent
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
- public boolean isEquals(BaseClass<E> other) {
+ public boolean isEquals(BaseClass<E> other, boolean contentOnly) {
if (other == null)
return false;
if (size() != other.size())
return false;
- if (!isSame(other))
- return false;
+ if (!contentOnly) {
+ if (!isSame(other))
+ return false;
- if (!getState().equals(other.getState()))
- return false;
+ if (!getState().equals(other.getState()))
+ return false;
+ }
Collections.sort(list, comparator);
Collections.sort(other.list, other.comparator);
for (int index = 0; index < size(); index++) {
- if (!((BaseClass) get(index)).isEquals(other.get(index)))
+ if (!((BaseClass) get(index)).isEquals(other.get(index), false))
return false;
}
return true;
}
+ /**
+ * Get the recursive state of the current object, i.e., its children. It
+ * represents the full state information about this object's children. It
+ * does not check the state of the object itself.
+ *
+ * @return a {@link String} representing the current content state of this
+ * object, i.e., its children
+ */
+ public String getContentState() {
+ StringBuilder builder = new StringBuilder();
+
+ for (E child : this) {
+ builder.append(child.getContentState());
+ }
+
+ return StringUtils.getHash(builder.toString());
+ }
+
/**
* Return the current ID of this object -- it is allowed to change over time
* (so, do not cache it).
/**
* Get the state of the current object, children <b>not included</b>. It
- * represents the full state information about this object, that is, two
- * objects with the same state (and class) must return TRUE if
- * {@link BaseClass#isEquals(BaseClass)} is called <b>and</b> their children
- * are equivalent.
+ * represents the full state information about this object, but do not check
+ * its children (see {@link BaseClass#getContentState()} for that).
*
* @return a {@link String} representing the current state of this object,
* children not included
public Card(File file, Format format) throws IOException {
this(Parser.parse(file, format));
- if (file != null) {
- if (file.exists()) {
- lastModified = file.lastModified();
- }
+ if (file != null && file.exists()) {
+ lastModified = file.lastModified();
}
this.format = format;
return false;
this.replaceListContent(Parser.parse(file, format));
+ lastModified = file.lastModified();
setPristine();
+
return true;
}
* @return the {@link String}
*/
public String toString(Format format) {
- return Parser.toString(this, format);
+ StringBuilder builder = new StringBuilder();
+ for (String line : Parser.toStrings(this, format)) {
+ builder.append(line);
+ builder.append("\r\n");
+ }
+ return builder.toString();
}
/**
*/
public String toString(Format format, int startingBKey) {
updateBKeys(false);
- return Parser.toString(this, format, startingBKey);
+
+ StringBuilder builder = new StringBuilder();
+ for (String line : Parser.toStrings(this, format, startingBKey)) {
+ builder.append(line);
+ builder.append("\r\n");
+ }
+
+ return builder.toString();
}
/**
package be.nikiroo.jvcard.parsers;
+import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
return contacts;
}
- // -1 = no bkeys
- public static String toString(Contact contact, int startingBKey) {
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * <p>
+ * Note that the BKey is actually not used in Pine mode.
+ * </p>
+ *
+ * @param card
+ * the card to convert
+ *
+ * @param startingBKey
+ * the starting BKey number (all the other will follow) or -1 for
+ * no BKey
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Contact contact, int startingBKey) {
// BKey is not used in pine mode
StringBuilder builder = new StringBuilder();
// note: save as pine means normal LN, nor CRLN
builder.append('\n');
- return builder.toString();
+ return Arrays.asList(new String[] { builder.toString() });
}
- public static String toString(Card card) {
- StringBuilder builder = new StringBuilder();
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * @param card
+ * the card to convert
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Card card) {
+ List<String> lines = new LinkedList<String>();
for (int index = 0; index < card.size(); index++) {
- builder.append(toString(card.get(index), -1));
+ lines.addAll(toStrings(card.get(index), -1));
}
- return builder.toString();
+ return lines;
}
}
}
}
- // -1 = no bkeys
- public static String toString(Card card, Format format) {
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * @param card
+ * the card to convert
+ *
+ * @param startingBKey
+ * the starting BKey number (all the other will follow) or -1 for
+ * no BKey
+ *
+ * @param format
+ * the output {@link Format} to use
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Card card, Format format) {
switch (format) {
case VCard21:
- return Vcard21Parser.toString(card);
+ return Vcard21Parser.toStrings(card);
case Abook:
- return AbookParser.toString(card);
+ return AbookParser.toStrings(card);
default:
throw new InvalidParameterException("Unknown format: "
}
}
- // -1 = no bkeys
- public static String toString(Contact contact, Format format,
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * @param card
+ * the card to convert
+ *
+ * @param startingBKey
+ * the starting BKey number (all the other will follow) or -1 for
+ * no BKey
+ *
+ * @param format
+ * the output {@link Format} to use
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Contact contact, Format format,
int startingBKey) {
switch (format) {
case VCard21:
- return Vcard21Parser.toString(contact, startingBKey);
+ return Vcard21Parser.toStrings(contact, startingBKey);
case Abook:
- return AbookParser.toString(contact, startingBKey);
+ return AbookParser.toStrings(contact, startingBKey);
default:
throw new InvalidParameterException("Unknown format: "
return contacts;
}
- // -1 = no bkeys
- public static String toString(Contact contact, int startingBKey) {
- StringBuilder builder = new StringBuilder();
-
- builder.append("BEGIN:VCARD");
- builder.append("\r\n");
- builder.append("VERSION:2.1");
- builder.append("\r\n");
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * @param card
+ * the card to convert
+ *
+ * @param startingBKey
+ * the starting BKey number (all the other will follow) or -1 for
+ * no BKey
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Contact contact, int startingBKey) {
+ List<String> lines = new LinkedList<String>();
+
+ lines.add("BEGIN:VCARD");
+ lines.add("VERSION:2.1");
for (Data data : contact) {
StringBuilder dataBuilder = new StringBuilder();
if (data.getGroup() != null && !data.getGroup().trim().equals("")) {
}
stop = Math.min(stop, dataBuilder.length());
- if (continuation)
- builder.append(' ');
- builder.append(dataBuilder, 0, stop);
- builder.append("\r\n");
+ if (continuation) {
+ lines.add(' ' + dataBuilder.substring(0, stop));
+ } else {
+ lines.add(dataBuilder.substring(0, stop));
+ }
dataBuilder.delete(0, stop);
continuation = true;
}
}
- builder.append("END:VCARD");
- builder.append("\r\n");
+ lines.add("END:VCARD");
- return builder.toString();
+ return lines;
}
- public static String toString(Card card) {
- StringBuilder builder = new StringBuilder();
+ /**
+ * Return a {@link String} representation of the given {@link Card}, line by
+ * line.
+ *
+ * @param card
+ * the card to convert
+ *
+ * @return the {@link String} representation
+ */
+ public static List<String> toStrings(Card card) {
+ List<String> lines = new LinkedList<String>();
for (Contact contact : card) {
- builder.append(toString(contact, -1));
+ lines.addAll(toStrings(contact, -1));
}
- builder.append("\r\n");
-
- return builder.toString();
+ return lines;
}
/**
break;
case POST:
synchronized (cardsLock) {
- doPostCard(cmd.getParam(), s.receiveBlock());
- s.sendBlock();
+ s.sendLine(doPostCard(cmd.getParam(), s.receiveBlock()));
break;
}
case LIST:
// timestamp:
lines.add(StringUtils.fromTime(card.getLastModified()));
-
- // TODO: !!! fix this !!!
- for (String line : card.toString(Format.VCard21).split("\r\n")) {
- lines.add(line);
- }
+ lines.addAll(Parser.toStrings(card, Format.VCard21));
}
}
* @param data
* the data to save
*
+ * @return the date of last modification
+ *
* @throws IOException
* in case of error
*/
- private void doPostCard(String name, List<String> data) throws IOException {
+ private String doPostCard(String name, List<String> data)
+ throws IOException {
if (name != null && name.length() > 0) {
File vcf = new File(dataDir.getAbsolutePath() + File.separator
+ name);
Card card = new Card(Parser.parse(data, Format.VCard21));
card.saveAs(vcf, Format.VCard21);
+
+ return StringUtils.fromTime(vcf.lastModified());
}
+
+ return "";
}
}
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.InvalidParameterException;
+import java.time.LocalDate;
import java.util.List;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
if (tsOriginal != -1) {
Card local = new Card(getCache(cacheDir), Format.VCard21);
Card original = new Card(getCache(cacheDirOrig), Format.VCard21);
- localChanges = !local.isEquals(original);
+ localChanges = !local.isEquals(original, true);
}
Verb action = null;
setLastModified(data.remove(0));
Card server = new Card(Parser.parse(data, Format.VCard21));
card.replaceListContent(server);
+
if (card.isDirty())
card.save();
card.saveAs(getCache(cacheDirOrig), Format.VCard21);
case POST:
s.sendCommand(Verb.POST, name);
s.sendLine(card.toString(Format.VCard21));
+ card.saveAs(getCache(cacheDirOrig), Format.VCard21);
+ setLastModified(s.receiveLine());
break;
default:
// TODO
return StringUtils.toTime(line);
} catch (FileNotFoundException e) {
return -1;
- } catch (IOException e) {
+ } catch (Exception e) {
return -1;
}
}
package be.nikiroo.jvcard.tui;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.text.Normalizer;
import java.text.Normalizer.Form;
import java.text.ParseException;
return -1;
}
}
+
+ /**
+ * Return a hash of the given {@link String}.
+ *
+ * @param input
+ * the input data
+ *
+ * @return the hash
+ */
+ static public String getHash(String input) {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+ md.update(input.getBytes());
+ byte byteData[] = md.digest();
+
+ StringBuffer hexString = new StringBuffer();
+ for (int i = 0; i < byteData.length; i++) {
+ String hex = Integer.toHexString(0xff & byteData[i]);
+ if (hex.length() == 1)
+ hexString.append('0');
+ hexString.append(hex);
+ }
+
+ return hexString.toString();
+ } catch (NoSuchAlgorithmException e) {
+ // all JVM most probably have an MD5 implementation, but even if
+ // not, returning the input is "correct", if inefficient and
+ // unsecure
+ return input;
+ }
+ }
}