org.apache.maven.model.jdom.util.JDomCleanupHelper Maven / Gradle / Ivy
package org.apache.maven.model.jdom.util;
/*
* 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.
*/
import org.codehaus.plexus.util.StringUtils;
import org.jdom2.Comment;
import org.jdom2.Content;
import org.jdom2.Element;
import org.jdom2.Text;
import org.jdom2.filter.ElementFilter;
import org.jdom2.util.IteratorIterable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.apache.maven.model.jdom.util.JDomCfg.POM_ELEMENT_PROFILE;
import static org.apache.maven.model.jdom.util.JDomCfg.POM_ELEMENT_PROFILES;
/**
* JDom cleanup methods for:
*
* - profiles
*
*/
public class JDomCleanupHelper {
/**
* Remove all empty profiles and profile tags.
* Empty is defined as
*
* - for tag profiles: there are no profile tags
* - for tag profile: Either there are only the tags activation and id present or all other tags are empty
*
* Thus, empty tags may contain comments which will be removed as well.
* Only those {@code profiles} tags are removed whose parent stated via {@code profilesParents}
*
* @param rootElement the root element.
* @param profilesParents list of allowed parents for {@code profiles} tags
*/
public static void cleanupEmptyProfiles(Element rootElement, List profilesParents) {
IteratorIterable filteredElements = rootElement.getDescendants(new ElementFilter(POM_ELEMENT_PROFILES));
List profiles = new ArrayList<>();
for (Element profilesElement : filteredElements) {
profiles.add(profilesElement);
}
for (Element profilesElement : profiles) {
if (!profilesParents.contains(profilesElement.getParentElement().getName())) {
continue;
}
removeElementWithEmptyChildren(profilesElement,
POM_ELEMENT_PROFILE,
Arrays.asList(JDomCfg.POM_ELEMENT_ID, JDomCfg.POM_ELEMENT_ACTIVATION));
if (!profilesElement.getDescendants(new ElementFilter(POM_ELEMENT_PROFILE)).hasNext()) {
JDomUtils.removeChildAndItsCommentFromContent(profilesElement.getParentElement(), profilesElement);
}
}
}
/**
* Remove empty element tags and their empty child tags.
* Empty tags may contain comments which will be removed as well.
*
* @param rootElement the root element.
* @param tag Tag to check.
*/
public static void cleanupEmptyElements(Element rootElement, String tag) {
IteratorIterable filteredElements = rootElement.getDescendants(new ElementFilter(tag));
List elementsToRemoveIfEmpty = new ArrayList<>();
for (Element element : filteredElements) {
elementsToRemoveIfEmpty.add(element);
}
for (Element elementToRemove : elementsToRemoveIfEmpty) {
List children = elementToRemove.getChildren();
if (children.size() == 0) {
JDomUtils.removeChildAndItsCommentFromContent(elementToRemove.getParentElement(), elementToRemove);
}
}
}
/**
* Squash multiple consecutive newlines into a single newline.
* Indentations are preserved.
*
* @param rootElement the root element.
*/
public static void squashMultilines(Element rootElement) {
// Compute groups of consecutive sibling content with only newlines (and whitespace)
List> newLineGroups = new ArrayList<>();
List currentGroup = new ArrayList<>();
for (Content descendant : rootElement.getDescendants()) {
if (JDomContentHelper.hasNewlines(descendant)) {
if (!currentGroup.isEmpty()) {
Element parent = currentGroup.get(0).getParent();
if (!parent.equals(descendant.getParentElement())) {
newLineGroups.add(currentGroup);
currentGroup = new ArrayList<>();
}
}
currentGroup.add((Text) descendant);
} else {
if (!currentGroup.isEmpty()) {
newLineGroups.add(currentGroup);
currentGroup = new ArrayList<>();
}
}
}
if (!currentGroup.isEmpty()) {
newLineGroups.add(currentGroup);
}
// For every group keep the last element (because it might be followed by whitespace which we want to keep for indentation)
// and set its text to two newlines (+ whitespace).
// Delete all other predecessor elements in the group.
for (List group : newLineGroups) {
int newlineCount = 0;
for (Text text : group) {
newlineCount += StringUtils.countMatches(text.getText(), "\n");
}
if (newlineCount > 2) {
Text last = group.get(group.size() - 1);
last.setText("\n\n" + last.getText().replaceAll("\n", ""));
group.remove(last);
for (Text text : group) {
text.getParentElement().removeContent(text);
text.detach();
}
}
}
}
/**
* Remove all empty children with tag name from parent element.
* The child is considered as empty if the child has either:
*
* - no children itself or
* - only children which are ignored or
* - empty children itself which are not ignored
*
*
* @param parent the parent element.
* @param tagName the tag name to search for.
* @param ignoreChildren List of children tag names which are ignored when searching for child elements.
*/
private static void removeElementWithEmptyChildren(Element parent, String tagName, List ignoreChildren) {
// Example:
// parent === 'profiles' Element
// tagName === 'profile'
// ignoreChildren === ['id', 'activation']
// filteredElements === direct children of parent which are of type Element and matching 'tagName'
IteratorIterable filteredElements = parent.getDescendants(new ElementFilter(tagName));
List contentToBeRemoved = new ArrayList<>();
for (Element filteredElement : filteredElements) {
boolean empty = true;
for (Element child : filteredElement.getChildren()) {
if (!ignoreChildren.contains(child.getName()) && child.getChildren().size() > 0) {
empty = false;
break;
}
}
if (empty) {
contentToBeRemoved.add(filteredElement);
contentToBeRemoved.addAll(getAttachedComments(filteredElement));
}
}
for (Content elementToBeRemoved : contentToBeRemoved) {
JDomUtils.removeChildAndItsCommentFromContent(parent, elementToBeRemoved);
}
}
/**
* Get all comments attached of the element.
*
* @param element the element to consider.
* @return List of {@link Comment}s
*/
private static List getAttachedComments(Element element) {
List contents = new ArrayList<>();
List siblings = getDirectContents(element.getParentElement());
int indexOfElement = siblings.indexOf(element);
for (int i = indexOfElement - 1; i >= 0; i--) {
if (JDomContentHelper.isNewline(siblings.get(i))) {
contents.add(siblings.get(i));
i--;
}
if (i >= 0 && siblings.get(i) instanceof Comment) {
contents.add(siblings.get(i));
continue;
}
if (i >= 0 && JDomContentHelper.isMultiNewLine(siblings.get(i))) {
contents.add(siblings.get(i));
}
break;
}
return contents;
}
/**
* Get all direct children of the element.
*
* @param element the element to consider.
* @return List of {@link Content}s
*/
private static List getDirectContents(Element element) {
// get all direct children
List children = new ArrayList<>();
Element parentElement = element.getParentElement();
if (null == parentElement) {
return children;
}
for (Content descendant : parentElement.getDescendants()) {
if (!descendant.getParent().equals(element)) {
// Only consider direct children
continue;
}
children.add(descendant);
}
return children;
}
}