X-Git-Url: http://git.nikiroo.be/?a=blobdiff_plain;f=src%2Fbe%2Fnikiroo%2Fjvcard%2FBaseClass.java;h=42d4b0dd7f57090a056870f13280f573e6d2146c;hb=f04a32e97c847d7e2551037a4d5f6a070879215c;hp=abaa9ccbd37d04fe7a48fc8a2a57b8c59ebb267a;hpb=cf77cb3542f2aefbebdb9aa00b358dbeb4489a73;p=jvcard.git diff --git a/src/be/nikiroo/jvcard/BaseClass.java b/src/be/nikiroo/jvcard/BaseClass.java index abaa9cc..42d4b0d 100644 --- a/src/be/nikiroo/jvcard/BaseClass.java +++ b/src/be/nikiroo/jvcard/BaseClass.java @@ -1,5 +1,6 @@ package be.nikiroo.jvcard; +import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -9,22 +10,27 @@ import java.util.LinkedList; import java.util.List; import java.util.ListIterator; -import be.nikiroo.jvcard.tui.StringUtils; +import be.nikiroo.utils.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 * children as dirty or not when needed. * - * All child elements can identify their parent. + *

+ * All child elements can identify their parent, and must not be added to 2 + * different objects without without first being removed from the previous one. + *

* + *

* The dirty state is bubbling up (when dirty = true) or down (when dirty = * false) -- so, making changes to a child element will also mark its parent as * "dirty", and marking an element as pristine will also affect all its child * elements. + *

