org.ops4j.pax.construct.util.XppPom.java Source code

Java tutorial

Introduction

Here is the source code for org.ops4j.pax.construct.util.XppPom.java

Source

package org.ops4j.pax.construct.util;

/*
 * Copyright 2007 Stuart McCulloch, Alin Dreghiciu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import java.io.File;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;
import java.util.Properties;

import org.apache.maven.model.Dependency;
import org.apache.maven.model.Model;
import org.apache.maven.model.Repository;
import org.apache.maven.project.MavenProject;
import org.codehaus.plexus.util.IOUtil;
import org.codehaus.plexus.util.xml.Xpp3Dom;
import org.codehaus.plexus.util.xml.Xpp3DomBuilder;
import org.codehaus.plexus.util.xml.pull.XmlPullParser;
import org.codehaus.plexus.util.xml.pull.XmlPullParserException;
import org.codehaus.plexus.util.xml.pull.XmlSerializer;
import org.ops4j.pax.construct.util.PomUtils.ExistingElementException;
import org.ops4j.pax.construct.util.PomUtils.Pom;

/**
 * Support round-trip editing of Maven POMs, preserving comments and formatting as much as possible
 */
public class XppPom implements Pom {
    /**
     * Underlying XML file
     */
    private final File m_file;

    /**
     * Current XML document
     */
    private Xpp3Dom m_pom;

    /**
     * Read Maven project details from existing file
     * 
     * @param pomFile XML file containing Maven project model
     * @throws IOException
     */
    public XppPom(File pomFile) throws IOException {
        // protect against changes in working directory
        m_file = DirUtils.resolveFile(pomFile, true);

        try {
            XmlPullParser parser = RoundTripXml.createParser();
            Reader reader = StreamFactory.newXmlReader(m_file);
            parser.setInput(reader);

            m_pom = Xpp3DomBuilder.build(parser, false);

            IOUtil.close(reader);
        } catch (XmlPullParserException e) {
            throw new IOException(e.getLocalizedMessage());
        }
    }

    /**
     * Create blank Maven project module
     * 
     * @param pomFile XML file, may or may not exist
     * @param groupId project group id
     * @param artifactId project artifact id
     */
    public XppPom(File pomFile, String groupId, String artifactId) {
        // protect against changes in working directory
        m_file = DirUtils.resolveFile(pomFile, true);

        m_pom = new Xpp3Dom("project");

        // standard header cruft
        m_pom.setAttribute("xmlns", "http://maven.apache.org/POM/4.0.0");
        m_pom.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        m_pom.setAttribute("xsi:schemaLocation",
                "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd");

        Xpp3DomMap.putValue(m_pom, "modelVersion", "4.0.0");
        Xpp3DomMap.putValue(m_pom, "groupId", groupId);
        Xpp3DomMap.putValue(m_pom, "artifactId", artifactId);
        Xpp3DomMap.putValue(m_pom, "name", "");
        Xpp3DomMap.putValue(m_pom, "packaging", "pom");

        m_file.getParentFile().mkdirs();
    }

    /**
     * {@inheritDoc}
     */
    public String getId() {
        // follow the Maven standard...
        return getGroupId() + ':' + getArtifactId() + ':' + getPackaging() + ':' + getVersion();
    }

    /**
     * {@inheritDoc}
     */
    public String getParentId() {
        Xpp3Dom parent = m_pom.getChild("parent");
        if (null == parent) {
            return null;
        }

        Xpp3Dom groupId = parent.getChild("groupId");
        Xpp3Dom artifactId = parent.getChild("artifactId");
        Xpp3Dom version = parent.getChild("version");

        // assume that the parent has pom packaging (seems reasonable assumption)
        return groupId.getValue() + ':' + artifactId.getValue() + ":pom:" + version.getValue();
    }

    /**
     * {@inheritDoc}
     */
    public String getGroupId() {
        Xpp3Dom groupId = m_pom.getChild("groupId");
        Xpp3Dom parent = m_pom.getChild("parent");
        if (null == groupId && null != parent) {
            // inherit group from parent element
            groupId = parent.getChild("groupId");
        }
        if (null == groupId) {
            return null;
        }
        return groupId.getValue();
    }

