[jdom-interest] Making setMixedContent appear atomic

Jason Hunter jhunter at collab.net
Thu Apr 12 20:38:59 PDT 2001


> Here's my feedback in the form of a modified copy of the setMixedContent
> method. After reading through today's digest of the mailing list the answer
> seems too simple; I think I must be missing something. 

Well, it turns out that making setMixedContent() appear atomic in case
of error is in fact harder than expected.  The difficulty comes from the
fact that parentage has to be managed carefully so that in case of error
all changes in parentage are reversed but in case of success all changes
are made.  You have to be careful so when you reverse you don't undo
something that was never done!  My current best attempt is attached
below.  It passes the various tests I've thrown at it.  If anyone spots
an error, please let me know.

The code would look better if I could do a look-ahead-scan but the rules
for what's a legal add and what's not are tricky in the case of Element
because there's all the tree integrity content to check.  I'd rather not
put all that logic here.  Plus detecting problems like someone having
the same element instance twice in the list wouldn't appear during a
simple scan but would appear during the actual add!  So look-ahead is
effectively out.

    public Element setMixedContent(List mixedContent) {

        if (mixedContent == null) {
            return this;
        }

        // Save list with original content and create a new list
        LinkedList oldContent = content;
        content = new LinkedList();

        RuntimeException ex = null;
        int itemsAdded = 0;

        try {
            for (Iterator i = mixedContent.iterator(); i.hasNext(); ) {
                Object obj = i.next();
                if (obj instanceof Element) {
                    addContent((Element)obj);
                }
                else if (obj instanceof String) {
                    addContent((String)obj);
                }
                else if (obj instanceof Comment) {
                    addContent((Comment)obj);
                }
                else if (obj instanceof ProcessingInstruction) {
                    addContent((ProcessingInstruction)obj);
                }
                else if (obj instanceof CDATA) {
                    addContent((CDATA)obj);
                }
                else if (obj instanceof Entity) {
                    addContent((Entity)obj);
                }
                else {
                    throw new IllegalAddException(
                      "An Element may directly contain only objects of
type " +
                      "String, Element, Comment, CDATA, Entity, and " +
                      "ProcessingInstruction: " +
                      (obj == null ? "null" : obj.getClass().getName())
+
                      " is not allowed");
                }
                itemsAdded++;
            }
        }
        catch (RuntimeException e) {
            ex = e;
        }
        finally {
            if (ex != null) {
                // Restore the original state
                content = oldContent;
                // Unmodify all modified elements.  DO NOT change any
later
                // elements tho because they may already have parents!
                Iterator i = mixedContent.iterator();
                while (itemsAdded-- > 0) {
                    Object obj = i.next();
                    if (obj instanceof Element) {
                        ((Element)obj).setParent(null);
                    }
                    else if (obj instanceof Comment) {
                        ((Comment)obj).setParent(null);
                    }
                    else if (obj instanceof ProcessingInstruction) {
                        ((ProcessingInstruction)obj).setParent(null);
                    }
                    else if (obj instanceof Entity) {
                        ((Entity)obj).setParent(null);
                    }
                }
                throw ex;
            }
        }

        // Remove parentage on the old content
        Iterator itr = oldContent.iterator();
        while (itr.hasNext()) {
            Object obj = itr.next();
            if (obj instanceof Element) {
                ((Element)obj).setParent(null);
            }
            else if (obj instanceof Comment) {
                ((Comment)obj).setParent(null);
            }
            else if (obj instanceof ProcessingInstruction) {
                ((ProcessingInstruction)obj).setParent(null);
            }
            else if (obj instanceof Entity) {
                ((Entity)obj).setParent(null);
            }
        }

        return this;
    }

The Document version is even more fun because of the special handling to
enforce the single root element rule.  Note I removed the "protected
rootElement" variable because managing it makes this code nearly
indecipherable.  Now getRootElement() just scans the Document list of
content.

    public Document setMixedContent(List mixedContent) {

        if (mixedContent == null) {
            return this;
        }

        // Save original content and create a new list
        Element oldRoot = getRootElement();
        List oldContent = content;
        content = new LinkedList();

        RuntimeException ex = null;
        boolean didRoot = false;
        int itemsAdded = 0;

        try {
            for (Iterator i = mixedContent.iterator(); i.hasNext(); ) {
                Object obj = i.next();
                if (obj instanceof Element) {
                    if (didRoot == false) {
                        setRootElement((Element)obj);
                        // Manually remove old root's doc ref, because
                        // setRootElement() can't see the old content
list to
                        // do it for us
                        oldRoot.setDocument(null);
                        didRoot = true;
                    }
                    else {
                        throw new IllegalAddException(
                          "A Document may contain only one root
element");
                    }
                }
                else if (obj instanceof Comment) {
                    addContent((Comment)obj);
                }
                else if (obj instanceof ProcessingInstruction) {
                    addContent((ProcessingInstruction)obj);
                }
                else {
                    throw new IllegalAddException(
                      "A Document may directly contain only objects of
type " +
                      "Element, Comment, and ProcessingInstruction: " +
                      (obj == null ? "null" : obj.getClass().getName())
+
                      " is not allowed");
                }
                itemsAdded++;
            }

            // Make sure they set a root element
            if (didRoot == false) {
                throw new IllegalAddException(
                    "A Document must contain a root element");
            }
        }
        catch (RuntimeException e) {
            ex = e;
        }
        finally {
            if (ex != null) {
                // Restore the original state and parentage and throw
                content = oldContent;
                oldRoot.setDocument(this);
                // Unmodify all modified elements.  DO NOT change any
later
                // elements tho because they may already have parents!
                Iterator itr = mixedContent.iterator();
                while (itemsAdded-- > 0) {
                    Object obj = itr.next();
                    if (obj instanceof Element) {
                        ((Element)obj).setDocument(null);
                    }
                    else if (obj instanceof Comment) {
                        ((Comment)obj).setDocument(null);
                    }
                    else if (obj instanceof ProcessingInstruction) {
                        ((ProcessingInstruction)obj).setDocument(null);
                    }
                }
                throw ex;
            }
        }

        // Remove parentage on the old content
        Iterator itr = oldContent.iterator();
        while (itr.hasNext()) {
            Object obj = itr.next();
            if (obj instanceof Element) {
                ((Element)obj).setDocument(null);
            }
            else if (obj instanceof Comment) {
                ((Comment)obj).setDocument(null);
            }
            else if (obj instanceof ProcessingInstruction) {
                ((ProcessingInstruction)obj).setDocument(null);
            }
        }

        return this;
    }

Jools, I suspect you're going to have a fun time making
FilterList.addAll() appear atomic.  :-)

-jh-



More information about the jdom-interest mailing list