* * @author niki - * + * * @param * the type of the child elements */ @@ -119,8 +125,8 @@ public abstract class BaseClass> implements List { * If not equals, the differences will be represented by the given * {@link List}s if they are not NULL. *
    - *
  • addedwill represent the elements in list but not in - * this
  • + *
  • added will represent the elements in list but not + * in this
  • *
  • removed will represent the elements in this but not * in list
  • *
  • from will represent the elements in list that are @@ -155,17 +161,20 @@ public abstract class BaseClass> implements List { Collections.sort(other, comparator); boolean equ = true; - while (mine.size() > 0 || other.size() > 0) { - E here = (mine.size() > 0) ? mine.remove(0) : null; - E there = (other.size() > 0) ? other.remove(0) : null; + E here = mine.size() > 0 ? mine.remove(0) : null; + E there = other.size() > 0 ? other.remove(0) : null; - if (here == null || comparator.compare(here, there) > 0) { + while (here != null || there != null) { + if (here == null + || (there != null && comparator.compare(here, there) > 0)) { if (added != null) added.add(there); + there = null; equ = false; } else if (there == null || comparator.compare(here, there) < 0) { if (removed != null) removed.add(here); + here = null; equ = false; } else { // they represent the same item @@ -176,7 +185,14 @@ public abstract class BaseClass> implements List { to.add(there); equ = false; } + here = null; + there = null; } + + if (here == null && mine.size() > 0) + here = mine.remove(0); + if (there == null && other.size() > 0) + there = other.remove(0); } return equ; @@ -240,21 +256,65 @@ public abstract class BaseClass> implements List { } /** - * 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. + * Get the recursive state of the current object, i.e., its children + * included. It represents the full state information about this object's + * children. It may not contains spaces nor new lines. + * + *

    + * Not that this state is lossy. You cannot retrieve the data from + * the state, it can only be used as an ID to check if data are identical. + *

    + * + * @param self + * also include state information about the current object itself + * (as opposed to its children) * * @return a {@link String} representing the current content state of this - * object, i.e., its children + * object, i.e., its children included */ - public String getContentState() { + public String getContentState(boolean self) { StringBuilder builder = new StringBuilder(); + buildContentStateRaw(builder, self); + return StringUtils.getMd5Hash(builder.toString()); + } + /** + * Return the (first) child element with the given ID or NULL if not found. + * + * @param id + * the id to look for + * + * @return the child element or NULL + */ + public E getById(String id) { for (E child : this) { - builder.append(child.getContentState()); + if (id == null) { + if (child.getId() == null) + return child; + } else { + if (id.equals(child.getId())) + return child; + } } - return StringUtils.getHash(builder.toString()); + return null; + } + + /** + * Return a {@link String} that can be used to identify this object in DEBUG + * mode, i.e., a "toString" method that can identify the object's content + * but still be readable in a log. + * + * @param depth + * the depth into which to descend (0 = only this object, not its + * children, negative value for infinite depth) + * + * @return the debug {@link String} + */ + public String getDebugInfo(int depth) { + StringBuilder builder = new StringBuilder(); + getDebugInfo(builder, depth, 0); + return builder.toString(); } /** @@ -268,7 +328,14 @@ public abstract class BaseClass> implements List { /** * Get the state of the current object, children not included. It * represents the full state information about this object, but do not check - * its children (see {@link BaseClass#getContentState()} for that). + * its children (see {@link BaseClass#getContentState()} for that). It may + * not contains spaces nor new lines. + * + *

    + * Not that this state is lossy. You cannot retrieve the data from + * the state, it can only be used as an ID to check if the data are + * identical. + *

    * * @return a {@link String} representing the current state of this object, * children not included @@ -276,7 +343,65 @@ public abstract class BaseClass> implements List { abstract public String getState(); /** - * Notify that this element has unsaved changes. + * Get the recursive state of the current object, i.e., its children + * included. It represents the full state information about this object's + * children. + *

    + * It is not hashed. + * + * @param builder + * the {@link StringBuilder} that will represent the current + * content state of this object, i.e., its children included + * @param self + * also include state information about the current object itself + * (as opposed to its children) + */ + void buildContentStateRaw(StringBuilder builder, boolean self) { + Collections.sort(this.list, comparator); + if (self) + builder.append(getState()); + for (E child : this) { + child.buildContentStateRaw(builder, true); + } + } + + /** + * Populate a {@link StringBuilder} that can be used to identify this object + * in DEBUG mode, i.e., a "toString" method that can identify the object's + * content but still be readable in a log. + * + * @param depth + * the depth into which to descend (0 = only this object, not its + * children, negative value for infinite depth) + * + * @param tab + * the current tabulation increment + */ + void getDebugInfo(StringBuilder builder, int depth, int tab) { + for (int i = 0; i < tab; i++) + builder.append(" "); + builder.append(getContentState(false) + " " + getId()); + + if (depth != 0) + builder.append(": ["); + + if (depth != 0) { + for (E child : this) { + builder.append("\n"); + child.getDebugInfo(builder, depth - 1, tab + 1); + } + } + if (depth != 0) { + builder.append("\n"); + for (int i = 0; i < tab; i++) + builder.append(" "); + builder.append("]"); + } + } + + /** + * Notify that this element and all its parent elements has unsaved + * changes. */ void setDirty() { dirty = true; @@ -297,7 +422,10 @@ public abstract class BaseClass> implements List { } /** - * Set the parent of this element and all its descendants. + * Set the parent of this element. + *

    + * Will also check and fix if needed the parent (this) of all its + * descendants (recursively). * * @param parent * the new parent @@ -309,6 +437,38 @@ public abstract class BaseClass> implements List { } } + /** + * Escape the given value to VCF standard. + * + * @param value + * the value to escape + * + * @return the escaped value + */ + protected String escape(String value) { + if (value == null) + return null; + + return value.replaceAll(",", "\\\\,").replaceAll(";", "\\\\;") + .replaceAll("\n", "\\\\n"); + } + + /** + * Unescape the given value from the VCF standard. + * + * @param value + * the value to unescape + * + * @return the unescaped value + */ + protected String unescape(String value) { + if (value == null) + return null; + + return value.replaceAll("\\\\,", ",").replaceAll("\\\\;", ";") + .replaceAll("\\\\n", "\n"); + } + /** * Each element that leaves the parent will pass trough here. * @@ -316,6 +476,12 @@ public abstract class BaseClass> implements List { * the element to remove from this */ private void _leave(E child) { + if (child.parent != null && child.parent != this) { + throw new InvalidParameterException( + "You are removing this child from its rightful parent, it must be yours to do so"); + } + + child.parent = null; setDirty(); } @@ -336,6 +502,11 @@ public abstract class BaseClass> implements List { * the element to add to this */ private void _enter(E child, boolean initialLoad) { + if (child.parent != null && child.parent != this) { + throw new InvalidParameterException( + "You are stealing this child from its rightful parent, you must remove it first"); + } + child.setParent(this); if (!initialLoad) { setDirty();