    /**
     * {@inheritDoc}
     */
    public String getArtifactId() {
        return m_pom.getChild("artifactId").getValue();
    }

    /**
     * {@inheritDoc}
     */
    public String getVersion() {
        Xpp3Dom version = m_pom.getChild("version");
        Xpp3Dom parent = m_pom.getChild("parent");
        if (null == version && null != parent) {
            // inherit version from parent element
            version = parent.getChild("version");
        }
        if (null == version) {
            return null;
        }
        return version.getValue();
    }

    /**
     * {@inheritDoc}
     */
    public String getPackaging() {
        Xpp3Dom packaging = m_pom.getChild("packaging");
        if (null == packaging) {
            return "jar";
        }
        return packaging.getValue();
    }

    /**
     * {@inheritDoc}
     */
    public List getModuleNames() {
        List names = new ArrayList();

        Xpp3Dom modules = m_pom.getChild("modules");
        if (null != modules) {
            Xpp3Dom[] values = modules.getChildren("module");
            for (int i = 0; i < values.length; i++) {
                names.add(values[i].getValue());
            }
        }

        return names;
    }

    /**
     * {@inheritDoc}
     */
    public Pom getContainingPom() {
        try {
            File baseDir = getBasedir();

            // check it really does contain our current project
            Pom pom = PomUtils.readPom(baseDir.getParentFile());
            if (pom.getModuleNames().contains(baseDir.getName())) {
                return pom;
            }
            return null;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public Pom getModulePom(String name) {
        try {
            // check it really is a valid module
            if (getModuleNames().contains(name)) {
                return PomUtils.readPom(new File(m_file.getParentFile(), name));
            }
            return null;
        } catch (IOException e) {
            return null;
        }
    }

    /**
     * {@inheritDoc}
     */
    public File getFile() {
        return m_file;
    }

    /**
     * {@inheritDoc}
     */
    public File getBasedir() {
        return m_file.getParentFile();
    }

    /**
     * {@inheritDoc}
     */
    public boolean isBundleProject() {
        // local project, so can use very simple test based on packaging type
        return getPackaging().indexOf("bundle") >= 0;
    }

    /**
     * {@inheritDoc}
     */
    public String getBundleSymbolicName() {
        Xpp3Dom properties = m_pom.getChild("properties");
        if (null != properties) {
            Xpp3Dom symbolicName = properties.getChild("bundle.symbolicName");
            if (null != symbolicName) {
                return symbolicName.getValue();
            }
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    public void setParent(Pom pom, String relativePath, boolean overwrite) throws ExistingElementException {
        MavenProject project = new MavenProject(new Model());

        project.setGroupId(pom.getGroupId());
        project.setArtifactId(pom.getArtifactId());
        project.setVersion(pom.getVersion());

        setParent(project, relativePath, overwrite);
    }

    /**
     * {@inheritDoc}
     */
    public void setParent(MavenProject project, String relativePath, boolean overwrite)
            throws ExistingElementException {
        if (m_pom.getChild("parent") != null && !overwrite) {
            throw new ExistingElementException("parent");
        }

        Xpp3DomMap parent = new Xpp3DomMap("parent");
        parent.putValue("relativePath", relativePath);
        parent.putValue("groupId", project.getGroupId());
        parent.putValue("artifactId", project.getArtifactId());
        parent.putValue("version", project.getVersion());

        Xpp3Dom newPom = new Xpp3Dom("project");
        newPom.addChild(parent);

        m_pom = Xpp3DomHelper.mergeXpp3Dom(newPom, m_pom);
    }

    /**
     * {@inheritDoc}
     */
    public void setGroupId(String newGroupId) {
        Xpp3Dom groupId = m_pom.getChild("groupId");
        if (null == groupId) {
            groupId = new Xpp3Dom("groupId");
            m_pom.addChild(groupId);
        }
        groupId.setValue(newGroupId);
    }

    /**
     * {@inheritDoc}
     */
    public void setVersion(String newVersion) {
        Xpp3Dom version = m_pom.getChild("version");
        if (null == version) {
            version = new Xpp3Dom("version");
            m_pom.addChild(version);
        }
        version.setValue(newVersion);
    }

    /**
     * {@inheritDoc}
     */
    public void addRepository(Repository repository, boolean snapshots, boolean releases, boolean overwrite,
            boolean pluginRepo) throws ExistingElementException {
        final String listName;
        final String elemName;

        if (pluginRepo) {
            listName = "pluginRepositories";
            elemName = "pluginRepository";
        } else {
            listName = "repositories";
            elemName = "repository";
        }

        String id = repository.getId();
        String url = repository.getUrl();

        String xpath = listName + '/' + elemName + "[id='" + id + "' or url='" + url + "']";

        // clear old elements when overwriting
        if (findChildren(xpath, overwrite) && !overwrite) {
            throw new ExistingElementException(elemName);
        }

        Xpp3DomMap repo = new Xpp3DomMap(elemName);
        repo.putValue("id", id);
        repo.putValue("url", url);

        if (!snapshots) {
            Xpp3DomMap snapshotFlag = new Xpp3DomMap("snapshots");
            snapshotFlag.putValue("enabled", "false");
            repo.addChild(snapshotFlag);
        }

        if (!releases) {
            Xpp3DomMap releaseFlag = new Xpp3DomMap("releases");
            releaseFlag.putValue("enabled", "false");
            repo.addChild(releaseFlag);
        }

        Xpp3Dom list = new Xpp3DomList(listName);
        list.addChild(repo);

        Xpp3Dom newPom = new Xpp3Dom("project");
        newPom.addChild(list);

        Xpp3DomHelper.mergeXpp3Dom(m_pom, newPom);
    }

    /**
     * {@inheritDoc}
     */
    public void addModule(String module, boolean overwrite) throws ExistingElementException {
        String xpath = "modules/module[.='" + module + "']";

        // clear old elements when overwriting
        if (findChildren(xpath, overwrite) && !overwrite) {
            throw new ExistingElementException("module");
        }

        Xpp3Dom mod = new Xpp3Dom("module");
        mod.setValue(module);

        Xpp3Dom list = new Xpp3DomList("modules");
        list.addChild(mod);

        Xpp3Dom newPom = new Xpp3Dom("project");
        newPom.addChild(list);

        Xpp3DomHelper.mergeXpp3Dom(m_pom, newPom);
    }

    /**
     * {@inheritDoc}
     */
    public boolean removeModule(String module) {
        String xpath = "modules/module[.='" + module + "']";

        return findChildren(xpath, true);
    }

    /**
     * {@inheritDoc}
     */
    public void addDependency(Dependency dependency, boolean overwrite) throws ExistingElementException {
        String groupId = dependency.getGroupId();
        String artifactId = dependency.getArtifactId();

        String xpath = "dependencies/dependency[groupId='" + groupId + "' and artifactId='" + artifactId + "']";

        // clear old elements when overwriting
        if (findChildren(xpath, overwrite) && !overwrite) {
            throw new ExistingElementException("dependency");
        }

        Xpp3DomMap dep = new Xpp3DomMap("dependency");
        dep.putValue("groupId", groupId);
        dep.putValue("artifactId", artifactId);
        dep.putValue("version", dependency.getVersion());
        dep.putValue("scope", dependency.getScope());

        String type = dependency.getType();
        if (!"jar".equals(type)) {
            // jar is the default type
            dep.putValue("type", type);
        }

        if (dependency.isOptional()) {
            dep.putValue("optional", "true");
        }

        Xpp3Dom list = new Xpp3DomList("dependencies");
        list.addChild(dep);

        Xpp3Dom newPom = new Xpp3Dom("project");
        newPom.addChild(list);

        Xpp3DomHelper.mergeXpp3Dom(m_pom, newPom);
    }

    /**
     * {@inheritDoc}
     */
    public boolean updateDependencyGroup(Dependency dependency, String newGroupId) {
        String groupId = dependency.getGroupId();
        String artifactId = dependency.getArtifactId();

        boolean updated = false;

        String xpath1 = "dependencies/dependency[groupId='" + groupId + "' and artifactId='" + artifactId + "']";
        updated = updateGroupId(xpath1, newGroupId) || updated;

        String xpath2 = "dependencyManagement/" + xpath1;
        updated = updateGroupId(xpath2, newGroupId) || updated;

        return updated;
    }

    /**
     * @param xpath simple XPATH query
     * @param newGroupId new group id
     * @return true if any elements were updated, otherwise false
     */
    private boolean updateGroupId(String xpath, String newGroupId) {
        XppPathQuery pathQuery = new XppPathQuery(xpath);
        Xpp3Dom parent = pathQuery.queryParent(m_pom);
        if (null == parent) {
            return false;
        }

        int[] children = pathQuery.queryChildren(parent);
        for (int i = 0; i < children.length; i++) {
            Xpp3Dom group = parent.getChild(children[i]).getChild("groupId");
            if (null != group) {
                group.setValue(newGroupId);
            }
        }
        return children.length > 0;
    }

    /**
     * @param xpath simple XPATH query
     * @param newVersion new version
     * @return true if any elements were updated, otherwise false
     */
    private boolean updateVersion(String xpath, String newVersion) {
        XppPathQuery pathQuery = new XppPathQuery(xpath);
        Xpp3Dom parent = pathQuery.queryParent(m_pom);
        if (null == parent) {
            return false;
        }

        int[] children = pathQuery.queryChildren(parent);
        for (int i = 0; i < children.length; i++) {
            Xpp3Dom fragment = parent.getChild(children[i]);
            Xpp3Dom version = fragment.getChild("version");
            if (null == version) {
                version = new Xpp3Dom("version");
                Xpp3DomList.addChild(fragment, 2, version);
            }
            version.setValue(newVersion);
        }
        return children.length > 0;
    }

    /**
     * {@inheritDoc}
     */
    public boolean removeDependency(Dependency dependency) {
        String groupId = dependency.getGroupId();
        String artifactId = dependency.getArtifactId();

        boolean updated = false;

        String xpath1 = "dependencies/dependency[groupId='" + groupId + "' and artifactId='" + artifactId + "']";
        updated = findChildren(xpath1, true) || updated;

        String xpath2 = "dependencyManagement/" + xpath1;
        updated = findChildren(xpath2, true) || updated;

        return updated;
    }

    /**
     * {@inheritDoc}
     */
    public void addExclusion(String groupId, String artifactId, boolean overwrite) throws ExistingElementException {
        Xpp3Dom dependencies = m_pom.getChild("dependencies");
        if (null == dependencies || dependencies.getChildCount() <= 0) {
            return; // can't exclude what isn't there!
        }

        String exclusionPath = "dependencies/dependency/exclusions/exclusion";
        String xpath = exclusionPath + "[groupId='" + groupId + "' and artifactId='" + artifactId + "']";

        // clear old elements when overwriting
        if (findChildren(xpath, overwrite) && !overwrite) {
            throw new ExistingElementException("exclusion");
        }

        Xpp3DomMap exclude = new Xpp3DomMap("exclusion");
        exclude.putValue("groupId", groupId);
        exclude.putValue("artifactId", artifactId);

        Xpp3Dom list = new Xpp3DomList("exclusions");
        list.addChild(exclude);

        Xpp3Dom newDependency = new Xpp3Dom("dependency");
        newDependency.addChild(list);

        // add exclusion to top-most dependency
        Xpp3DomHelper.mergeXpp3Dom(dependencies.getChild(0), newDependency);
    }

    /**
     * {@inheritDoc}
     */
    public boolean removeExclusion(String groupId, String artifactId) {
        boolean updated = false;

        String exclusionPath = "dependencies/dependency/exclusions/exclusion";

        String xpath1 = exclusionPath + "[groupId='" + groupId + "' and artifactId='" + artifactId + "']";
        updated = findChildren(xpath1, true) || updated;

        String xpath2 = "dependencyManagement/" + xpath1;
        updated = findChildren(xpath2, true) || updated;

        return updated;
    }

    /**
     * {@inheritDoc}
     */
    public Properties getProperties() {
        Properties properties = new Properties();

        Xpp3Dom map = m_pom.getChild("properties");
        if (null != map) {
            Xpp3Dom[] entries = map.getChildren();
            for (int i = 0; i < entries.length; i++) {
                properties.setProperty(entries[i].getName(), entries[i].getValue());
            }
        }

        return properties;
    }

    /**
     * {@inheritDoc}
     */
    public void setProperty(String key, String value) {
        Xpp3Dom map = m_pom.getChild("properties");
        if (null == map) {
            map = new Xpp3Dom("properties");
            m_pom.addChild(map);
        }

        Xpp3Dom entry = new Xpp3Dom(key);
        entry.setValue(value);

        map.addChild(entry);
    }

    /**
     * {@inheritDoc}
     */
    public boolean updatePluginVersion(String groupId, String artifactId, String newVersion) {
        boolean updated = false;

        String plugins = "plugins/plugin[groupId='" + groupId + "' and artifactId='" + artifactId + "']";

        updated = updateVersion("build/" + plugins, newVersion) || updated;
        updated = updateVersion("build/pluginManagement/" + plugins, newVersion) || updated;

        return updated;
    }

    /**
     * {@inheritDoc}
     */
    public void mergeSection(Pom pom, String fromSection, String toSection, boolean append) {
        if (!(pom instanceof XppPom)) {
            throw new IllegalArgumentException("Unable to merge POM type " + pom.getClass());
        }

        mergeSection(((XppPom) pom).m_pom, fromSection, toSection, append);
    }

    /**
     * Merge a section of XML from another XML fragment
     * 
     * @param from another XML fragment
     * @param fromSection path to XML section to merge from
     * @param toSection path to XML section to merge into
     * @param append when true, append instead of merging
     */
    private void mergeSection(Xpp3Dom from, String fromSection, String toSection, boolean append) {
        String[] fromPath = fromSection.split("/");

        // find source section
        Xpp3Dom source = from;
        for (int i = 0; i < fromPath.length; i++) {
            source = source.getChild(fromPath[i]);
            if (null == source) {
                return;
            }
        }

        if (append) {
            Xpp3DomList.makeIntoList(source);
        }

        Xpp3Dom project = new Xpp3Dom("project");

        // create skeleton template
        Xpp3Dom skeleton = project;
        if (toSection != null) {
            String[] toPath = toSection.split("/");
            for (int i = 0; i < toPath.length; i++) {
                Xpp3Dom temp = new Xpp3Dom(toPath[i]);
                skeleton.addChild(temp);
                skeleton = temp;
            }
        }

        // add source to template
        skeleton.addChild(source);

        m_pom = Xpp3DomHelper.mergeXpp3Dom(m_pom, project);
    }

    /**
     * {@inheritDoc}
     */
    public void overlayDetails(Pom pom) {
        if (!(pom instanceof XppPom)) {
            throw new IllegalArgumentException("Unable to overlay POM type " + pom.getClass());
        }

        Xpp3Dom overlay = ((XppPom) pom).m_pom;
        Xpp3Dom project = new Xpp3Dom("project");

        // record before we drop any elements
        List newModules = pom.getModuleNames();

        // avoid corruption of key elements
        removeProtectedElements(overlay);

        Xpp3Dom[] sections = m_pom.getChildren();
        for (int i = 0; i < sections.length; i++) {
            // provide basic XML framework underneath the overlay
            if (null == overlay.getChild(sections[i].getName())) {
                project.addChild(sections[i]);
            }
        }

        Xpp3Dom originalPom = new Xpp3Dom(m_pom);
        m_pom = Xpp3DomHelper.mergeXpp3Dom(project, overlay);

        // we want to keep these plugins exactly as they were in the original Pax-Construct v2 POMs
        String plugins = "plugins/plugin[artifactId='maven-bundle-plugin' or artifactId='maven-pax-plugin']";

        findChildren("build/" + plugins, true);
        mergeSection(originalPom, "build/plugins", "build", true);

        findChildren("build/pluginManagement/" + plugins, true);
        mergeSection(originalPom, "build/pluginManagement/plugins", "build/pluginManagement", true);

        // merge properties - customized values take precedence
        mergeSection(originalPom, "properties", null, false);

        // new modules go below existing infrastructure entries
        for (Iterator i = newModules.iterator(); i.hasNext();) {
            String module = (String) i.next();
            if (new File(getBasedir(), module).exists()) {
                addModule(module, true);
            }
        }
    }

    /**
     * @param fragment existing XML fragment
     */
    private static void removeProtectedElements(Xpp3Dom fragment) {
        List protectedElements = Arrays.asList(new String[] { "modelVersion", "parent", "artifactId", "groupId",
                "version", "packaging", "modules" });

        Xpp3Dom[] elements = fragment.getChildren();
        for (int i = elements.length - 1; i >= 0; i--) {
            // we don't want these protected elements customized
            if (protectedElements.contains(elements[i].getName())) {
                fragment.removeChild(i);
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void write() throws IOException {
        String encoding = StreamFactory.getXmlEncoding(m_file);
        Writer writer = StreamFactory.newXmlWriter(m_file);

        XmlSerializer serializer = RoundTripXml.createSerializer();

        serializer.setOutput(writer);
        serializer.startDocument(encoding, null);
        m_pom.writeToSerializer(null, serializer);
        serializer.endDocument();

        IOUtil.close(writer);
    }

    /**
     * Local utility class to help construct a "map" style XML fragment
     */
    private static class Xpp3DomMap extends Xpp3Dom {
        /**
         * Create a new map fragment
         * 
         * @param elementName name of the map element
         */
        public Xpp3DomMap(String elementName) {
            super(elementName);
        }

        /**
         * Add a mapping to the map
         * 
         * @param elementName element name
         * @param elementValue element value
         */
        public void putValue(String elementName, String elementValue) {
            putValue(this, elementName, elementValue);
        }

        /**
         * @param map map fragment
         * @param name element name
         * @param value element value
         */
        static void putValue(Xpp3Dom map, String name, String value) {
            if (null != value) {
                // only store non-null mapppings
                Xpp3Dom child = new Xpp3Dom(name);
                child.setValue(value);
                map.addChild(child);
            }
        }
    }

    /**
     * Private utility class to help construct a "list" style XML fragment
     */
    private static class Xpp3DomList extends Xpp3Dom {
        /**
         * Create a new list fragment
         * 
         * @param elementName name of the list element
         */
        public Xpp3DomList(String elementName) {
            super(elementName);
            makeIntoList(this);
        }

        /**
         * Switch an existing XML fragment to use the "list" style
         * 
         * @param fragment existing XML fragment
         */
        public static void makeIntoList(Xpp3Dom fragment) {
            // list fragments must append their children when merging with other XML fragments
            fragment.setAttribute(CHILDREN_COMBINATION_MODE_ATTRIBUTE, CHILDREN_COMBINATION_APPEND);
        }

        /**
         * Support addition of XML nodes at specific positions
         * 
         * @param parent parent node
         * @param index index at which the child is to be inserted
         * @param child child node
         */
        public static void addChild(Xpp3Dom parent, int index, Xpp3Dom child) {
            int count = parent.getChildCount();

            // basic API adds to end
            parent.addChild(child);

            for (int i = index; i < count; i++) {
                // shuffle round like a circular buffer
                Xpp3Dom temp = parent.getChild(index);
                parent.removeChild(index);
                parent.addChild(temp);
            }
        }
    }

    /**
     * Local utility method to check for child elements based on a simple XPATH query
     * 
     * @param xpath simple XPATH query
     * @param clear remove matching elements
     * @return true if any child elements matched, otherwise false
     */
    private boolean findChildren(String xpath, boolean clear) {
        XppPathQuery pathQuery = new XppPathQuery(xpath);
        Xpp3Dom parent = pathQuery.queryParent(m_pom);

        if (null == parent) {
            return false;
        }

        int[] children = pathQuery.queryChildren(parent);

        if (clear) {
            // sort ascending order
            Arrays.sort(children);

            // now remove in reverse in case array shrinks
            for (int i = children.length - 1; i >= 0; i--) {
                parent.removeChild(children[i]);
            }
        }

        return children.length > 0;
    }

    /**
     * {@inheritDoc}
     */
    public boolean equals(Object obj) {
        if (obj instanceof XppPom) {
            return getId().equals(((XppPom) obj).getId());
        }
        return false;
    }

    /**
     * {@inheritDoc}
     */
    public int hashCode() {
        return getId().hashCode();
    }

    /**
     * {@inheritDoc}
     */
    public String toString() {
        return getId();
    }
}