package be.nikiroo.jvcard;
+import java.security.InvalidParameterException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
* 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.
+ * <p>
+ * 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.
+ * </p>
*
+ * <p>
* 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.
+ * </p>
*
* @author niki
*
* If not equals, the differences will be represented by the given
* {@link List}s if they are not NULL.
* <ul>
- * <li><tt>added</tt>will represent the elements in <tt>list</tt> but not in
- * <tt>this</tt></li>
+ * <li><tt>added</tt> will represent the elements in <tt>list</tt> but not
+ * in <tt>this</tt></li>
* <li><tt>removed</tt> will represent the elements in <tt>this</tt> but not
* in <tt>list</tt></li>
* <li><tt>from<tt> will represent the elements in <tt>list</tt> that are
}
/**
- * Get the recursive state of the current object, i.e., its children. It
- * represents the full state information about this object's children.
+ * 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.
+ *
+ * <p>
+ * Not that this state is <b>lossy</b>. You cannot retrieve the data from
+ * the state, it can only be used as an ID to check if data are identical.
+ * </p>
+ *
+ * @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 included
*/
- public String getContentState() {
+ public String getContentState(boolean self) {
StringBuilder builder = new StringBuilder();
- buildContentStateRaw(builder);
+ buildContentStateRaw(builder, self);
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)
+ *
+ * @return the debug {@link String}
+ */
+ public String getDebugInfo(int depth) {
+ StringBuilder builder = new StringBuilder();
+ getDebugInfo(builder, depth, 0);
+ return 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, 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.
+ *
+ * <p>
+ * Not that this state is <b>lossy</b>. You cannot retrieve the data from
+ * the state, it can only be used as an ID to check if thw data are
+ * identical.
+ * </p>
*
* @return a {@link String} representing the current state of this object,
* children not included
abstract public String getState();
/**
- * Get the recursive state of the current object, i.e., its children. It
- * represents the full state information about this object's children.
+ * 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) {
- builder.append(getState());
+ void buildContentStateRaw(StringBuilder builder, boolean self) {
+ Collections.sort(this.list, comparator);
+ if (self)
+ builder.append(getState());
for (E child : this) {
- child.buildContentStateRaw(builder);
+ 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)
+ *
+ * @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("]");
}
}
}
}
+ /**
+ * 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.
*
* 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();
}
* 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();