Java tutorial
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(); } }