/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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. */ package org.codehaus.mojo.pom; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Source; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerConfigurationException; import javax.xml.transform.TransformerException; import javax.xml.transform.TransformerFactory; import javax.xml.transform.TransformerFactoryConfigurationError; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import org.apache.maven.plugin.MojoExecutionException; import org.apache.maven.plugin.MojoFailureException; import org.apache.maven.plugin.logging.Log; import org.apache.maven.project.MavenProject; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.xml.sax.SAXException; /** * @author joerg */ public class ProjectContainer { /** The property-prefix {@value} . */ public static final String PROPERTY_PREFIX_POM = "pom."; /** The property-prefix {@value} . */ public static final String PROPERTY_PREFIX_PROJECT = "project."; /** The prefix of a variable expression */ public static final String PROPERTY_PREFIX = "${"; /** The suffix of a variable expression */ public static final String PROPERTY_SUFFIX = "}"; public static final String XML_TAG_GROUPID = "groupId"; public static final String XML_TAG_ARTIFACTID = "artifactId"; public static final String XML_TAG_VERSION = "version"; public static final String XML_TAG_PACKAGING = "packaging"; public static final String XML_TAG_TYPE = "type"; public static final String XML_TAG_PARENT = "parent"; public static final String XML_TAG_DEPENDENCIES = "dependencies"; public static final String XML_TAG_DEPENDENCY_MANAGEMENT = "dependencyManagement"; public static final String XML_TAG_DEPENDENCY = "dependency"; public static final String XML_TAG_PROPERTIES = "properties"; public static final String XML_TAG_SCOPE = "scope"; public static final String XML_TAG_CLASSIFIER = "classifier"; private static final DocumentBuilder documentBuilder; private static final Transformer transformer; private static final String DEFAULT_SCOPE = "compile"; private static final String DEFAULT_TYPE = "jar"; static { try { documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); } catch (ParserConfigurationException e) { throw new IllegalStateException("JAXP missconfigured!", e); } try { TransformerFactory transformerFactory = TransformerFactory.newInstance(); transformer = transformerFactory.newTransformer(); transformer.setOutputProperty(OutputKeys.METHOD, "xml"); transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); } catch (TransformerConfigurationException e) { throw new IllegalStateException("JAXP missconfigured!", e); } catch (TransformerFactoryConfigurationError e) { throw new IllegalStateException("JAXP missconfigured!", e); } } private final Log logger; private final ProjectContainer parent; private final MavenProject project; private final ProjectId id; private Document document; private List<Element> dependenciesList; private List<Element> dependencyManagementList; private final Map<String, String> propertyUpdateMap; private boolean modified; /** * The constructor. */ public ProjectContainer(MavenProject project, ProjectContainer parent, Log logger) { super(); this.logger = logger; this.id = new ProjectId(project); this.parent = parent; this.project = project; this.modified = false; this.propertyUpdateMap = new HashMap<String, String>(); } public void updatePropertyValue(String propertyName, String newValue) throws MojoFailureException, MojoExecutionException { String replacedValue = this.propertyUpdateMap.get(propertyName); if ((replacedValue != null) && !(replacedValue.equals(newValue))) { throw new MojoFailureException("Value of property '" + propertyName + "' already set to '" + replacedValue + "'. Failed to set to '" + newValue + "'!"); } String oldValue = this.project.getProperties().getProperty(propertyName); if (oldValue == null) { throw new MojoFailureException("Property '" + propertyName + "' is NOT declared in '" + this.id + " - can NOT set to '" + newValue + "'!"); } Element elementProject = getPomDocument().getDocumentElement(); Element elementProperty = DomUtilities.getChildElement(elementProject, XML_TAG_PROPERTIES, propertyName); if (elementProperty == null) { if (this.parent == null) { throw new MojoExecutionException("Internal error - missing parent of '" + this.id + "'!"); } else { this.parent.updatePropertyValue(propertyName, newValue); } } else { String currentValue = elementProperty.getTextContent(); getLogger().debug( "Updating property '" + propertyName + "' from '" + currentValue + "' to '" + newValue + "' in project '" + this.id + "'..."); elementProperty.setTextContent(newValue); setModified(); this.propertyUpdateMap.put(propertyName, newValue); } } /** * @return the logger */ public Log getLogger() { return this.logger; } /** * @return the id */ public ProjectId getId() { return this.id; } /** * @return the parent */ public ProjectContainer getParent() { return this.parent; } /** * @return the project */ public MavenProject getProject() { return this.project; } private String getValue(Element dependencyElement, boolean resolveProperties, String tagname) throws MojoExecutionException { String value = DomUtilities.getChildElementValue(dependencyElement, tagname); if (resolveProperties && (value != null) && value.startsWith(ProjectContainer.PROPERTY_PREFIX) && value.endsWith(ProjectContainer.PROPERTY_SUFFIX)) { String variableName = value.substring(ProjectContainer.PROPERTY_PREFIX.length(), value .length() - ProjectContainer.PROPERTY_SUFFIX.length()); String internalProperty = null; if (variableName.startsWith(PROPERTY_PREFIX_PROJECT)) { internalProperty = variableName.substring(PROPERTY_PREFIX_PROJECT.length()); } else if (variableName.startsWith(PROPERTY_PREFIX_POM)) { internalProperty = variableName.substring(PROPERTY_PREFIX_POM.length()); } String resolvedValue = null; if (internalProperty == null) { resolvedValue = this.project.getProperties().getProperty(variableName); } else { if (internalProperty.equals(XML_TAG_GROUPID)) { resolvedValue = this.project.getGroupId(); } else if (internalProperty.equals(XML_TAG_ARTIFACTID)) { resolvedValue = this.project.getArtifactId(); } else if (internalProperty.equals(XML_TAG_VERSION)) { resolvedValue = this.project.getVersion(); } else { getLogger().warn( "Could NOT resolve internal property '" + internalProperty + "' - ignoring ..."); } } if (resolvedValue == null) { getLogger().warn("Could NOT resolve property '" + variableName + "' - ignoring ..."); } else { value = resolvedValue; } } return value; } public DependencyInfo createDependencyInfo(Element dependencyElement, boolean resolveProperties) throws MojoExecutionException { String groupId = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_GROUPID); String artifactId = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_ARTIFACTID); String version = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_VERSION); String type = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_TYPE); if (type == null) { type = DEFAULT_TYPE; } String scope = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_SCOPE); if (scope == null) { scope = DEFAULT_SCOPE; } String classifier = getValue(dependencyElement, resolveProperties, ProjectContainer.XML_TAG_CLASSIFIER); return new DependencyInfo(groupId, artifactId, version, type, scope, classifier); } /** * @return the document */ public Document getPomDocument() throws MojoExecutionException { if (this.document == null) { try { this.document = documentBuilder.parse(this.project.getFile()); } catch (SAXException e) { throw new MojoExecutionException( "Illegal POM: " + this.project.getFile().getAbsolutePath(), e); } catch (IOException e) { throw new MojoExecutionException("Error reading POM: " + this.project.getFile().getAbsolutePath(), e); } } return this.document; } /** * @return the dependenciesList */ public List<Element> getPomDependenciesList() throws MojoExecutionException { if (this.dependenciesList == null) { this.dependenciesList = DomUtilities.getChildElements(getPomDocument().getDocumentElement(), XML_TAG_DEPENDENCIES, XML_TAG_DEPENDENCY); if (this.dependenciesList == null) { getLogger().debug("No dependencies found!"); this.dependenciesList = new ArrayList<Element>(); } else { getLogger().debug("Number of dependencies found: " + this.dependenciesList.size()); } } return this.dependenciesList; } /** * @return the dependencyManagementList */ public List<Element> getPomDependencyManagementList() throws MojoExecutionException { if (this.dependencyManagementList == null) { this.dependencyManagementList = DomUtilities.getChildElements(getPomDocument() .getDocumentElement(), XML_TAG_DEPENDENCY_MANAGEMENT, XML_TAG_DEPENDENCY); if (this.dependencyManagementList == null) { getLogger().debug("No dependency-management found!"); this.dependencyManagementList = new ArrayList<Element>(); } else { getLogger().debug( "Number of dependencies in dependency-management found: " + this.dependenciesList.size()); } } return this.dependencyManagementList; } public void setModified() { this.modified = true; } public void save(String encoding, boolean overwrite) throws MojoExecutionException { if ((this.document != null) && this.modified) { File targetFile; if (overwrite) { targetFile = this.project.getFile(); } else { targetFile = new File(this.project.getBuild().getDirectory(), "pom-refactored.xml"); } getLogger().info("Writing " + targetFile.getAbsolutePath() + " ..."); try { Source source = new DOMSource(this.document); if (!targetFile.exists()) { targetFile.getParentFile().mkdirs(); targetFile.createNewFile(); } OutputStream outStream = new FileOutputStream(targetFile); Writer writer = new OutputStreamWriter(outStream, encoding); XalanHackWriter hackWriter = new XalanHackWriter(writer); PrintWriter pw = new PrintWriter(hackWriter); try { // Xalan-J sucks! // http://www.nabble.com/Output-a-new-line-after-the-XML-declaration-using-indent%3D%22yes%22-td15040090.html pw.println("<?xml version='1.0' encoding='" + encoding + "'?>"); Result result = new StreamResult(pw); transformer.transform(source, result); pw.println(); } catch (TransformerException e) { throw new MojoExecutionException("Error writing POM: " + targetFile.getAbsolutePath(), e); } finally { pw.close(); } } catch (IOException e) { throw new MojoExecutionException("Error writing POM: " + targetFile.getAbsolutePath(), e); } } } private static class XalanHackWriter extends Writer { private final Writer delegate; private boolean done; private static final String COMMENT_OPEN = "<!--"; private static final String COMMENT_CLOSE = "--><"; private int commentOpenCount; private int commentCloseCount; /** * The constructor. * * @param delegate */ public XalanHackWriter(Writer delegate) { super(); this.delegate = delegate; this.done = false; } /** * {@inheritDoc} */ @Override public void close() throws IOException { this.delegate.close(); } /** * {@inheritDoc} */ @Override public void flush() throws IOException { this.delegate.flush(); } /** * {@inheritDoc} */ @Override public void write(char[] cbuf, int off, int len) throws IOException { if (!this.done) { int index = off; if (this.commentOpenCount < COMMENT_OPEN.length()) { for (; index < len; index++) { if (cbuf[index] == COMMENT_OPEN.charAt(this.commentOpenCount)) { this.commentOpenCount++; if (this.commentOpenCount == COMMENT_OPEN.length()) { index++; break; } } else { if ((this.commentOpenCount == 1) && (cbuf[index] != '?')) { // root-tag passed... this.done = true; break; } this.commentOpenCount = 0; } } } if (this.commentCloseCount < COMMENT_CLOSE.length()) { for (; index < len; index++) { if (cbuf[index] == COMMENT_CLOSE.charAt(this.commentCloseCount)) { this.commentCloseCount++; if (this.commentCloseCount == COMMENT_CLOSE.length()) { // commentClosed and root-tag in same line (Xalan-J bug) int firstLength = index - off; if (firstLength > 0) { this.delegate.write(cbuf, off, firstLength); } this.delegate.write(System.getProperty("line.separator")); this.delegate.write(cbuf, index, len - firstLength); return; } } else { this.commentCloseCount = 0; } } } } this.delegate.write(cbuf, off, len); } } }