diff --git a/Java-base/maven-shared-utils/Dockerfile b/Java-base/maven-shared-utils/Dockerfile
new file mode 100644
index 000000000..e208c4890
--- /dev/null
+++ b/Java-base/maven-shared-utils/Dockerfile
@@ -0,0 +1,28 @@
+FROM ubuntu:22.04
+
+RUN export DEBIAN_FRONTEND=noninteractive \
+ && apt-get update \
+ && apt-get install -y software-properties-common \
+ && add-apt-repository ppa:deadsnakes/ppa \
+ && apt-get update \
+ && apt-get install -y \
+ build-essential \
+ git \
+ vim \
+ jq \
+ && apt-get autoremove -y && apt-get clean -y && rm -rf /var/lib/apt/list/*
+
+RUN apt-get -y install sudo \
+ openjdk-8-jdk \
+ maven
+
+RUN bash -c "echo 2 | update-alternatives --config java"
+
+COPY src /workspace
+WORKDIR /workspace
+
+RUN mvn install -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100 -DskipTests=true -DskipITs=true -Dtest=None -DfailIfNoTests=false
+
+RUN mvn test -V -B -Denforcer.skip=true -Dcheckstyle.skip=true -Dcobertura.skip=true -Drat.skip=true -Dlicense.skip=true -Dfindbugs.skip=true -Dgpg.skip=true -Dskip.npm=true -Dskip.gulp=true -Dskip.bower=true -Drat.numUnapprovedLicenses=100
+
+ENV TZ=Asia/Seoul
diff --git a/Java-base/maven-shared-utils/src/Jenkinsfile b/Java-base/maven-shared-utils/src/Jenkinsfile
new file mode 100644
index 000000000..09ac70f12
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/Jenkinsfile
@@ -0,0 +1,20 @@
+/**
+ * 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.
+ */
+
+asfMavenTlpStdBuild()
diff --git a/Java-base/maven-shared-utils/src/README.md b/Java-base/maven-shared-utils/src/README.md
new file mode 100644
index 000000000..6ddc421da
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/README.md
@@ -0,0 +1,99 @@
+
+Contributing to [Apache Maven Shared Utils](https://maven.apache.org/shared/maven-shared-utils/)
+======================
+
+[![ASF Jira](https://img.shields.io/endpoint?url=https%3A%2F%2Fmaven.apache.org%2Fbadges%2Fasf_jira-MSHARED.json)][jira]
+[![Apache License, Version 2.0, January 2004](https://img.shields.io/github/license/apache/maven.svg?label=License)][license]
+[![Maven Central](https://img.shields.io/maven-central/v/org.apache.maven.shared/maven-shared-utils.svg?label=Maven%20Central)](https://search.maven.org/artifact/org.apache.maven.shared/maven-shared-utils)
+[![Jenkins Status](https://img.shields.io/jenkins/s/https/builds.apache.org/job/maven-box/job/maven-shared-utils/job/master.svg)][build]
+[![Jenkins tests](https://img.shields.io/jenkins/t/https/builds.apache.org/job/maven-box/job/maven-shared-utils/job/master.svg)][test-results]
+
+
+You have found a bug or you have an idea for a cool new feature? Contributing
+code is a great way to give something back to the open source community. Before
+you dig right into the code, there are a few guidelines that we need
+contributors to follow so that we can have a chance of keeping on top of
+things.
+
+Getting Started
+---------------
+
++ Make sure you have a [JIRA account](https://issues.apache.org/jira/).
++ Make sure you have a [GitHub account](https://github.com/signup/free).
++ If you're planning to implement a new feature, it makes sense to discuss your changes
+ on the [dev list][ml-list] first.
+ This way you can make sure you're not wasting your time on something that isn't
+ considered to be in Apache Maven's scope.
++ Submit a ticket for your issue, assuming one does not already exist.
+ + Clearly describe the issue, including steps to reproduce when it is a bug.
+ + Make sure you fill in the earliest version that you know has the issue.
++ Fork the repository on GitHub.
+
+Making and Submitting Changes
+--------------
+
+We accept Pull Requests via GitHub. The [developer mailing list][ml-list] is the
+main channel of communication for contributors.
+There are some guidelines which will make applying PRs easier for us:
++ Create a topic branch from where you want to base your work (this is usually the master branch).
+ Push your changes to a topic branch in your fork of the repository.
++ Make commits of logical units.
++ Respect the original code style: by using the same [codestyle][code-style],
+ patches should only highlight the actual difference, not being disturbed by any formatting issues:
+ + Only use spaces for indentation.
+ + Create minimal diffs - disable on save actions like reformat source code or organize imports.
+ If you feel the source code should be reformatted, create a separate PR for this change.
+ + Check for unnecessary whitespace with `git diff --check` before committing.
++ Make sure your commit messages are in the proper format. Your commit message should contain the key of the JIRA issue.
+```
+[MSHARED-XXX] - Subject of the JIRA Ticket
+ Optional supplemental description.
+```
++ Make sure you have added the necessary tests (JUnit/IT) for your changes.
++ Run all the tests with `mvn -Prun-its verify` to assure nothing else was accidentally broken.
++ Submit a pull request to the repository in the Apache organization.
++ Update your JIRA ticket and include a link to the pull request in the ticket.
+
+If you plan to contribute on a regular basis, please consider filing a [contributor license agreement][cla].
+
+Making Trivial Changes
+----------------------
+
+For changes of a trivial nature to comments and documentation, it is not always
+necessary to create a new ticket in JIRA. In this case, it is appropriate to
+start the first line of a commit with '(doc)' instead of a ticket number.
+
+Additional Resources
+--------------------
+
++ [Contributing patches](https://maven.apache.org/guides/development/guide-maven-development.html#Creating_and_submitting_a_patch)
++ [Apache Maven Shared Components project page][jira]
++ [Contributor License Agreement][cla]
++ [General GitHub documentation](https://help.github.com/)
++ [GitHub pull request documentation](https://help.github.com/send-pull-requests/)
++ [Apache Maven Twitter Account](https://twitter.com/ASFMavenProject)
++ #Maven IRC channel on freenode.org
+
+[jira]: https://issues.apache.org/jira/projects/MSHARED/
+[license]: https://www.apache.org/licenses/LICENSE-2.0
+[ml-list]: https://maven.apache.org/mailing-lists.html
+[code-style]: https://maven.apache.org/developers/conventions/code.html
+[cla]: https://www.apache.org/licenses/#clas
+[maven-wiki]: https://cwiki.apache.org/confluence/display/MAVEN/Index
+[test-results]: https://builds.apache.org/job/maven-box/job/maven-shared-utils/job/master/lastCompletedBuild/testReport/
+[build]: https://builds.apache.org/job/maven-box/job/maven-shared-utils/job/master/
diff --git a/Java-base/maven-shared-utils/src/deploySite.sh b/Java-base/maven-shared-utils/src/deploySite.sh
new file mode 100755
index 000000000..f6c265d75
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/deploySite.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+#
+# 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.
+#
+
+mvn -Preporting site site:stage $@
+mvn scm-publish:publish-scm $@
diff --git a/Java-base/maven-shared-utils/src/findbugs-exclude.xml b/Java-base/maven-shared-utils/src/findbugs-exclude.xml
new file mode 100755
index 000000000..0fc3f2611
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/findbugs-exclude.xml
@@ -0,0 +1,83 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java-base/maven-shared-utils/src/pom.xml b/Java-base/maven-shared-utils/src/pom.xml
new file mode 100644
index 000000000..714a52000
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/pom.xml
@@ -0,0 +1,164 @@
+
+
+
+ 4.0.0
+
+
+ org.apache.maven.shared
+ maven-shared-components
+ 34
+
+
+ maven-shared-utils
+ 3.3.0-SNAPSHOT
+
+ Apache Maven Shared Utils
+ Shared utilities for use by Maven core and plugins
+
+
+ scm:git:https://gitbox.apache.org/repos/asf/maven-shared-utils.git
+ scm:git:https://gitbox.apache.org/repos/asf/maven-shared-utils.git
+ https://github.com/apache/maven-shared-utils/tree/${project.scm.tag}
+ HEAD
+
+
+ jira
+ https://issues.apache.org/jira/issues/?jql=project%20%3D%20MSHARED%20AND%20component%20%3D%20maven-shared-utils
+
+
+ Jenkins
+ https://builds.apache.org/job/maven-box/job/maven-shared-utils/
+
+
+
+ apache.website
+ scm:svn:https://svn.apache.org/repos/asf/maven/website/components/${maven.site.path}
+
+
+
+
+
+ Kathryn Newbould
+
+
+
+
+ RedundantThrows,NewlineAtEndOfFile,ParameterNumber,MethodLength,FileLength,ModifierOrder
+
+ 7
+ 3.1.0
+
+
+
+
+ org.fusesource.jansi
+ jansi
+ 1.13
+ true
+
+
+ junit
+ junit
+ 4.13
+ test
+
+
+ org.hamcrest
+ hamcrest-core
+ 2.2
+ test
+
+
+ commons-io
+ commons-io
+ 2.6
+
+
+ org.apache.commons
+ commons-lang3
+ 3.8.1
+ test
+
+
+ org.apache.commons
+ commons-text
+ 1.3
+ test
+
+
+ com.google.code.findbugs
+ jsr305
+ 3.0.2
+ provided
+
+
+
+ org.apache.maven
+ maven-core
+ ${mavenVersion}
+ test
+
+
+ org.codehaus.plexus
+ plexus-container-default
+ 1.7.1
+ provided
+
+
+ org.apache.maven.plugin-testing
+ maven-plugin-testing-harness
+ 2.1
+ test
+
+
+
+
+
+
+ org.codehaus.mojo
+ findbugs-maven-plugin
+
+ findbugs-exclude.xml
+
+
+
+ org.apache.rat
+ apache-rat-plugin
+
+
+ src/test/resources/directorywalker/**/*
+ src/test/resources/symlinks/**/*
+ src/test/resources/executable
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+ false
+
+
+
+
+
+
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/Os.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/Os.java
new file mode 100644
index 000000000..c4c5f33b9
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/Os.java
@@ -0,0 +1,430 @@
+package org.apache.maven.shared.utils;
+
+/*
+ * 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 java.util.Collections;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Set;
+
+/**
+ *
Condition that tests the OS type.
+ *
+ *
This class got copied over from Apache ANT.
+ * Even the version from plexus-utils was
+ * only an ANT fork!
+ * The last time it got copied was on 2011-08-12
+ *
+ *
When merging changes please take care of the special
+ * OS_FAMILY handling in this version of Os.java!
+ *
+ * @author Stefan Bodewig
+ * @author Magesh Umasankar
+ * @author Brian Fox
+ * @author Mark Struberg
+ *
+ */
+public class Os
+{
+ /**
+ * The OS Name.
+ */
+ public static final String OS_NAME = System.getProperty( "os.name" ).toLowerCase( Locale.ENGLISH );
+
+ /**
+ * The OA architecture.
+ */
+ public static final String OS_ARCH = System.getProperty( "os.arch" ).toLowerCase( Locale.ENGLISH );
+
+ /**
+ * The OS version.
+ */
+ public static final String OS_VERSION = System.getProperty( "os.version" ).toLowerCase( Locale.ENGLISH );
+
+ /**
+ * The path separator.
+ */
+ public static final String PATH_SEP = System.getProperty( "path.separator" );
+
+ /**
+ * system line separator , e.g. "\n" on unixoid systems and "\r\n" on Windows
+ */
+ public static final String LINE_SEP = System.getProperty( "line.separator" );
+
+ /**
+ * OS Family
+ */
+ public static final String OS_FAMILY = getOsFamily();
+
+ // store the valid families
+ private static final Set VALID_FAMILIES = getValidFamilies();
+
+
+ /**
+ * OS family to look for
+ */
+ private String family;
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_WINDOWS = "windows";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_WIN9X = "win9x";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_NT = "winnt";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_OS2 = "os/2";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_NETWARE = "netware";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_DOS = "dos";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_MAC = "mac";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_TANDEM = "tandem";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_UNIX = "unix";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_OPENVMS = "openvms";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_ZOS = "z/os";
+
+ /**
+ * OS family that can be tested for. {@value}
+ */
+ public static final String FAMILY_OS400 = "os/400";
+
+ /**
+ * OpenJDK is reported to call MacOS X "Darwin"
+ *
+ * @see bugzilla issue
+ * @see HADOOP-3318
+ */
+ private static final String DARWIN = "darwin";
+
+
+ /**
+ * The set of valid families. This methods initializes the set until
+ * VALID_FAMILIES constant is set.
+ * @return The set of families.
+ */
+ public static Set getValidFamilies()
+ {
+ if ( VALID_FAMILIES != null )
+ {
+ return VALID_FAMILIES;
+ }
+
+ Set valid = new HashSet();
+ valid.add( FAMILY_DOS );
+ valid.add( FAMILY_MAC );
+ valid.add( FAMILY_NETWARE );
+ valid.add( FAMILY_NT );
+ valid.add( FAMILY_OPENVMS );
+ valid.add( FAMILY_OS2 );
+ valid.add( FAMILY_OS400 );
+ valid.add( FAMILY_TANDEM );
+ valid.add( FAMILY_UNIX );
+ valid.add( FAMILY_WIN9X );
+ valid.add( FAMILY_WINDOWS );
+ valid.add( FAMILY_ZOS );
+
+ return Collections.unmodifiableSet( valid );
+ }
+
+ /**
+ * Default constructor
+ */
+ public Os()
+ {
+ //default
+ }
+
+ /**
+ * Constructor that sets the family attribute
+ *
+ * @param family a String value
+ */
+ public Os( String family )
+ {
+ setFamily( family );
+ }
+
+ /**
+ * Sets the desired OS family type
+ *
+ * @param f The OS family type desired
+ * Possible values:
+ *
+ *
dos
+ *
mac
+ *
netware
+ *
os/2
+ *
tandem
+ *
unix
+ *
windows
+ *
win9x
+ *
z/os
+ *
os/400
+ *
+ */
+ private void setFamily( String f )
+ {
+ family = f.toLowerCase( Locale.ENGLISH );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the type of
+ * that set in setFamily.
+ *
+ * @return true if the os matches.
+ * @see Os#setFamily(String)
+ */
+ boolean eval()
+ {
+ return isOs( family, null, null, null );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the
+ * given OS family.
+ *
+ * @param family the family to check for
+ * @return true if the OS matches
+ *
+ */
+ public static boolean isFamily( String family )
+ {
+ return isOs( family, null, null, null );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the
+ * given OS name.
+ *
+ * @param name the OS name to check for
+ * @return true if the OS matches
+ *
+ */
+ public static boolean isName( String name )
+ {
+ return isOs( null, name, null, null );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the
+ * given OS architecture.
+ *
+ * @param arch the OS architecture to check for
+ * @return true if the OS matches
+ *
+ */
+ public static boolean isArch( String arch )
+ {
+ return isOs( null, null, arch, null );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the
+ * given OS version.
+ *
+ * @param version the OS version to check for
+ * @return true if the OS matches
+ *
+ */
+ public static boolean isVersion( String version )
+ {
+ return isOs( null, null, null, version );
+ }
+
+ /**
+ * Determines if the OS on which Ant is executing matches the
+ * given OS family, name, architecture and version
+ *
+ * @param family The OS family
+ * @param name The OS name
+ * @param arch The OS architecture
+ * @param version The OS version
+ * @return true if the OS matches
+ *
+ */
+ private static boolean isOs( String family, String name, String arch, String version )
+ {
+ boolean retValue = false;
+
+ if ( family != null || name != null || arch != null || version != null )
+ {
+
+ boolean isFamily = true;
+ boolean isName = true;
+ boolean isArch = true;
+ boolean isVersion = true;
+
+ if ( family != null )
+ {
+
+ //windows probing logic relies on the word 'windows' in
+ //the OS
+ boolean isWindows = OS_NAME.contains( FAMILY_WINDOWS );
+ boolean is9x = false;
+ boolean isNT = false;
+ if ( isWindows )
+ {
+ //there are only four 9x platforms that we look for
+ is9x =
+ ( OS_NAME.contains( "95" ) || OS_NAME.contains( "98" ) || OS_NAME.contains( "me" )
+ //wince isn't really 9x, but crippled enough to
+ //be a muchness. Ant doesnt run on CE, anyway.
+ || OS_NAME.contains( "ce" ) );
+ isNT = !is9x;
+ }
+ if ( family.equals( FAMILY_WINDOWS ) )
+ {
+ isFamily = isWindows;
+ }
+ else if ( family.equals( FAMILY_WIN9X ) )
+ {
+ isFamily = isWindows && is9x;
+ }
+ else if ( family.equals( FAMILY_NT ) )
+ {
+ isFamily = isWindows && isNT;
+ }
+ else if ( family.equals( FAMILY_OS2 ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_OS2 );
+ }
+ else if ( family.equals( FAMILY_NETWARE ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_NETWARE );
+ }
+ else if ( family.equals( FAMILY_DOS ) )
+ {
+ isFamily = PATH_SEP.equals( ";" ) && !isFamily( FAMILY_NETWARE );
+ }
+ else if ( family.equals( FAMILY_MAC ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_MAC ) || OS_NAME.contains( DARWIN );
+ }
+ else if ( family.equals( FAMILY_TANDEM ) )
+ {
+ isFamily = OS_NAME.contains( "nonstop_kernel" );
+ }
+ else if ( family.equals( FAMILY_UNIX ) )
+ {
+ isFamily = PATH_SEP.equals( ":" ) && !isFamily( FAMILY_OPENVMS ) && ( !isFamily( FAMILY_MAC )
+ || OS_NAME.endsWith( "x" ) || OS_NAME.contains( DARWIN ) );
+ }
+ else if ( family.equals( FAMILY_ZOS ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_ZOS ) || OS_NAME.contains( "os/390" );
+ }
+ else if ( family.equals( FAMILY_OS400 ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_OS400 );
+ }
+ else if ( family.equals( FAMILY_OPENVMS ) )
+ {
+ isFamily = OS_NAME.contains( FAMILY_OPENVMS );
+ }
+ else
+ {
+ isFamily = OS_NAME.contains( family.toLowerCase( Locale.US ) );
+ }
+ }
+ if ( name != null )
+ {
+ isName = name.equals( OS_NAME );
+ }
+ if ( arch != null )
+ {
+ isArch = arch.equals( OS_ARCH );
+ }
+ if ( version != null )
+ {
+ isVersion = version.equals( OS_VERSION );
+ }
+ retValue = isFamily && isName && isArch && isVersion;
+ }
+ return retValue;
+ }
+
+ /**
+ * Helper method to determine the current OS family.
+ *
+ * @return name of current OS family.
+ */
+ private static String getOsFamily()
+ {
+ Set families = getValidFamilies();
+
+ for ( String fam : families )
+ {
+ if ( Os.isFamily( fam ) )
+ {
+ return fam;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Test if the given family String represents a valid Family
+ *
+ * @param family the os family
+ * @return true if 'family' represents a valid OS-Family, false otherwise.
+ */
+ public static boolean isValidFamily( String family )
+ {
+ return VALID_FAMILIES.contains( family );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PathTool.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PathTool.java
new file mode 100644
index 000000000..28bc3083f
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PathTool.java
@@ -0,0 +1,355 @@
+package org.apache.maven.shared.utils;
+
+/*
+ * 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 java.io.File;
+import java.util.StringTokenizer;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Path tool contains static methods to assist in determining path-related
+ * information such as relative paths.
+ *
+ * This class originally got developed at Apache Anakia and later maintained
+ * in maven-utils of Apache Maven-1.
+ * Some external fixes by Apache Committers have been applied later.
+ */
+public class PathTool
+{
+
+ /**
+ * The constructor.
+ *
+ * @deprecated This is a utility class with only static methods. Don't create instances of it.
+ */
+ @Deprecated
+ public PathTool()
+ {
+ }
+
+ /**
+ * Determines the relative path of a filename from a base directory.
+ * This method is useful in building relative links within pages of
+ * a web site. It provides similar functionality to Anakia's
+ * $relativePath context variable. The arguments to
+ * this method may contain either forward or backward slashes as
+ * file separators. The relative path returned is formed using
+ * forward slashes as it is expected this path is to be used as a
+ * link in a web page (again mimicking Anakia's behavior).
+ *
+ * This method is thread-safe.
+ *
+ *
+ *
+ * @param basedir The base directory.
+ * @param filename The filename that is relative to the base
+ * directory.
+ * @return The relative path of the filename from the base
+ * directory. This value is not terminated with a forward slash.
+ * A zero-length string is returned if: the filename is not relative to
+ * the base directory, basedir is null or zero-length,
+ * or filename is null or zero-length.
+ */
+ public static String getRelativePath( @Nullable String basedir, @Nullable String filename )
+ {
+ basedir = uppercaseDrive( basedir );
+ filename = uppercaseDrive( filename );
+
+ /*
+ * Verify the arguments and make sure the filename is relative
+ * to the base directory.
+ */
+ if ( basedir == null || basedir.length() == 0 || filename == null || filename.length() == 0
+ || !filename.startsWith( basedir ) )
+ {
+ return "";
+ }
+
+ /*
+ * Normalize the arguments. First, determine the file separator
+ * that is being used, then strip that off the end of both the
+ * base directory and filename.
+ */
+ String separator = determineSeparator( filename );
+ basedir = StringUtils.chompLast( basedir, separator );
+ filename = StringUtils.chompLast( filename, separator );
+
+ /*
+ * Remove the base directory from the filename to end up with a
+ * relative filename (relative to the base directory). This
+ * filename is then used to determine the relative path.
+ */
+ String relativeFilename = filename.substring( basedir.length() );
+
+ return determineRelativePath( relativeFilename, separator );
+ }
+
+ /**
+ * This method can calculate the relative path between two pathes on a file system.
+ *
+ *
+ * Note: On Windows based system, the / character should be replaced by \ character.
+ *
+ * @param oldPath old path
+ * @param newPath new path
+ * @return a relative file path from oldPath.
+ */
+ public static String getRelativeFilePath( final String oldPath, final String newPath )
+ {
+ if ( StringUtils.isEmpty( oldPath ) || StringUtils.isEmpty( newPath ) )
+ {
+ return "";
+ }
+
+ // normalise the path delimiters
+ String fromPath = new File( oldPath ).getPath();
+ String toPath = new File( newPath ).getPath();
+
+ // strip any leading slashes if its a windows path
+ if ( toPath.matches( "^\\[a-zA-Z]:" ) )
+ {
+ toPath = toPath.substring( 1 );
+ }
+ if ( fromPath.matches( "^\\[a-zA-Z]:" ) )
+ {
+ fromPath = fromPath.substring( 1 );
+ }
+
+ // lowercase windows drive letters.
+ if ( fromPath.startsWith( ":", 1 ) )
+ {
+ fromPath = Character.toLowerCase( fromPath.charAt( 0 ) ) + fromPath.substring( 1 );
+ }
+ if ( toPath.startsWith( ":", 1 ) )
+ {
+ toPath = Character.toLowerCase( toPath.charAt( 0 ) ) + toPath.substring( 1 );
+ }
+
+ // check for the presence of windows drives. No relative way of
+ // traversing from one to the other.
+ if ( ( toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) )
+ && ( !toPath.substring( 0, 1 ).equals( fromPath.substring( 0, 1 ) ) ) )
+ {
+ // they both have drive path element but they dont match, no
+ // relative path
+ return null;
+ }
+
+ if ( ( toPath.startsWith( ":", 1 ) && !fromPath.startsWith( ":", 1 ) )
+ || ( !toPath.startsWith( ":", 1 ) && fromPath.startsWith( ":", 1 ) ) )
+ {
+ // one has a drive path element and the other doesnt, no relative
+ // path.
+ return null;
+ }
+
+ String resultPath = buildRelativePath( toPath, fromPath, File.separatorChar );
+
+ if ( newPath.endsWith( File.separator ) && !resultPath.endsWith( File.separator ) )
+ {
+ return resultPath + File.separator;
+ }
+
+ return resultPath;
+ }
+
+ // ----------------------------------------------------------------------
+ // Private methods
+ // ----------------------------------------------------------------------
+
+ /**
+ * Determines the relative path of a filename. For each separator
+ * within the filename (except the leading if present), append the
+ * "../" string to the return value.
+ *
+ * @param filename The filename to parse.
+ * @param separator The separator used within the filename.
+ * @return The relative path of the filename. This value is not
+ * terminated with a forward slash. A zero-length string is
+ * returned if: the filename is zero-length.
+ */
+ @Nonnull private static String determineRelativePath( @Nonnull String filename, @Nonnull String separator )
+ {
+ if ( filename.length() == 0 )
+ {
+ return "";
+ }
+
+ /*
+ * Count the slashes in the relative filename, but exclude the
+ * leading slash. If the path has no slashes, then the filename
+ * is relative to the current directory.
+ */
+ int slashCount = StringUtils.countMatches( filename, separator ) - 1;
+ if ( slashCount <= 0 )
+ {
+ return ".";
+ }
+
+ /*
+ * The relative filename contains one or more slashes indicating
+ * that the file is within one or more directories. Thus, each
+ * slash represents a "../" in the relative path.
+ */
+ StringBuilder sb = new StringBuilder();
+ for ( int i = 0; i < slashCount; i++ )
+ {
+ sb.append( "../" );
+ }
+
+ /*
+ * Finally, return the relative path but strip the trailing
+ * slash to mimic Anakia's behavior.
+ */
+ return StringUtils.chop( sb.toString() );
+ }
+
+ /**
+ * Helper method to determine the file separator (forward or
+ * backward slash) used in a filename. The slash that occurs more
+ * often is returned as the separator.
+ *
+ * @param filename The filename parsed to determine the file
+ * separator.
+ * @return The file separator used within filename.
+ * This value is either a forward or backward slash.
+ */
+ private static String determineSeparator( String filename )
+ {
+ int forwardCount = StringUtils.countMatches( filename, "/" );
+ int backwardCount = StringUtils.countMatches( filename, "\\" );
+
+ return forwardCount >= backwardCount ? "/" : "\\";
+ }
+
+ /**
+ * Cygwin prefers lowercase drive letters, but other parts of maven use uppercase
+ *
+ * @param path old path
+ * @return String
+ */
+ static String uppercaseDrive( @Nullable String path )
+ {
+ if ( path == null )
+ {
+ return null;
+ }
+ if ( path.length() >= 2 && path.charAt( 1 ) == ':' )
+ {
+ path = Character.toUpperCase( path.charAt( 0 ) ) + path.substring( 1 );
+ }
+ return path;
+ }
+
+ @Nonnull private static String buildRelativePath( @Nonnull String toPath, @Nonnull String fromPath,
+ final char separatorChar )
+ {
+ // use tokeniser to traverse paths and for lazy checking
+ StringTokenizer toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
+ StringTokenizer fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
+
+ int count = 0;
+
+ // walk along the to path looking for divergence from the from path
+ while ( toTokeniser.hasMoreTokens() && fromTokeniser.hasMoreTokens() )
+ {
+ if ( separatorChar == '\\' )
+ {
+ if ( !fromTokeniser.nextToken().equalsIgnoreCase( toTokeniser.nextToken() ) )
+ {
+ break;
+ }
+ }
+ else
+ {
+ if ( !fromTokeniser.nextToken().equals( toTokeniser.nextToken() ) )
+ {
+ break;
+ }
+ }
+
+ count++;
+ }
+
+ // reinitialise the tokenisers to count positions to retrieve the
+ // gobbled token
+
+ toTokeniser = new StringTokenizer( toPath, String.valueOf( separatorChar ) );
+ fromTokeniser = new StringTokenizer( fromPath, String.valueOf( separatorChar ) );
+
+ while ( count-- > 0 )
+ {
+ fromTokeniser.nextToken();
+ toTokeniser.nextToken();
+ }
+
+ StringBuilder relativePath = new StringBuilder();
+
+ // add back refs for the rest of from location.
+ while ( fromTokeniser.hasMoreTokens() )
+ {
+ fromTokeniser.nextToken();
+
+ relativePath.append( ".." );
+
+ if ( fromTokeniser.hasMoreTokens() )
+ {
+ relativePath.append( separatorChar );
+ }
+ }
+
+ if ( relativePath.length() != 0 && toTokeniser.hasMoreTokens() )
+ {
+ relativePath.append( separatorChar );
+ }
+
+ // add fwd fills for whatevers left of newPath.
+ while ( toTokeniser.hasMoreTokens() )
+ {
+ relativePath.append( toTokeniser.nextToken() );
+
+ if ( toTokeniser.hasMoreTokens() )
+ {
+ relativePath.append( separatorChar );
+ }
+ }
+ return relativePath.toString();
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PropertyUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PropertyUtils.java
new file mode 100644
index 000000000..70e21ed24
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/PropertyUtils.java
@@ -0,0 +1,218 @@
+package org.apache.maven.shared.utils;
+
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Static utility methods for loading properties.
+ */
+public class PropertyUtils
+{
+
+ /**
+ * The constructor.
+ *
+ * @deprecated This is a utility class with only static methods. Don't create instances of it.
+ */
+ @Deprecated
+ public PropertyUtils()
+ {
+ }
+
+ /**
+ * @param url the URL which should be used to load the properties
+ * @return the loaded properties
+ * @deprecated use {@link #loadOptionalProperties(java.net.URL)} instead. This method should not
+ * be used as it suppresses exceptions silently when loading properties fails and returns {@code null}
+ * instead of an empty {@code Properties} instance when the given {@code URL} is {@code null}.
+ */
+ @Deprecated
+ public static java.util.Properties loadProperties( @Nonnull URL url )
+ {
+ try ( InputStream in = url.openStream() )
+ {
+ return loadProperties( in );
+ }
+ catch ( Exception e )
+ {
+ // ignore
+ }
+ return null;
+ }
+
+ /**
+ * @param file the file from which the properties will be loaded
+ * @return the loaded properties
+ * @deprecated use {@link #loadOptionalProperties(java.io.File)} instead. This method should not
+ * be used as it suppresses exceptions silently when loading properties fails and returns {@code null}
+ * instead of an empty {@code Properties} instance when the given {@code File} is {@code null}.
+ */
+ @Deprecated
+ public static Properties loadProperties( @Nonnull File file )
+ {
+ try ( InputStream in = new FileInputStream( file ) )
+ {
+ return loadProperties( in );
+ }
+ catch ( Exception e )
+ {
+ // ignore
+ }
+ return null;
+ }
+
+ /**
+ * Loads {@code Properties} from an {@code InputStream} and closes the stream.
+ * In a future release, this will no longer close the stream, so callers
+ * should close the stream themselves.
+ *
+ * @param is {@link InputStream}
+ * @return the loaded properties
+ * @deprecated use {@link #loadOptionalProperties(java.io.InputStream)} instead. This method
+ * should not be used as it suppresses exceptions silently when loading properties fails.
+ */
+ @Deprecated
+ public static Properties loadProperties( @Nullable InputStream is )
+ {
+ try
+ {
+ Properties result = new Properties();
+ if ( is != null )
+ {
+ try ( InputStream in = is )
+ {
+ result.load( in );
+ }
+ catch ( IOException e )
+ {
+ // ignore
+ }
+ }
+ return result;
+ }
+ catch ( Exception e )
+ {
+ // ignore
+ }
+ return null;
+ }
+
+ /**
+ * Loads {@code Properties} from a given {@code URL}.
+ *
+ * If the given {@code URL} is {@code null} or the properties can't be read, an empty properties object is returned.
+ *
+ *
+ * @param url the {@code URL} of the properties resource to load or {@code null}
+ * @return the loaded properties or an empty {@code Properties} instance if properties fail to load
+ * @since 3.1.0
+ */
+ @Nonnull
+ public static Properties loadOptionalProperties( final @Nullable URL url )
+ {
+
+ Properties properties = new Properties();
+ if ( url != null )
+ {
+ try ( InputStream in = url.openStream() )
+ {
+ properties.load( in );
+ }
+ catch ( IllegalArgumentException | IOException ex )
+ {
+ // ignore and return empty properties
+ }
+ }
+ return properties;
+ }
+
+ /**
+ * Loads {@code Properties} from a {@code File}.
+ *
+ * If the given {@code File} is {@code null} or the properties file can't be read, an empty properties object is
+ * returned.
+ *
Originally from
+ * Turbine, the
+ * GenerationJavaCore library and Velocity.
+ * Later a lots methods from commons-lang StringUtils
+ * got added too. Gradually smaller additions and fixes have been made
+ * over the time by various ASF committers.
Whitespace is defined by
+ * {@link Character#isWhitespace(char)}.
+ *
+ * @param str String target to delete whitespace from
+ * @return the String without whitespace
+ * @throws NullPointerException
+ */
+ @Nonnull public static String deleteWhitespace( @Nonnull String str )
+ {
+ StringBuilder buffer = new StringBuilder();
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( !Character.isWhitespace( str.charAt( i ) ) )
+ {
+ buffer.append( str.charAt( i ) );
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ *
Checks if a String is non null and is
+ * not empty (length > 0).
+ *
+ * @param str the String to check
+ * @return true if the String is non-null, and not length zero
+ */
+ public static boolean isNotEmpty( @Nullable String str )
+ {
+ return ( ( str != null ) && ( str.length() > 0 ) );
+ }
+
+ /**
+ *
Checks if a (trimmed) String is null or empty.
+ *
+ *
Note: In future releases, this method will no longer trim the input string such that it works
+ * complementary to {@link #isNotEmpty(String)}. Code that wants to test for whitespace-only strings should be
+ * migrated to use {@link #isBlank(String)} instead.
+ *
+ * @param str the String to check
+ * @return true if the String is null, or
+ * length zero once trimmed
+ */
+ public static boolean isEmpty( @Nullable String str )
+ {
+ return ( ( str == null ) || ( str.trim().length() == 0 ) );
+ }
+
+ /**
+ *
+ * Checks if a String is whitespace, empty ("") or null.
+ *
+ *
+ * @param str the String to check, may be null
+ * @return true if the String is not empty and not null and not whitespace
+ *
+ */
+ public static boolean isNotBlank( @Nullable String str )
+ {
+ return !isBlank( str );
+ }
+
+ // Equals and IndexOf
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Compares two Strings, returning true if they are equal.
+ *
+ *
nulls are handled without exceptions. Two null
+ * references are considered to be equal. The comparison is case sensitive.
+ *
+ * @param str1 the first string
+ * @param str2 the second string
+ * @return true if the Strings are equal, case sensitive, or
+ * both null
+ * @see java.lang.String#equals(Object)
+ * @deprecated use {@code java.lang.Objects.equals()}
+ */
+ @Deprecated
+ public static boolean equals( @Nullable String str1, @Nullable String str2 )
+ {
+ return ( str1 == null ? str2 == null : str1.equals( str2 ) );
+ }
+
+ /**
+ *
Compares two Strings, returning true if they are equal ignoring
+ * the case.
+ *
+ *
Nulls are handled without exceptions. Two null
+ * references are considered equal. Comparison is case insensitive.
+ *
+ * @param str1 the first string
+ * @param str2 the second string
+ * @return true if the Strings are equal, case insensitive, or
+ * both null
+ * @see java.lang.String#equalsIgnoreCase(String)
+ */
+ public static boolean equalsIgnoreCase( String str1, String str2 )
+ {
+ return ( str1 == null ? str2 == null : str1.equalsIgnoreCase( str2 ) );
+ }
+
+ /**
+ *
Find the first index of any of a set of potential substrings.
+ *
+ *
null String will return -1.
+ *
+ * @param str the String to check
+ * @param searchStrs the Strings to search for
+ * @return the first index of any of the searchStrs in str
+ * @throws NullPointerException if any of searchStrs[i] is null
+ */
+ public static int indexOfAny( String str, String... searchStrs )
+ {
+ if ( ( str == null ) || ( searchStrs == null ) )
+ {
+ return -1;
+ }
+ // String's can't have a MAX_VALUEth index.
+ int ret = Integer.MAX_VALUE;
+
+ int tmp;
+ for ( String searchStr : searchStrs )
+ {
+ tmp = str.indexOf( searchStr );
+ if ( tmp == -1 )
+ {
+ continue;
+ }
+
+ if ( tmp < ret )
+ {
+ ret = tmp;
+ }
+ }
+
+ return ( ret == Integer.MAX_VALUE ) ? -1 : ret;
+ }
+
+ /**
+ *
Find the latest index of any of a set of potential substrings.
+ *
+ *
null string will return -1.
+ *
+ * @param str the String to check
+ * @param searchStrs the Strings to search for
+ * @return the last index of any of the Strings
+ * @throws NullPointerException if any of searchStrs[i] is null
+ */
+ public static int lastIndexOfAny( String str, String... searchStrs )
+ {
+ if ( ( str == null ) || ( searchStrs == null ) )
+ {
+ return -1;
+ }
+ int ret = -1;
+ int tmp;
+ for ( String searchStr : searchStrs )
+ {
+ tmp = str.lastIndexOf( searchStr );
+ if ( tmp > ret )
+ {
+ ret = tmp;
+ }
+ }
+ return ret;
+ }
+
+ // Substring
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Gets a substring from the specified string avoiding exceptions.
+ *
+ *
A negative start position can be used to start n
+ * characters from the end of the String.
+ *
+ * @param str the String to get the substring from
+ * @param start the position to start from, negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position
+ */
+ public static String substring( String str, int start )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+
+ // handle negatives, which means last n characters
+ if ( start < 0 )
+ {
+ start = str.length() + start; // remember start is negative
+ }
+
+ if ( start < 0 )
+ {
+ start = 0;
+ }
+ if ( start > str.length() )
+ {
+ return "";
+ }
+
+ return str.substring( start );
+ }
+
+ /**
+ *
Gets a substring from the specified String avoiding exceptions.
+ *
+ *
A negative start position can be used to start/end n
+ * characters from the end of the String.
+ *
+ * @param str the String to get the substring from
+ * @param start the position to start from, negative means
+ * count back from the end of the string by this many characters
+ * @param end the position to end at (exclusive), negative means
+ * count back from the end of the String by this many characters
+ * @return substring from start position to end position
+ */
+ public static String substring( String str, int start, int end )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+
+ // handle negatives
+ if ( end < 0 )
+ {
+ end = str.length() + end; // remember end is negative
+ }
+ if ( start < 0 )
+ {
+ start = str.length() + start; // remember start is negative
+ }
+
+ // check length next
+ if ( end > str.length() )
+ {
+ // check this works.
+ end = str.length();
+ }
+
+ // if start is greater than end, return ""
+ if ( start > end )
+ {
+ return "";
+ }
+
+ if ( start < 0 )
+ {
+ start = 0;
+ }
+ if ( end < 0 )
+ {
+ end = 0;
+ }
+
+ return str.substring( start, end );
+ }
+
+ /**
+ *
Gets the leftmost n characters of a String.
+ *
+ *
If n characters are not available, or the
+ * String is null, the String will be returned without
+ * an exception.
+ *
+ * @param str the String to get the leftmost characters from
+ * @param len the length of the required String
+ * @return the leftmost characters
+ * @throws IllegalArgumentException if len is less than zero
+ */
+ public static String left( String str, int len )
+ {
+ if ( len < 0 )
+ {
+ throw new IllegalArgumentException( "Requested String length " + len + " is less than zero" );
+ }
+ if ( ( str == null ) || ( str.length() <= len ) )
+ {
+ return str;
+ }
+ else
+ {
+ return str.substring( 0, len );
+ }
+ }
+
+ /**
+ *
Gets the rightmost n characters of a String.
+ *
+ *
If n characters are not available, or the String
+ * is null, the String will be returned without an
+ * exception.
+ *
+ * @param str the String to get the rightmost characters from
+ * @param len the length of the required String
+ * @return the leftmost characters
+ * @throws IllegalArgumentException if len is less than zero
+ */
+ public static String right( String str, int len )
+ {
+ if ( len < 0 )
+ {
+ throw new IllegalArgumentException( "Requested String length " + len + " is less than zero" );
+ }
+ if ( ( str == null ) || ( str.length() <= len ) )
+ {
+ return str;
+ }
+ else
+ {
+ return str.substring( str.length() - len );
+ }
+ }
+
+ /**
+ *
Gets n characters from the middle of a String.
+ *
+ *
If n characters are not available, the remainder
+ * of the String will be returned without an exception. If the
+ * String is null, null will be returned.
+ *
+ * @param str the String to get the characters from
+ * @param pos the position to start from
+ * @param len the length of the required String
+ * @return the leftmost characters
+ * @throws IndexOutOfBoundsException if pos is out of bounds
+ * @throws IllegalArgumentException if len is less than zero
+ */
+ public static String mid( String str, int pos, int len )
+ {
+ if ( ( pos < 0 ) || ( ( str != null ) && ( pos > str.length() ) ) )
+ {
+ throw new StringIndexOutOfBoundsException( "String index " + pos + " is out of bounds" );
+ }
+ if ( len < 0 )
+ {
+ throw new IllegalArgumentException( "Requested String length " + len + " is less than zero" );
+ }
+ if ( str == null )
+ {
+ return null;
+ }
+ if ( str.length() <= ( pos + len ) )
+ {
+ return str.substring( pos );
+ }
+ else
+ {
+ return str.substring( pos, pos + len );
+ }
+ }
+
+ // Splitting
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Splits the provided text into a array, using whitespace as the
+ * separator.
+ *
+ *
The separator is not included in the returned String array.
+ *
+ * @param str the String to parse
+ * @return an array of parsed Strings
+ */
+ @Nonnull public static String[] split( @Nonnull String str )
+ {
+ return split( str, null, -1 );
+ }
+
+ /**
+ * @param text the text to be split
+ * @param separator the separator at which the text will be split
+ * @return the resulting array
+ * @see #split(String, String, int)
+ */
+ @Nonnull public static String[] split( @Nonnull String text, @Nullable String separator )
+ {
+ return split( text, separator, -1 );
+ }
+
+ /**
+ *
Splits the provided text into a array, based on a given separator.
+ *
+ *
The separator is not included in the returned String array. The
+ * maximum number of splits to perform can be controlled. A null
+ * separator causes splitting on whitespace.
+ *
+ *
This is useful for quickly splitting a String into
+ * an array of tokens, instead of an enumeration of tokens (as
+ * StringTokenizer does).
+ *
+ * @param str the string to parse
+ * @param separator Characters used as the delimiters. If
+ * null, splits on whitespace.
+ * @param max the maximum number of elements to include in the
+ * array. A zero or negative value implies no limit.
+ * @return an array of parsed Strings
+ */
+ @Nonnull public static String[] split( @Nonnull String str, @Nullable String separator, int max )
+ {
+ StringTokenizer tok;
+ if ( separator == null )
+ {
+ // Null separator means we're using StringTokenizer's default
+ // delimiter, which comprises all whitespace characters.
+ tok = new StringTokenizer( str );
+ }
+ else
+ {
+ tok = new StringTokenizer( str, separator );
+ }
+
+ int listSize = tok.countTokens();
+ if ( ( max > 0 ) && ( listSize > max ) )
+ {
+ listSize = max;
+ }
+
+ String[] list = new String[listSize];
+ int i = 0;
+ int lastTokenBegin;
+ int lastTokenEnd = 0;
+ while ( tok.hasMoreTokens() )
+ {
+ if ( ( max > 0 ) && ( i == listSize - 1 ) )
+ {
+ // In the situation where we hit the max yet have
+ // tokens left over in our input, the last list
+ // element gets all remaining text.
+ String endToken = tok.nextToken();
+ lastTokenBegin = str.indexOf( endToken, lastTokenEnd );
+ list[i] = str.substring( lastTokenBegin );
+ break;
+ }
+ else
+ {
+ list[i] = tok.nextToken();
+ lastTokenBegin = str.indexOf( list[i], lastTokenEnd );
+ lastTokenEnd = lastTokenBegin + list[i].length();
+ }
+ i++;
+ }
+ return list;
+ }
+
+ // Joining
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Concatenates elements of an array into a single String.
+ *
+ *
The difference from join is that concatenate has no delimiter.
+ *
+ * @param array the array of values to concatenate.
+ * @return the concatenated string.
+ */
+ @Nonnull public static String concatenate( @Nonnull Object... array )
+ {
+ return join( array, "" );
+ }
+
+ /**
+ *
Joins the elements of the provided array into a single String
+ * containing the provided list of elements.
+ *
+ *
No delimiter is added before or after the list. A
+ * null separator is the same as a blank String.
+ *
+ * @param array the array of values to join together
+ * @param separator the separator character to use
+ * @return the joined String
+ */
+ @Nonnull public static String join( @Nonnull Object[] array, @Nullable String separator )
+ {
+ if ( separator == null )
+ {
+ separator = "";
+ }
+ int arraySize = array.length;
+ int bufSize = ( arraySize == 0 ? 0 : ( array[0].toString().length() + separator.length() ) * arraySize );
+ StringBuilder buf = new StringBuilder( bufSize );
+
+ for ( int i = 0; i < arraySize; i++ )
+ {
+ if ( i > 0 )
+ {
+ buf.append( separator );
+ }
+ buf.append( array[i] );
+ }
+ return buf.toString();
+ }
+
+ /**
+ *
Joins the elements of the provided Iterator into
+ * a single String containing the provided elements.
+ *
+ *
No delimiter is added before or after the list. A
+ * null separator is the same as a blank String.
+ *
+ * @param iterator the Iterator of values to join together
+ * @param separator the separator character to use
+ * @return the joined String
+ */
+ @Nonnull public static String join( @Nonnull Iterator> iterator, String separator )
+ {
+ if ( separator == null )
+ {
+ separator = "";
+ }
+ StringBuilder buf = new StringBuilder( 256 ); // Java default is 16, probably too small
+ while ( iterator.hasNext() )
+ {
+ buf.append( iterator.next() );
+ if ( iterator.hasNext() )
+ {
+ buf.append( separator );
+ }
+ }
+ return buf.toString();
+ }
+
+ // Replacing
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Replace a char with another char inside a larger String, once.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl char to search for
+ * @param with char to replace with
+ * @return the text with any replacements processed
+ * @see #replace(String text, char repl, char with, int max)
+ */
+ public static String replaceOnce( @Nullable String text, char repl, char with )
+ {
+ return replace( text, repl, with, 1 );
+ }
+
+ /**
+ *
Replace all occurances of a char within another char.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl char to search for
+ * @param with char to replace with
+ * @return the text with any replacements processed
+ * @see #replace(String text, char repl, char with, int max)
+ */
+ public static String replace( @Nullable String text, char repl, char with )
+ {
+ return replace( text, repl, with, -1 );
+ }
+
+ /**
+ *
Replace a char with another char inside a larger String,
+ * for the first max values of the search char.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl char to search for
+ * @param with char to replace with
+ * @param max maximum number of values to replace, or -1 if no maximum
+ * @return the text with any replacements processed
+ */
+ public static String replace( @Nullable String text, char repl, char with, int max )
+ {
+ return replace( text, String.valueOf( repl ), String.valueOf( with ), max );
+ }
+
+ /**
+ *
Replace a String with another String inside a larger String, once.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl String to search for
+ * @param with String to replace with
+ * @return the text with any replacements processed
+ * @see #replace(String text, String repl, String with, int max)
+ */
+ public static String replaceOnce( @Nullable String text, @Nullable String repl, @Nullable String with )
+ {
+ return replace( text, repl, with, 1 );
+ }
+
+ /**
+ *
Replace all occurrences of a String within another String.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl String to search for
+ * @param with String to replace with
+ * @return the text with any replacements processed
+ * @see #replace(String text, String repl, String with, int max)
+ */
+ public static String replace( @Nullable String text, @Nullable String repl, @Nullable String with )
+ {
+ return replace( text, repl, with, -1 );
+ }
+
+ /**
+ *
Replace a String with another String inside a larger String,
+ * for the first max values of the search String.
+ *
+ *
A null reference passed to this method is a no-op.
+ *
+ * @param text text to search and replace in
+ * @param repl String to search for
+ * @param with String to replace with
+ * @param max maximum number of values to replace, or -1 if no maximum
+ * @return the text with any replacements processed
+ */
+ public static String replace( @Nullable String text, @Nullable String repl, @Nullable String with, int max )
+ {
+ if ( ( text == null ) || ( repl == null ) || ( with == null ) || ( repl.length() == 0 ) )
+ {
+ return text;
+ }
+
+ StringBuilder buf = new StringBuilder( text.length() );
+ int start = 0, end;
+ while ( ( end = text.indexOf( repl, start ) ) != -1 )
+ {
+ buf.append( text, start, end ).append( with );
+ start = end + repl.length();
+
+ if ( --max == 0 )
+ {
+ break;
+ }
+ }
+ buf.append( text, start, text.length() );
+ return buf.toString();
+ }
+
+ /**
+ *
Overlay a part of a String with another String.
+ *
+ * @param text String to do overlaying in
+ * @param overlay String to overlay
+ * @param start position to start overlaying at
+ * @param end position to stop overlaying before
+ * @return String with overlaid text
+ * @throws NullPointerException if text or overlay is null
+ */
+ @Nonnull public static String overlayString( @Nonnull String text, @Nonnull String overlay, int start, int end )
+ {
+ if ( overlay == null )
+ {
+ throw new NullPointerException( "overlay is null" );
+ }
+ return new StringBuilder( start + overlay.length() + text.length() - end + 1 )
+ .append( text, 0, start )
+ .append( overlay )
+ .append( text, end, text.length() )
+ .toString();
+ }
+
+ // Centering
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Center a String in a larger String of size n.
+ *
+ *
Uses spaces as the value to buffer the String with.
+ * Equivalent to center(str, size, " ").
+ *
+ * @param str String to center
+ * @param size int size of new String
+ * @return String containing centered String
+ * @throws NullPointerException if str is null
+ */
+ @Nonnull public static String center( @Nonnull String str, int size )
+ {
+ return center( str, size, " " );
+ }
+
+ /**
+ *
Center a String in a larger String of size n.
+ *
+ *
Uses a supplied String as the value to buffer the String with.
+ *
+ * @param str String to center
+ * @param size int size of new String
+ * @param delim String to buffer the new String with
+ * @return String containing centered String
+ * @throws NullPointerException if str or delim is null
+ * @throws ArithmeticException if delim is the empty String
+ */
+ @Nonnull public static String center( @Nonnull String str, int size, @Nonnull String delim )
+ {
+ int sz = str.length();
+ int p = size - sz;
+ if ( p < 1 )
+ {
+ return str;
+ }
+ str = leftPad( str, sz + p / 2, delim );
+ str = rightPad( str, size, delim );
+ return str;
+ }
+
+ // Chomping
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Remove the last newline, and everything after it from a String.
+ *
+ * @param str String to chomp the newline from
+ * @return String without chomped newline
+ * @throws NullPointerException if str is null
+ */
+ @Nonnull public static String chomp( @Nonnull String str )
+ {
+ return chomp( str, "\n" );
+ }
+
+ /**
+ *
Remove the last value of a supplied String, and everything after
+ * it from a String.
+ *
+ * @param str String to chomp from
+ * @param sep String to chomp
+ * @return String without chomped ending
+ * @throws NullPointerException if str or sep is null
+ */
+ @Nonnull public static String chomp( @Nonnull String str, @Nonnull String sep )
+ {
+ int idx = str.lastIndexOf( sep );
+ if ( idx != -1 )
+ {
+ return str.substring( 0, idx );
+ }
+ else
+ {
+ return str;
+ }
+ }
+
+ /**
+ *
Remove a newline if and only if it is at the end
+ * of the supplied String.
+ *
+ * @param str String to chomp from
+ * @return String without chomped ending
+ * @throws NullPointerException if str is null
+ */
+ @Nonnull public static String chompLast( @Nonnull String str )
+ {
+ return chompLast( str, "\n" );
+ }
+
+ /**
+ *
Remove a value if and only if the String ends with that value.
+ *
+ * @param str String to chomp from
+ * @param sep String to chomp
+ * @return String without chomped ending
+ * @throws NullPointerException if str or sep is null
+ */
+ @Nonnull public static String chompLast( @Nonnull String str, @Nonnull String sep )
+ {
+ if ( str.length() == 0 )
+ {
+ return str;
+ }
+ String sub = str.substring( str.length() - sep.length() );
+ if ( sep.equals( sub ) )
+ {
+ return str.substring( 0, str.length() - sep.length() );
+ }
+ else
+ {
+ return str;
+ }
+ }
+
+ /**
+ *
Remove everything and return the last value of a supplied String, and
+ * everything after it from a String.
+ *
+ * @param str String to repeat
+ * @param repeat number of times to repeat str
+ * @return String with repeated String
+ * @throws NegativeArraySizeException if repeat < 0
+ * @throws NullPointerException if str is null
+ */
+ @Nonnull public static String repeat( @Nonnull String str, int repeat )
+ {
+ StringBuilder buffer = new StringBuilder( repeat * str.length() );
+ for ( int i = 0; i < repeat; i++ )
+ {
+ buffer.append( str );
+ }
+ return buffer.toString();
+ }
+
+ /**
+ *
Right pad a String with spaces.
+ *
+ *
The String is padded to the size of n.
+ *
+ * @param str String to repeat
+ * @param size number of times to repeat str
+ * @return right padded String
+ * @throws NullPointerException if str is null
+ */
+ @Nonnull public static String rightPad( @Nonnull String str, int size )
+ {
+ return rightPad( str, size, " " );
+ }
+
+ /**
+ *
Right pad a String with a specified string.
+ *
+ *
The String is padded to the size of n.
+ *
+ * @param str String to pad out
+ * @param size size to pad to
+ * @param delim String to pad with
+ * @return right padded String
+ * @throws NullPointerException if str or delim is null
+ * @throws ArithmeticException if delim is the empty String
+ */
+ @Nonnull public static String rightPad( @Nonnull String str, int size, @Nonnull String delim )
+ {
+ size = ( size - str.length() ) / delim.length();
+ if ( size > 0 )
+ {
+ str += repeat( delim, size );
+ }
+ return str;
+ }
+
+ /**
+ *
Left pad a String with spaces.
+ *
+ *
The String is padded to the size of n.
+ *
+ * @param str String to pad out
+ * @param size size to pad to
+ * @return left padded String
+ * @throws NullPointerException if str or delim is null
+ */
+ @Nonnull public static String leftPad( @Nonnull String str, int size )
+ {
+ return leftPad( str, size, " " );
+ }
+
+ /**
+ * Left pad a String with a specified string. Pad to a size of n.
+ *
+ * @param str String to pad out
+ * @param size size to pad to
+ * @param delim String to pad with
+ * @return left padded String
+ * @throws NullPointerException if str or delim is null
+ * @throws ArithmeticException if delim is the empty string
+ */
+ @Nonnull public static String leftPad( @Nonnull String str, int size, @Nonnull String delim )
+ {
+ size = ( size - str.length() ) / delim.length();
+ if ( size > 0 )
+ {
+ str = repeat( delim, size ) + str;
+ }
+ return str;
+ }
+
+ // Stripping
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Remove whitespace from the front and back of a String.
+ *
+ * @param str the String to remove whitespace from
+ * @return the stripped String
+ */
+ public static String strip( String str )
+ {
+ return strip( str, null );
+ }
+
+ /**
+ *
Remove a specified String from the front and back of a
+ * String.
+ *
+ *
If whitespace is wanted to be removed, used the
+ * {@link #strip(java.lang.String)} method.
+ *
+ * @param str the String to remove a string from
+ * @param delim the String to remove at start and end
+ * @return the stripped String
+ */
+ public static String strip( String str, @Nullable String delim )
+ {
+ str = stripStart( str, delim );
+ return stripEnd( str, delim );
+ }
+
+ /**
+ *
Strip whitespace from the front and back of every String
+ * in the array.
+ *
+ * @param strs the Strings to remove whitespace from
+ * @return the stripped Strings
+ */
+ public static String[] stripAll( String... strs )
+ {
+ return stripAll( strs, null );
+ }
+
+ /**
+ *
Strip the specified delimiter from the front and back of
+ * every String in the array.
+ *
+ * @param strs the Strings to remove a String from
+ * @param delimiter the String to remove at start and end
+ * @return the stripped Strings
+ */
+ public static String[] stripAll( String[] strs, @Nullable String delimiter )
+ {
+ if ( ( strs == null ) || ( strs.length == 0 ) )
+ {
+ return strs;
+ }
+ int sz = strs.length;
+ String[] newArr = new String[sz];
+ for ( int i = 0; i < sz; i++ )
+ {
+ newArr[i] = strip( strs[i], delimiter );
+ }
+ return newArr;
+ }
+
+ /**
+ *
Strip any of a supplied String from the end of a String.
+ *
+ *
If the strip String is null, whitespace is
+ * stripped.
+ *
+ * @param str the String to remove characters from
+ * @param strip the String to remove
+ * @return the stripped String
+ */
+ public static String stripEnd( String str, @Nullable String strip )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+ int end = str.length();
+
+ if ( strip == null )
+ {
+ while ( ( end != 0 ) && Character.isWhitespace( str.charAt( end - 1 ) ) )
+ {
+ end--;
+ }
+ }
+ else
+ {
+ while ( ( end != 0 ) && ( strip.indexOf( str.charAt( end - 1 ) ) != -1 ) )
+ {
+ end--;
+ }
+ }
+ return str.substring( 0, end );
+ }
+
+ /**
+ *
Strip any of a supplied String from the start of a String.
+ *
+ *
If the strip String is null, whitespace is
+ * stripped.
Uses {@link Character#isWhitespace(char)} as a
+ * separator between words.
+ *
+ *
null will return null.
+ *
+ * @param str the String to capitalise
+ * @return capitalised String
+ */
+ public static String capitaliseAllWords( String str )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+ int sz = str.length();
+ StringBuilder buffer = new StringBuilder( sz );
+ boolean space = true;
+ for ( int i = 0; i < sz; i++ )
+ {
+ char ch = str.charAt( i );
+ if ( Character.isWhitespace( ch ) )
+ {
+ buffer.append( ch );
+ space = true;
+ }
+ else if ( space )
+ {
+ buffer.append( Character.toTitleCase( ch ) );
+ space = false;
+ }
+ else
+ {
+ buffer.append( ch );
+ }
+ }
+ return buffer.toString();
+ }
+
+ /**
+ *
Uncapitalise all the words in a string.
+ *
+ *
Uses {@link Character#isWhitespace(char)} as a
+ * separator between words.
+ *
+ *
null will return null.
+ *
+ * @param str the string to uncapitalise
+ * @return uncapitalised string
+ */
+ public static String uncapitaliseAllWords( String str )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+ int sz = str.length();
+ StringBuilder buffer = new StringBuilder( sz );
+ boolean space = true;
+ for ( int i = 0; i < sz; i++ )
+ {
+ char ch = str.charAt( i );
+ if ( Character.isWhitespace( ch ) )
+ {
+ buffer.append( ch );
+ space = true;
+ }
+ else if ( space )
+ {
+ buffer.append( Character.toLowerCase( ch ) );
+ space = false;
+ }
+ else
+ {
+ buffer.append( ch );
+ }
+ }
+ return buffer.toString();
+ }
+
+ // Nested extraction
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Get the String that is nested in between two instances of the
+ * same String.
+ *
+ *
If str is null, will
+ * return null.
+ *
+ * @param str the String containing nested-string
+ * @param tag the String before and after nested-string
+ * @return the String that was nested, or null
+ * @throws NullPointerException if tag is null
+ */
+ public static String getNestedString( String str, @Nonnull String tag )
+ {
+ return getNestedString( str, tag, tag );
+ }
+
+ /**
+ *
Get the String that is nested in between two Strings.
+ *
+ * @param str the String containing nested-string
+ * @param open the String before nested-string
+ * @param close the String after nested-string
+ * @return the String that was nested, or null
+ * @throws NullPointerException if open or close is null
+ */
+ public static String getNestedString( String str, @Nonnull String open, @Nonnull String close )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+ int start = str.indexOf( open );
+ if ( start != -1 )
+ {
+ int end = str.indexOf( close, start + open.length() );
+ if ( end != -1 )
+ {
+ return str.substring( start + open.length(), end );
+ }
+ }
+ return null;
+ }
+
+ /**
+ *
How many times is the substring in the larger String.
+ *
+ *
null returns 0.
+ *
+ * @param str the String to check
+ * @param sub the substring to count
+ * @return the number of occurrences, 0 if the String is null
+ * @throws NullPointerException if sub is null
+ */
+ public static int countMatches( @Nullable String str, @Nonnull String sub )
+ {
+ if ( sub.equals( "" ) )
+ {
+ return 0;
+ }
+ if ( str == null )
+ {
+ return 0;
+ }
+ int count = 0;
+ int idx = 0;
+ while ( ( idx = str.indexOf( sub, idx ) ) != -1 )
+ {
+ count++;
+ idx += sub.length();
+ }
+ return count;
+ }
+
+ // Character Tests
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Checks if the String contains only Unicode letters.
+ *
+ *
null will return false.
+ * An empty String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains letters, and is non-null
+ */
+ public static boolean isAlpha( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( !Character.isLetter( str.charAt( i ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
Checks if the String contains only whitespace.
+ *
+ *
null will return false. An
+ * empty String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains whitespace, and is non-null
+ */
+ public static boolean isWhitespace( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( ( !Character.isWhitespace( str.charAt( i ) ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
Checks if the String contains only Unicode letters and
+ * space (' ').
+ *
+ *
null will return false. An
+ * empty String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains letters and space,
+ * and is non-null
+ */
+ public static boolean isAlphaSpace( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( ( !Character.isLetter( str.charAt( i ) ) ) && ( str.charAt( i ) != ' ' ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
Checks if the String contains only Unicode letters or digits.
+ *
+ *
null will return false. An empty
+ * String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains letters or digits,
+ * and is non-null
+ */
+ public static boolean isAlphanumeric( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( !Character.isLetterOrDigit( str.charAt( i ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
Checks if the String contains only Unicode letters, digits
+ * or space (' ').
+ *
+ *
null will return false. An empty
+ * String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains letters, digits or space,
+ * and is non-null
+ */
+ public static boolean isAlphanumericSpace( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( ( !Character.isLetterOrDigit( str.charAt( i ) ) ) && ( str.charAt( i ) != ' ' ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ *
Checks if the String contains only Unicode digits.
+ *
+ *
null will return false.
+ * An empty String will return true.
+ *
+ * @param str the String to check
+ * @return true if only contains digits, and is non-null
+ */
+ public static boolean isNumeric( String str )
+ {
+ if ( str == null )
+ {
+ return false;
+ }
+ int sz = str.length();
+ for ( int i = 0; i < sz; i++ )
+ {
+ if ( !Character.isDigit( str.charAt( i ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Defaults
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Returns either the passed in Object as a String,
+ * or, if the Object is null, an empty
+ * String.
+ *
+ * @param obj the Object to check
+ * @return the passed in Object's toString, or blank if it was
+ * null
+ * @deprecated use {@code java.lang.Objects.toString()}
+ */
+ @Deprecated
+ @Nonnull public static String defaultString( Object obj )
+ {
+ return defaultString( obj, "" );
+ }
+
+ /**
+ *
Returns either the passed in Object as a String,
+ * or, if the Object is null, a passed
+ * in default String.
+ *
+ * @param obj the Object to check
+ * @param defaultString the default String to return if str is
+ * null
+ * @return the passed in string, or the default if it was
+ * null
+ * @deprecated use {@code java.lang.Objects.toString()}
+ */
+ @Deprecated
+ @Nonnull public static String defaultString( Object obj, @Nonnull String defaultString )
+ {
+ return ( obj == null ) ? defaultString : obj.toString();
+ }
+
+ // Reversing
+ //--------------------------------------------------------------------------
+
+ /**
+ *
Reverse a String.
+ *
+ *
null String returns null.
+ *
+ * @param str the String to reverse
+ * @return the reversed String
+ */
+ public static String reverse( String str )
+ {
+ if ( str == null )
+ {
+ return null;
+ }
+ return new StringBuffer( str ).reverse().toString();
+ }
+
+ /**
+ *
Reverses a String that is delimited by a specific character.
+ *
+ *
The Strings between the delimiters are not reversed.
+ * Thus java.lang.String becomes String.lang.java (if the delimiter
+ * is '.').
+ *
+ * @param str the String to reverse
+ * @param delimiter the delimiter to use
+ * @return the reversed String
+ */
+ @Nonnull public static String reverseDelimitedString( @Nonnull String str, String delimiter )
+ {
+ // could implement manually, but simple way is to reuse other,
+ // probably slower, methods.
+ String[] strs = split( str, delimiter );
+ reverseArray( strs );
+ return join( strs, delimiter );
+ }
+
+ /**
+ *
Reverses an array.
+ *
+ * @param array the array to reverse
+ */
+ private static void reverseArray( @Nonnull String... array )
+ {
+ int i = 0;
+ int j = array.length - 1;
+ String tmp;
+
+ while ( j > i )
+ {
+ tmp = array[j];
+ array[j] = array[i];
+ array[i] = tmp;
+ j--;
+ i++;
+ }
+ }
+
+ // Abbreviating
+ //--------------------------------------------------------------------------
+
+ /**
+ * Turn "Now is the time for all good men" into "Now is the time for..."
+ *
+ * Specifically:
+ *
+ * If str is less than max characters long, return it.
+ * Else abbreviate it to (substring(str, 0, max-3) + "...").
+ * If maxWidth is less than 3, throw an IllegalArgumentException.
+ * In no case will it return a string of length greater than maxWidth.
+ *
+ * @param s The string to be abbreviated.
+ * @param maxWidth maximum length of result string
+ * @return The abbreviated string.
+ */
+ @Nonnull public static String abbreviate( @Nonnull String s, int maxWidth )
+ {
+ return abbreviate( s, 0, maxWidth );
+ }
+
+ /**
+ * Turn "Now is the time for all good men" into "...is the time for..."
+ *
+ * Works like abbreviate(String, int), but allows you to specify a "left edge"
+ * offset. Note that this left edge is not necessarily going to be the leftmost
+ * character in the result, or the first
+ * character following the ellipses, but it will appear somewhere in the result.
+ * In no case will it return a string of length greater than maxWidth.
+ *
+ * @param s String to abbreviate.
+ * @param offset left edge of source string
+ * @param maxWidth maximum length of result string
+ * @return The abbreviated string.
+ */
+ @Nonnull public static String abbreviate( @Nonnull String s, int offset, int maxWidth )
+ {
+ if ( maxWidth < 4 )
+ {
+ throw new IllegalArgumentException( "Minimum abbreviation width is 4" );
+ }
+ if ( s.length() <= maxWidth )
+ {
+ return s;
+ }
+ if ( offset > s.length() )
+ {
+ offset = s.length();
+ }
+ if ( ( s.length() - offset ) < ( maxWidth - 3 ) )
+ {
+ offset = s.length() - ( maxWidth - 3 );
+ }
+ if ( offset <= 4 )
+ {
+ return s.substring( 0, maxWidth - 3 ) + "...";
+ }
+ if ( maxWidth < 7 )
+ {
+ throw new IllegalArgumentException( "Minimum abbreviation width with offset is 7" );
+ }
+ if ( ( offset + ( maxWidth - 3 ) ) < s.length() )
+ {
+ return "..." + abbreviate( s.substring( offset ), maxWidth - 3 );
+ }
+ return "..." + s.substring( s.length() - ( maxWidth - 3 ) );
+ }
+
+ // Difference
+ //--------------------------------------------------------------------------
+
+ /**
+ * Compare two strings, and return the portion where they differ.
+ * (More precisely, return the remainder of the second string,
+ * starting from where it's different from the first.)
+ *
+ * E.g. strdiff("i am a machine", "i am a robot") -> "robot"
+ *
+ * @param s1 The first string.
+ * @param s2 The second string.
+ * @return the portion of s2 where it differs from s1; returns the empty string ("") if they are equal
+ */
+ public static String difference( @Nonnull String s1, @Nonnull String s2 )
+ {
+ int at = differenceAt( s1, s2 );
+ if ( at == -1 )
+ {
+ return "";
+ }
+ return s2.substring( at );
+ }
+
+ /**
+ * Compare two strings, and return the index at which the strings begin to differ.
+ *
+ * E.g. strdiff("i am a machine", "i am a robot") -> 7
+ *
+ *
+ * @param s1 The first string.
+ * @param s2 The second string.
+ * @return the index where s2 and s1 begin to differ; -1 if they are equal
+ */
+ public static int differenceAt( @Nonnull String s1, @Nonnull String s2 )
+ {
+ int i;
+ for ( i = 0; ( i < s1.length() ) && ( i < s2.length() ); ++i )
+ {
+ if ( s1.charAt( i ) != s2.charAt( i ) )
+ {
+ break;
+ }
+ }
+ if ( ( i < s2.length() ) || ( i < s1.length() ) )
+ {
+ return i;
+ }
+ return -1;
+ }
+
+ /**
+ * Fill all 'variables' in the given text with the values from the map.
+ * Any text looking like '${key}' will get replaced by the value stored
+ * in the namespace map under the 'key'.
+ *
+ * @param text The text where replacements will be searched for.
+ * @param namespace The namespace which contains the replacements.
+ * @return the interpolated text.
+ */
+ public static String interpolate( String text, @Nonnull Map, ?> namespace )
+ {
+ for ( Map.Entry, ?> entry : namespace.entrySet() )
+ {
+ String key = entry.getKey().toString();
+
+ Object obj = entry.getValue();
+
+ if ( obj == null )
+ {
+ throw new NullPointerException( "The value of the key '" + key + "' is null." );
+ }
+
+ String value = obj.toString();
+
+ text = replace( text, "${" + key + "}", value );
+
+ if ( !key.contains( " " ) )
+ {
+ text = replace( text, "$" + key, value );
+ }
+ }
+ return text;
+ }
+
+ /**
+ * This is basically the inverse of {@link #addAndDeHump(String)}.
+ * It will remove the 'replaceThis' parameter and uppercase the next
+ * character afterwards.
+ *
+ * removeAndHump( "this-is-it", %quot;-" );
+ *
+ * will become 'ThisIsIt'.
+ *
+ * @param data The data.
+ * @param replaceThis The things which should be replaced.
+ * @return humped String
+ */
+ @Nonnull public static String removeAndHump( @Nonnull String data, @Nonnull String replaceThis )
+ {
+ String temp;
+
+ StringBuilder out = new StringBuilder();
+
+ temp = data;
+
+ StringTokenizer st = new StringTokenizer( temp, replaceThis );
+
+ while ( st.hasMoreTokens() )
+ {
+ String element = st.nextToken();
+
+ out.append( capitalizeFirstLetter( element ) );
+ }
+
+ return out.toString();
+ }
+
+ /**
+ * Converts the first character of the given String to uppercase.
+ * This method does not trim spaces!
+ *
+ * @param data the String to get capitalized
+ * @return data string with the first character transformed to uppercase
+ * @throws NullPointerException if data is null
+ * @throws IndexOutOfBoundsException if data is empty
+ */
+ @Nonnull public static String capitalizeFirstLetter( @Nonnull String data )
+ {
+ char firstChar = data.charAt( 0 );
+ char titleCase = Character.toTitleCase( firstChar );
+ if ( firstChar == titleCase )
+ {
+ return data;
+ }
+ StringBuilder result = new StringBuilder( data.length() );
+ result.append( titleCase );
+ result.append( data, 1, data.length() );
+ return result.toString();
+ }
+
+ /**
+ * Converts the first character of the given String to lowercase.
+ * This method does not trim spaces!
+ *
+ *
+ * @param data the String to get it's first character lower-cased.
+ * @return data string with the first character transformed to lowercase
+ * @throws NullPointerException if data is null
+ * @throws IndexOutOfBoundsException if data is empty
+ */
+ @Nonnull public static String lowercaseFirstLetter( @Nonnull String data )
+ {
+ char firstLetter = Character.toLowerCase( data.substring( 0, 1 ).charAt( 0 ) );
+
+ String restLetters = data.substring( 1 );
+
+ return firstLetter + restLetters;
+ }
+
+ /**
+ * Take the input string and un-camel-case it.
+ *
+ * 'ThisIsIt' will become 'this-is-it'.
+ *
+ * @param view the view
+ * @return deHumped String
+ */
+ @Nonnull public static String addAndDeHump( @Nonnull String view )
+ {
+ StringBuilder sb = new StringBuilder();
+
+ for ( int i = 0; i < view.length(); i++ )
+ {
+ if ( ( i != 0 ) && Character.isUpperCase( view.charAt( i ) ) )
+ {
+ sb.append( '-' );
+ }
+
+ sb.append( view.charAt( i ) );
+ }
+
+ return sb.toString().trim().toLowerCase( Locale.ENGLISH );
+ }
+
+ /**
+ *
Quote and escape a String with the given character, handling null.
Sets the shell or command-line interpretor for the detected operating system,
+ * and the shell arguments.
+ */
+ private void setDefaultShell()
+ {
+ //If this is windows set the shell to command.com or cmd.exe with correct arguments.
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ if ( Os.isFamily( Os.FAMILY_WIN9X ) )
+ {
+ setShell( new CommandShell() );
+ }
+ else
+ {
+ setShell( new CmdShell() );
+ }
+ }
+ else
+ {
+ setShell( new BourneShell() );
+ }
+ }
+
+ /**
+ * Creates an argument object.
+ *
+ *
Each commandline object has at most one instance of the
+ * argument class. This method calls
+ * this.createArgument(false).
+ *
+ * @return the argument object.
+ */
+ public Arg createArg()
+ {
+ return this.createArg( false );
+ }
+
+ /**
+ * Creates an argument object and adds it to our list of args.
+ *
+ *
Each commandline object has at most one instance of the
+ * argument class.
+ *
+ * @param insertAtStart if true, the argument is inserted at the
+ * beginning of the list of args, otherwise it is appended.
+ * @return The arguments.
+ */
+ public Arg createArg( boolean insertAtStart )
+ {
+ Arg argument = new Argument();
+ if ( insertAtStart )
+ {
+ arguments.add( 0, argument );
+ }
+ else
+ {
+ arguments.add( argument );
+ }
+ return argument;
+ }
+
+ /**
+ * Sets the executable to run.
+ * @param executable The executable.
+ */
+ public void setExecutable( String executable )
+ {
+ shell.setExecutable( executable );
+ }
+
+ /**
+ * @return The executable.
+ */
+ public String getExecutable()
+ {
+
+ return shell.getExecutable();
+ }
+
+ /**
+ * @param line The arguments.
+ */
+ public void addArguments( String... line )
+ {
+ for ( String aLine : line )
+ {
+ createArg().setValue( aLine );
+ }
+ }
+
+ /**
+ * Add an environment variable
+ * @param name The name of the environment variable.
+ * @param value The appropriate value.
+ */
+ public void addEnvironment( String name, String value )
+ {
+ //envVars.add( name + "=" + value );
+ envVars.put( name, value );
+ }
+
+ /**
+ * Add system environment variables
+ */
+ public void addSystemEnvironment()
+ {
+ Properties systemEnvVars = CommandLineUtils.getSystemEnvVars();
+
+ for ( Object o : systemEnvVars.keySet() )
+ {
+ String key = (String) o;
+ if ( !envVars.containsKey( key ) )
+ {
+ addEnvironment( key, systemEnvVars.getProperty( key ) );
+ }
+ }
+ }
+
+ /**
+ * Return the list of environment variables
+ * @return an array of all environment variables.
+ */
+ public String[] getEnvironmentVariables()
+ {
+ addSystemEnvironment();
+ String[] environmentVars = new String[envVars.size()];
+ int i = 0;
+ for ( String name : envVars.keySet() )
+ {
+ String value = envVars.get( name );
+ environmentVars[i] = name + "=" + value;
+ i++;
+ }
+ return environmentVars;
+ }
+
+ /**
+ * Returns the executable and all defined arguments.
+ * @return an array of all arguments incl. executable.
+ */
+ public String[] getCommandline()
+ {
+ final String[] args = getArguments();
+ String executable = getExecutable();
+
+ if ( executable == null )
+ {
+ return args;
+ }
+ final String[] result = new String[args.length + 1];
+ result[0] = executable;
+ System.arraycopy( args, 0, result, 1, args.length );
+ return result;
+ }
+
+ /**
+ * @return the shell, executable and all defined arguments without masking any arguments.
+ */
+ private String[] getShellCommandline()
+ {
+ return getShellCommandline( false ) ;
+ }
+
+ /**
+ * @param mask flag to mask any arguments (having his {@code mask} field to {@code true}).
+ * @return the shell, executable and all defined arguments with masking some arguments if
+ * {@code mask} parameter is on
+ */
+ private String[] getShellCommandline( boolean mask )
+ {
+ List shellCommandLine = getShell().getShellCommandLine( getArguments( mask ) );
+ return shellCommandLine.toArray( new String[shellCommandLine.size()] );
+ }
+
+ /**
+ * Returns all arguments defined by addLine,
+ * addValue or the argument object.
+ * @return an array of arguments.
+ */
+ public String[] getArguments()
+ {
+ return getArguments( false );
+ }
+
+ /**
+ * Returns all arguments defined by addLine,
+ * addValue or the argument object.
+ *
+ * @param mask flag to mask any arguments (having his {@code mask} field to {@code true}).
+ * @return an array of arguments.
+ */
+ public String[] getArguments( boolean mask )
+ {
+ List result = new ArrayList( arguments.size() * 2 );
+ for ( Arg argument : arguments )
+ {
+ Argument arg = (Argument) argument;
+ String[] s = arg.getParts();
+ if ( s != null )
+ {
+ if ( mask && ( arg.isMask() ) )
+ {
+ // should be a key-pair argument
+ if ( s.length > 0 )
+ {
+
+ // use a masked copy
+ String[] copy = new String[s.length];
+ Arrays.fill( copy, "*****" );
+ s = copy;
+ }
+ }
+ Collections.addAll( result, s );
+ }
+ }
+
+ return result.toArray( new String[result.size()] );
+ }
+
+ /** {@inheritDoc}
+ */
+ public String toString()
+ {
+ return StringUtils.join( getShellCommandline( true ), " " );
+ }
+
+
+ /** {@inheritDoc}
+ */
+ public Object clone()
+ {
+ throw new RuntimeException( "Do we ever clone a commandline?" );
+/* Commandline c = new Commandline( (Shell) shell.clone() );
+ c.addArguments( getArguments() );
+ return c;*/
+ }
+
+ /**
+ * Sets working directory.
+ * @param path The to be set as working directory.
+ */
+ public void setWorkingDirectory( String path )
+ {
+ shell.setWorkingDirectory( path );
+ }
+
+ /**
+ * Sets execution directory.
+ * @param workingDirectory The working directory.
+ */
+ public void setWorkingDirectory( File workingDirectory )
+ {
+ shell.setWorkingDirectory( workingDirectory );
+ }
+
+ /**
+ * @return The working directory.
+ */
+ public File getWorkingDirectory()
+ {
+ return shell.getWorkingDirectory();
+ }
+
+ /**
+ * Clear out the arguments but leave the executable in place for another operation.
+ */
+ public void clearArgs()
+ {
+ arguments.clear();
+ }
+
+ /**
+ * Executes the command.
+ * @return The process.
+ * @throws CommandLineException in case of errors.
+ */
+ public Process execute()
+ throws CommandLineException
+ {
+ Process process;
+
+ //addEnvironment( "MAVEN_TEST_ENVAR", "MAVEN_TEST_ENVAR_VALUE" );
+
+ String[] environment = getEnvironmentVariables();
+
+ File workingDir = shell.getWorkingDirectory();
+
+ try
+ {
+ if ( workingDir == null )
+ {
+ process = Runtime.getRuntime().exec( getShellCommandline(), environment );
+ }
+ else
+ {
+ if ( !workingDir.exists() )
+ {
+ throw new CommandLineException(
+ "Working directory \"" + workingDir.getPath() + "\" does not exist!" );
+ }
+ else if ( !workingDir.isDirectory() )
+ {
+ throw new CommandLineException(
+ "Path \"" + workingDir.getPath() + "\" does not specify a directory." );
+ }
+
+ process = Runtime.getRuntime().exec( getShellCommandline(), environment, workingDir );
+ }
+ }
+ catch ( IOException ex )
+ {
+ throw new CommandLineException( "Error while executing process.", ex );
+ }
+
+ return process;
+ }
+
+ /**
+ * Allows to set the shell to be used in this command line.
+ *
+ * @param shell the shell
+ */
+ void setShell( Shell shell )
+ {
+ this.shell = shell;
+ }
+
+ /**
+ * Get the shell to be used in this command line.
+ * @return the shell.
+ */
+ public Shell getShell()
+ {
+ return shell;
+ }
+
+ /**
+ *
+ */
+ public static class Argument
+ implements Arg
+ {
+ private String[] parts;
+
+ private boolean mask;
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setValue( String value )
+ {
+ if ( value != null )
+ {
+ parts = new String[]{ value };
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setLine( String line ) throws CommandLineException
+ {
+ if ( line == null )
+ {
+ return;
+ }
+ try
+ {
+ parts = CommandLineUtils.translateCommandline( line );
+ }
+ catch ( CommandLineException e )
+ {
+ System.err.println( "Error translating Commandline." );
+ throw( e );
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setFile( File value )
+ {
+ parts = new String[]{ value.getAbsolutePath() };
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setMask( boolean mask )
+ {
+ this.mask = mask;
+ }
+
+ /**
+ * @return The parts.
+ */
+ private String[] getParts()
+ {
+ return parts;
+ }
+
+ /**
+ * @return true/false
+ */
+ public boolean isMask()
+ {
+ return mask;
+ }
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/DefaultConsumer.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/DefaultConsumer.java
new file mode 100644
index 000000000..59d99719d
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/DefaultConsumer.java
@@ -0,0 +1,44 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.io.IOException;
+
+/**
+ * @author Emmanuel Venisse
+ */
+public class DefaultConsumer
+ implements StreamConsumer
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void consumeLine( String line ) throws IOException
+ {
+ System.out.println( line );
+ if ( System.out.checkError() )
+ {
+ throw new IOException( String.format( "Failure writing line '%s' to stdout.", line ) );
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/ShutdownHookUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/ShutdownHookUtils.java
new file mode 100644
index 000000000..d1ea56868
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/ShutdownHookUtils.java
@@ -0,0 +1,73 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.security.AccessControlException;
+
+/**
+ * A shutdown hook that does not throw any exceptions upon container startup/shutdown or security manager
+ * restrictions.
+ *
+ * Incorrect usage of the hook itself may still throw an exception.
+ *
+ * @author Kristian Rosenvold
+ */
+public class ShutdownHookUtils
+{
+
+ /**
+ * @param hook The thread hook.
+ */
+ public static void addShutDownHook( Thread hook )
+ {
+ try
+ {
+ Runtime.getRuntime().addShutdownHook( hook );
+ }
+ catch ( IllegalStateException ignore )
+ {
+ // ignore
+ }
+ catch ( AccessControlException ignore )
+ {
+ // ignore
+ }
+ }
+
+ /**
+ * @param hook The hook which should be removed.
+ */
+ public static void removeShutdownHook( Thread hook )
+ {
+ try
+ {
+ Runtime.getRuntime().removeShutdownHook( hook );
+ }
+ catch ( IllegalStateException ignore )
+ {
+ // ignore
+ }
+ catch ( AccessControlException ignore )
+ {
+ // ignore
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamConsumer.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamConsumer.java
new file mode 100644
index 000000000..0cde961e1
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamConsumer.java
@@ -0,0 +1,43 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.io.IOException;
+
+/**
+ * Works in concert with the StreamPumper class to
+ * allow implementations to gain access to the lines being
+ * "Pumped".
+ *
+ * Please note that implementations of this interface can be expected to be
+ * called from arbitrary threads and must therefore be threadsafe.
+ *
+ * @author Florin Vancea
+ * @author Paul Julius
+ */
+public interface StreamConsumer
+{
+ /**
+ * Called when the StreamPumper pumps a line from the Stream.
+ * @param line The line to be consumed.
+ * @throws IOException if consuming {@code line} fails.
+ */
+ void consumeLine( String line ) throws IOException;
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamFeeder.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamFeeder.java
new file mode 100644
index 000000000..6f6723c40
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamFeeder.java
@@ -0,0 +1,151 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Read from an InputStream and write the output to an OutputStream.
+ *
+ * @author Trygve Laugstøl
+ */
+class StreamFeeder
+ extends AbstractStreamHandler
+{
+
+ private final AtomicReference input;
+
+ private final AtomicReference output;
+
+ private volatile Throwable exception;
+
+ /**
+ * Create a new StreamFeeder
+ *
+ * @param input Stream to read from
+ * @param output Stream to write to
+ */
+ StreamFeeder( InputStream input, OutputStream output )
+ {
+ super();
+ this.input = new AtomicReference( input );
+ this.output = new AtomicReference( output );
+ }
+
+ @Override
+ public void run()
+ {
+ try
+ {
+ feed();
+ }
+ catch ( Throwable e )
+ {
+ // Catch everything so the streams will be closed and flagged as done.
+ if ( this.exception != null )
+ {
+ this.exception = e;
+ }
+ }
+ finally
+ {
+ close();
+
+ synchronized ( this )
+ {
+ notifyAll();
+ }
+ }
+ }
+
+ public void close()
+ {
+ setDone();
+ final InputStream is = input.getAndSet( null );
+ if ( is != null )
+ {
+ try
+ {
+ is.close();
+ }
+ catch ( IOException ex )
+ {
+ if ( this.exception != null )
+ {
+ this.exception = ex;
+ }
+ }
+ }
+
+ final OutputStream os = output.getAndSet( null );
+ if ( os != null )
+ {
+ try
+ {
+ os.close();
+ }
+ catch ( IOException ex )
+ {
+ if ( this.exception != null )
+ {
+ this.exception = ex;
+ }
+ }
+ }
+ }
+
+ /**
+ * @since 3.2.0
+ */
+ public Throwable getException()
+ {
+ return this.exception;
+ }
+
+ @SuppressWarnings( "checkstyle:innerassignment" )
+ private void feed()
+ throws IOException
+ {
+ InputStream is = input.get();
+ OutputStream os = output.get();
+ boolean flush = false;
+
+ if ( is != null && os != null )
+ {
+ for ( int data; !isDone() && ( data = is.read() ) != -1; )
+ {
+ if ( !isDisabled() )
+ {
+ os.write( data );
+ flush = true;
+ }
+ }
+
+ if ( flush )
+ {
+ os.flush();
+ }
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamPumper.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamPumper.java
new file mode 100644
index 000000000..980bfb61a
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/StreamPumper.java
@@ -0,0 +1,161 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.Charset;
+import javax.annotation.Nullable;
+
+/**
+ * Class to pump the error stream during Process's runtime. Copied from the Ant built-in task.
+ *
+ * @author Florin Vancea
+ * @author Paul Julius
+ */
+public class StreamPumper
+ extends AbstractStreamHandler
+{
+ private final BufferedReader in;
+
+ private final StreamConsumer consumer;
+
+ private volatile Exception exception = null;
+
+ private static final int SIZE = 1024;
+
+ /**
+ * @param in {@link InputStream}
+ * @param consumer {@link StreamConsumer}
+ */
+ public StreamPumper( InputStream in, StreamConsumer consumer )
+ {
+ this( new InputStreamReader( in ), consumer );
+ }
+
+ /**
+ * @param in {@link InputStream}
+ * @param consumer {@link StreamConsumer}
+ * @param charset {@link Charset}
+ */
+ public StreamPumper( InputStream in, StreamConsumer consumer, @Nullable Charset charset )
+ {
+ this( null == charset ? new InputStreamReader( in ) : new InputStreamReader( in, charset ), consumer );
+ }
+
+ /**
+ * @param in {@link Reader}
+ * @param consumer {@link StreamConsumer}
+ */
+ private StreamPumper( Reader in, StreamConsumer consumer )
+ {
+ super();
+ this.in = new BufferedReader( in, SIZE );
+ this.consumer = consumer;
+ }
+
+ /** run it. */
+ public void run()
+ {
+ try
+ {
+ for ( String line = in.readLine(); line != null; line = in.readLine() )
+ {
+ try
+ {
+ if ( exception == null )
+ {
+ consumeLine( line );
+ }
+ }
+ catch ( Exception t )
+ {
+ exception = t;
+ }
+ }
+ }
+ catch ( IOException e )
+ {
+ exception = e;
+ }
+ finally
+ {
+ try
+ {
+ in.close();
+ }
+ catch ( final IOException e2 )
+ {
+ if ( this.exception == null )
+ {
+ this.exception = e2;
+ }
+ }
+
+ synchronized ( this )
+ {
+ setDone();
+
+ this.notifyAll();
+ }
+ }
+ }
+
+ /**
+ * flush.
+ *
+ * @deprecated As of 3.2.0, removed without replacement.
+ */
+ @Deprecated
+ public void flush()
+ {
+ // Nothing to flush.
+ }
+
+ /**
+ * Close it.
+ *
+ * @deprecated As of 3.2.0, removed without replacement.
+ */
+ @Deprecated
+ public void close()
+ {
+ // Nothing to close.
+ }
+
+ /**
+ * @return {@link Exception}
+ */
+ public Exception getException()
+ {
+ return exception;
+ }
+
+ private void consumeLine( String line ) throws IOException
+ {
+ if ( consumer != null && !isDisabled() )
+ {
+ consumer.consumeLine( line );
+ }
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/WriterStreamConsumer.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/WriterStreamConsumer.java
new file mode 100644
index 000000000..b93ccb7ce
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/WriterStreamConsumer.java
@@ -0,0 +1,56 @@
+package org.apache.maven.shared.utils.cli;
+
+/*
+ * 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 java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * @author Jason van Zyl
+ *
+ */
+public class WriterStreamConsumer
+ implements StreamConsumer
+{
+
+ private final BufferedWriter writer;
+
+ /**
+ * @param writer {@link Writer}
+ */
+ public WriterStreamConsumer( Writer writer )
+ {
+ super();
+ this.writer = new BufferedWriter( writer );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void consumeLine( String line ) throws IOException
+ {
+ this.writer.append( line );
+ this.writer.newLine();
+ this.writer.flush();
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaTool.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaTool.java
new file mode 100644
index 000000000..22826802e
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaTool.java
@@ -0,0 +1,360 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.apache.maven.shared.utils.Os;
+import org.apache.maven.shared.utils.StringUtils;
+import org.apache.maven.shared.utils.cli.CommandLineException;
+import org.apache.maven.shared.utils.cli.CommandLineUtils;
+import org.apache.maven.shared.utils.cli.Commandline;
+import org.apache.maven.shared.utils.cli.StreamConsumer;
+import org.codehaus.plexus.logging.AbstractLogEnabled;
+
+import java.io.File;
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Map;
+
+/**
+ * Abstract implementation of a {@link JavaTool}.
+ *
+ * @author Tony Chemit
+ * @since 0.5
+ * @param
+ */
+public abstract class AbstractJavaTool
+ extends AbstractLogEnabled
+ implements JavaTool
+{
+
+ /**
+ * The java tool name to find out in the jdk.
+ */
+ private final String javaToolName;
+
+ /**
+ * The location of the java tool executable file.
+ */
+ private String javaToolFile;
+
+ /**
+ * Optional toolChain used to find java tool executable file.
+ */
+ private Object toolchain;
+
+ /**
+ * @param javaToolName The name of the java tool.
+ */
+ protected AbstractJavaTool( String javaToolName )
+ {
+ this.javaToolName = javaToolName;
+ }
+
+ /**
+ * Create the command line object given the request.
+ *
+ * @param request User request on the java tool
+ * @param javaToolFileLocation Location of the java tool file to use
+ * @return the command line
+ * @throws JavaToolException if could not create the command line from the request
+ */
+ protected abstract Commandline createCommandLine( Request request, String javaToolFileLocation )
+ throws JavaToolException;
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getJavaToolName()
+ {
+ return javaToolName;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setToolchain( Object toolchain )
+ {
+ this.toolchain = toolchain;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public JavaToolResult execute( Request request )
+ throws JavaToolException
+ {
+
+ if ( javaToolFile == null )
+ {
+
+ // find the java tool file to use
+ try
+ {
+ javaToolFile = findJavaToolExecutable();
+ }
+ catch ( Exception e )
+ {
+ throw new JavaToolException( "Error finding " + javaToolName + " executable. Reason: " + e.getMessage(),
+ e );
+ }
+ }
+
+ // creates the command line from the given request
+ Commandline cli = createCommandLine( request, javaToolFile );
+
+ // execute it
+ JavaToolResult result = executeCommandLine( cli, request );
+
+ // return result
+ return result;
+ }
+
+ /**
+ * @return {@link InputStream}
+ */
+ protected InputStream createSystemInputStream()
+ {
+ InputStream systemIn = new InputStream()
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ public int read()
+ {
+ return -1;
+ }
+
+ };
+ return systemIn;
+ }
+
+ /**
+ * @param cli {@link Commandline}
+ * @param request The request.
+ * @return {@link JavaToolRequest}
+ */
+ protected JavaToolResult executeCommandLine( Commandline cli, Request request )
+ {
+ if ( getLogger().isDebugEnabled() )
+ {
+ getLogger().debug( "Executing: " + cli );
+ }
+
+ JavaToolResult result = createResult();
+
+ result.setCommandline( cli );
+
+ InputStream systemIn = createSystemInputStream();
+
+ StreamConsumer systemOut = createSystemOutStreamConsumer( request );
+
+ StreamConsumer systemErr = createSystemErrorStreamConsumer( request );
+
+ try
+ {
+ int resultCode = CommandLineUtils.executeCommandLine( cli, systemIn, systemOut, systemErr );
+
+ result.setExitCode( resultCode );
+ }
+ catch ( CommandLineException e )
+ {
+ result.setExecutionException( e );
+ }
+
+ return result;
+ }
+
+ /**
+ * @param request The request.
+ * @return {@link StreamConsumer}
+ */
+ protected StreamConsumer createSystemErrorStreamConsumer( Request request )
+ {
+ StreamConsumer systemErr = request.getSystemErrorStreamConsumer();
+
+ if ( systemErr == null )
+ {
+ systemErr = new StreamConsumer()
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void consumeLine( final String line )
+ {
+ getLogger().warn( line );
+ }
+
+ };
+ }
+ return systemErr;
+ }
+
+ /**
+ * @param request The request.
+ * @return {@link StreamConsumer}
+ */
+ protected StreamConsumer createSystemOutStreamConsumer( Request request )
+ {
+ StreamConsumer systemOut = request.getSystemOutStreamConsumer();
+
+ if ( systemOut == null )
+ {
+
+ systemOut = new StreamConsumer()
+ {
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void consumeLine( final String line )
+ {
+ getLogger().info( line );
+
+ }
+
+ };
+ }
+ return systemOut;
+ }
+
+ /**
+ * @return The JavaToolResult.
+ */
+ protected JavaToolResult createResult()
+ {
+ return new JavaToolResult();
+ }
+
+ /**
+ * @return The location of the java tool executable.
+ */
+ protected String findJavaToolExecutable()
+ {
+ String executable = null;
+
+ if ( toolchain != null )
+ {
+ executable = findToolchainExecutable();
+ }
+
+ String command = javaToolName + ( Os.isFamily( Os.FAMILY_WINDOWS ) ? ".exe" : "" );
+
+ if ( executable == null )
+ {
+ executable = findExecutable( command, System.getProperty( "java.home" ), "../bin", "bin", "../sh" );
+ }
+
+ if ( executable == null )
+ {
+
+ Map env = System.getenv();
+
+ String[] variables = { "JDK_HOME", "JAVA_HOME" };
+
+ for ( String variable : variables )
+ {
+ executable = findExecutable( command, env.get( variable ), "bin", "sh" );
+ if ( executable != null )
+ {
+ break;
+ }
+ }
+ }
+
+ if ( executable == null )
+ {
+ executable = command;
+ }
+
+ return executable;
+ }
+
+ /**
+ * Run toolchain.findTool( javaToolName ); through reflection to avoid compile dependency on
+ * Maven core.
+ */
+ private String findToolchainExecutable()
+ {
+ try
+ {
+ Method m = toolchain.getClass().getMethod( "findTool", String.class );
+ return (String) m.invoke( toolchain, javaToolName );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ // should not happen if toolchain is really a Toolchain object
+ getLogger().warn( "unexpected NoSuchMethodException", e );
+ }
+ catch ( SecurityException e )
+ {
+ // should not happen
+ getLogger().warn( "unexpected SecurityException", e );
+ }
+ catch ( IllegalAccessException e )
+ {
+ // should not happen
+ getLogger().warn( "unexpected IllegalAccessException", e );
+ }
+ catch ( IllegalArgumentException e )
+ {
+ // should not happen: parameter is the right type
+ getLogger().warn( "unexpected IllegalArgumentException", e );
+ }
+ catch ( InvocationTargetException e )
+ {
+ // not expected...
+ getLogger().warn( "unexpected InvocationTargetException", e );
+ }
+ return null;
+ }
+
+ /**
+ * Finds the specified command in any of the given sub directories of the specified JDK/JRE home directory.
+ *
+ * @param command The command to find, must not be null.
+ * @param homeDir The home directory to search in, may be null.
+ * @param subDirs The sub directories of the home directory to search in, must not be null.
+ * @return The (absolute) path to the command if found, null otherwise.
+ */
+ private String findExecutable( String command, String homeDir, String... subDirs )
+ {
+ String result = null;
+ if ( StringUtils.isNotEmpty( homeDir ) )
+ {
+ for ( String subDir : subDirs )
+ {
+ File file = new File( new File( homeDir, subDir ), command );
+
+ if ( file.isFile() )
+ {
+ result = file.getAbsolutePath();
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaToolRequest.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaToolRequest.java
new file mode 100644
index 000000000..6e18178ab
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/AbstractJavaToolRequest.java
@@ -0,0 +1,75 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.apache.maven.shared.utils.cli.StreamConsumer;
+
+/**
+ * Abstract implementation of a {@link JavaToolRequest}.
+ *
+ * @author Tony Chemit
+ * @since 0.5
+ */
+public class AbstractJavaToolRequest
+ implements JavaToolRequest
+{
+
+ /**
+ * Optional system out stream consumer used by the commandline execution.
+ */
+ private StreamConsumer systemOutStreamConsumer;
+
+ /**
+ * Optional system error stream consumer used by the commandline execution.
+ */
+ private StreamConsumer systemErrorStreamConsumer;
+
+ /**
+ * {@inheritDoc}
+ */
+ public StreamConsumer getSystemOutStreamConsumer()
+ {
+ return systemOutStreamConsumer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public StreamConsumer getSystemErrorStreamConsumer()
+ {
+ return systemErrorStreamConsumer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setSystemOutStreamConsumer( StreamConsumer systemOutStreamConsumer )
+ {
+ this.systemOutStreamConsumer = systemOutStreamConsumer;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void setSystemErrorStreamConsumer( StreamConsumer systemErrorStreamConsumer )
+ {
+ this.systemErrorStreamConsumer = systemErrorStreamConsumer;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaTool.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaTool.java
new file mode 100644
index 000000000..b4bc3a9a6
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaTool.java
@@ -0,0 +1,72 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.
+ */
+
+/**
+ * Describes a java tool, means a executable available in the jdk.
+ *
+ * The name of the tool ({@link #getJavaToolName()}) reflects the name of the executable that should exists as an
+ * executable in the jdk, like {@code jarsigner, keytool, javadoc, ...}.
+ *
+ * An abstract implementation of the {@link JavaTool} named {@link AbstractJavaTool} use the command line API to execute
+ * any user requests of this tool.
+ *
+ * @author Tony Chemit
+ * @since 0.5
+ * @param
+ */
+public interface JavaTool
+{
+
+ /**
+ * Return the name of the java tool. This is exactly the name (without his extension) of the executable to
+ * find in the {@code jdk/bin} directory.
+ *
+ * For example: {@code jarsigner, keytool, javadoc, ...}
+ *
+ * @return the name of the java tool.
+ */
+ String getJavaToolName();
+
+ /**
+ * Set an optional tool chain to find out the java tool executable location.
+ *
+ * @param toolchain optional tool chain to find out the java tool executable location.
+ * To avoid direct dependency on Maven core, this parameter is an Object that will be
+ * used as Toolchain through reflection
+ */
+ void setToolchain( Object toolchain );
+
+ /**
+ * Execute the input request and then returns the result of the execution.
+ *
+ * If could not create the java tool invocation, a {@link JavaToolException} will be thrown.
+ *
+ * If execution fails, then the result will have a none-zero {@link JavaToolResult#getExitCode()} and his
+ * {@link JavaToolResult#getExecutionException()} will be filled with the error, otherwise the exist code will be
+ * zero.
+ *
+ * @param request the request to perform
+ * @return the result of the tool execution
+ * @throws JavaToolException if could not create the java tool invocation
+ */
+ JavaToolResult execute( Request request )
+ throws JavaToolException;
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolException.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolException.java
new file mode 100644
index 000000000..fa10b33bb
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolException.java
@@ -0,0 +1,55 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.
+ */
+
+/**
+ * Signals an error during the construction of the command line used to invoke java tool, e.g. illegal invocation
+ * arguments.
+ *
+ * This should not be confused with a failure of the invoked java tool build itself which will be reported by means of a
+ * non-zero exit code.
+ *
+ * @author Tony Chemit
+ *
+ * @see JavaToolResult#getExitCode()
+ * @since 0.5
+ */
+public class JavaToolException
+ extends Exception
+{
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * @param message The message of the exception.
+ */
+ public JavaToolException( String message )
+ {
+ super( message );
+ }
+
+ /**
+ * @param message The message of the exception.
+ * @param cause The cause of the exception.
+ */
+ public JavaToolException( String message, Throwable cause )
+ {
+ super( message, cause );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolRequest.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolRequest.java
new file mode 100644
index 000000000..7227774c0
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolRequest.java
@@ -0,0 +1,66 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.apache.maven.shared.utils.cli.StreamConsumer;
+
+/**
+ * Specifies the minimum parameters used to control a {@link JavaTool} invocation.
+ *
+ * @author Tony Chemit
+ * @since 0.5
+ */
+public interface JavaToolRequest
+{
+
+ /**
+ * Gets the value of the {@code systemOutStreamConsumer} field.
+ *
+ * This option field if filled is used by the commandline tool to consume system ouput stream of the jarsigner
+ * command.
+ *
+ * @return the value of the {@code systemOutStreamConsumer} field.
+ */
+ StreamConsumer getSystemOutStreamConsumer();
+
+ /**
+ * Gets the value of the {@code systemErrorStreamConsumer} field.
+ *
+ * This option field if filled is used by the commandline tool to consume system error stream of the jarsigner
+ * command.
+ *
+ * @return the value of the {@code systemErrorStreamConsumer} field.
+ */
+ StreamConsumer getSystemErrorStreamConsumer();
+
+ /**
+ * Sets the new given value to the field {@code systemOutStreamConsumer} of the request.
+ *
+ * @param systemOutStreamConsumer the new value of the field {@code systemOutStreamConsumer}.
+ */
+ void setSystemOutStreamConsumer( StreamConsumer systemOutStreamConsumer );
+
+ /**
+ * Sets the new given value to the field {@code systemErrorStreamConsumer} of the request.
+ *
+ * @param systemErrorStreamConsumer the new value of the field {@code systemErrorStreamConsumer}.
+ */
+ void setSystemErrorStreamConsumer( StreamConsumer systemErrorStreamConsumer );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolResult.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolResult.java
new file mode 100644
index 000000000..f3a6a77ec
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/javatool/JavaToolResult.java
@@ -0,0 +1,110 @@
+package org.apache.maven.shared.utils.cli.javatool;
+
+/*
+ * 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.apache.maven.shared.utils.cli.CommandLineException;
+import org.apache.maven.shared.utils.cli.Commandline;
+
+/**
+ * Describes the result of a {@link JavaTool} invocation.
+ *
+ * @author Tony Chemit
+ * @since 0.5
+ */
+public class JavaToolResult
+{
+ /**
+ * The exception that prevented to execute the command line, will be null if jarSigner could be
+ * successfully started.
+ */
+ private CommandLineException executionException;
+
+ /**
+ * The exit code reported by the Maven invocation.
+ */
+ private int exitCode = Integer.MIN_VALUE;
+
+ /**
+ * The command line used to obtain this result.
+ */
+ private Commandline commandline;
+
+ /**
+ * Gets the exit code from the tool invocation. A non-zero value indicates a build failure. Note:
+ * This value is undefined if {@link #getExecutionException()} reports an exception.
+ *
+ * @return The exit code from the tool invocation.
+ */
+ public int getExitCode()
+ {
+ return exitCode;
+ }
+
+ /**
+ * Gets the command line used.
+ *
+ * @return The command line used
+ */
+ public Commandline getCommandline()
+ {
+ return commandline;
+ }
+
+ /**
+ * Gets the exception that possibly occurred during the execution of the command line.
+ *
+ * @return The exception that prevented to invoke tool or null if the command line was successfully
+ * processed by the operating system.
+ */
+ public CommandLineException getExecutionException()
+ {
+ return executionException;
+ }
+
+ /**
+ * Sets the exit code reported by the tool invocation.
+ *
+ * @param exitCode The exit code reported by the tool invocation.
+ */
+ public void setExitCode( int exitCode )
+ {
+ this.exitCode = exitCode;
+ }
+
+ /**
+ * Sets the exception that prevented to execute the command line.
+ *
+ * @param executionException The exception that prevented to execute the command line, may be null.
+ */
+ public void setExecutionException( CommandLineException executionException )
+ {
+ this.executionException = executionException;
+ }
+
+ /**
+ * Set the commandline used to obtain this result.
+ *
+ * @param commandline the commandline used to obtain this result
+ */
+ public void setCommandline( Commandline commandline )
+ {
+ this.commandline = commandline;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java
new file mode 100644
index 000000000..e3af66512
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/BourneShell.java
@@ -0,0 +1,138 @@
+package org.apache.maven.shared.utils.cli.shell;
+
+/*
+ * 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 java.util.ArrayList;
+import java.util.List;
+import org.apache.maven.shared.utils.Os;
+
+/**
+ * @author Jason van Zyl
+ */
+public class BourneShell
+ extends Shell
+{
+
+ /**
+ * Create instance of BourneShell.
+ */
+ public BourneShell()
+ {
+ setUnconditionalQuoting( true );
+ setShellCommand( "/bin/sh" );
+ setArgumentQuoteDelimiter( '\'' );
+ setExecutableQuoteDelimiter( '\'' );
+ setSingleQuotedArgumentEscaped( true );
+ setSingleQuotedExecutableEscaped( false );
+ setQuotedExecutableEnabled( true );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public String getExecutable()
+ {
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ return super.getExecutable();
+ }
+
+ return quoteOneItem( super.getExecutable(), true );
+ }
+
+ /** {@inheritDoc} */
+ public List getShellArgsList()
+ {
+ List shellArgs = new ArrayList();
+ List existingShellArgs = super.getShellArgsList();
+
+ if ( ( existingShellArgs != null ) && !existingShellArgs.isEmpty() )
+ {
+ shellArgs.addAll( existingShellArgs );
+ }
+
+ shellArgs.add( "-c" );
+
+ return shellArgs;
+ }
+
+ /** {@inheritDoc} */
+ public String[] getShellArgs()
+ {
+ String[] shellArgs = super.getShellArgs();
+ if ( shellArgs == null )
+ {
+ shellArgs = new String[0];
+ }
+
+ if ( ( shellArgs.length > 0 ) && !shellArgs[shellArgs.length - 1].equals( "-c" ) )
+ {
+ String[] newArgs = new String[shellArgs.length + 1];
+
+ System.arraycopy( shellArgs, 0, newArgs, 0, shellArgs.length );
+ newArgs[shellArgs.length] = "-c";
+
+ shellArgs = newArgs;
+ }
+
+ return shellArgs;
+ }
+
+ /** {@inheritDoc} */
+ protected String getExecutionPreamble()
+ {
+ if ( getWorkingDirectoryAsString() == null )
+ {
+ return null;
+ }
+
+ String dir = getWorkingDirectoryAsString();
+
+ return "cd " + quoteOneItem( dir, false ) + " && ";
+ }
+
+ /**
+ *
+ *
+ * @param path not null path.
+ * @return the path unified correctly for the Bourne shell.
+ */
+ protected String quoteOneItem( String path, boolean isExecutable )
+ {
+ if ( path == null )
+ {
+ return null;
+ }
+
+ return "'" + path.replace( "'", "'\"'\"'" ) + "'";
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CmdShell.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CmdShell.java
new file mode 100644
index 000000000..04aa6de1d
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CmdShell.java
@@ -0,0 +1,97 @@
+package org.apache.maven.shared.utils.cli.shell;
+
+/*
+ * 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 java.util.Arrays;
+import java.util.List;
+
+/**
+ * Implementation to call the CMD Shell present on Windows NT, 2000, XP, 7, 8, and 10.
+ *
+ * @author Carlos Sanchez
+ *
+ */
+public class CmdShell
+ extends Shell
+{
+ /**
+ * Create an instance of CmdShell.
+ */
+ public CmdShell()
+ {
+ setShellCommand( "cmd.exe" );
+ setQuotedExecutableEnabled( true );
+ setShellArgs( new String[]{ "/X", "/C" } );
+ }
+
+ /**
+ *
+ * Specific implementation that quotes all the command line.
+ *
+ *
+ * Workaround for http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6468220
+ *
+ *
+ * From cmd.exe /? output:
+ *
+ *
+ *
+ * If /C or /K is specified, then the remainder of the command line after
+ * the switch is processed as a command line, where the following logic is
+ * used to process quote (") characters:
+ *
+ * 1. If all of the following conditions are met, then quote characters
+ * on the command line are preserved:
+ *
+ * - no /S switch
+ * - exactly two quote characters
+ * - no special characters between the two quote characters,
+ * where special is one of: &<>()@ˆ|
+ * - there are one or more whitespace characters between the
+ * the two quote characters
+ * - the string between the two quote characters is the name
+ * of an executable file.
+ *
+ * 2. Otherwise, old behavior is to see if the first character is
+ * a quote character and if so, strip the leading character and
+ * remove the last quote character on the command line, preserving
+ * any text after the last quote character.
+ *
+ *
+ *
+ * Always quoting the entire command line, regardless of these conditions
+ * appears to make Windows processes invoke successfully.
+ *
+ *
+ * @param executable The executable.
+ * @param arguments The arguments for the executable.
+ * @return The resulting command line.
+ */
+ public List getCommandLine( String executable, String... arguments )
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.append( '"' );
+ sb.append( super.getCommandLine( executable, arguments ).get( 0 ) );
+ sb.append( '"' );
+
+ return Arrays.asList( sb.toString() );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CommandShell.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CommandShell.java
new file mode 100644
index 000000000..ba85633bf
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/CommandShell.java
@@ -0,0 +1,42 @@
+package org.apache.maven.shared.utils.cli.shell;
+
+/*
+ * 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.
+ */
+
+
+/**
+ * Implementation to call the Command.com Shell present on Windows 95, 98 and Me
+ *
+ * @author Carlos Sanchez
+ * @deprecated Windows ME is long dead. Update to Windows 10 and use {@link CmdShell}.
+ */
+@Deprecated
+public class CommandShell
+ extends Shell
+{
+ /**
+ * Create an instance.
+ */
+ public CommandShell()
+ {
+ setShellCommand( "command.com" );
+ setShellArgs( new String[]{ "/C" } );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java
new file mode 100644
index 000000000..02681086f
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/cli/shell/Shell.java
@@ -0,0 +1,418 @@
+package org.apache.maven.shared.utils.cli.shell;
+
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import org.apache.maven.shared.utils.StringUtils;
+
+/**
+ * Class that abstracts the Shell functionality,
+ * with subclasses for shells that behave particularly, like
+ *
+ *
+ *
command.com
+ *
cmd.exe
+ *
+ *
+ * @author Carlos Sanchez
+ *
+ */
+public class Shell
+ implements Cloneable
+{
+ private static final char[] DEFAULT_QUOTING_TRIGGER_CHARS = { ' ' };
+
+ private String shellCommand;
+
+ private final List shellArgs = new ArrayList();
+
+ private boolean quotedArgumentsEnabled = true;
+
+ private boolean unconditionalQuoting = false;
+
+ private String executable;
+
+ private String workingDir;
+
+ private boolean quotedExecutableEnabled = true;
+
+ private boolean singleQuotedArgumentEscaped = false;
+
+ private boolean singleQuotedExecutableEscaped = false;
+
+ private char argQuoteDelimiter = '\"';
+
+ private char exeQuoteDelimiter = '\"';
+
+ /**
+ * Set the command to execute the shell (e.g. COMMAND.COM, /bin/bash,...).
+ *
+ * @param shellCommand the command
+ */
+ void setShellCommand( String shellCommand )
+ {
+ this.shellCommand = shellCommand;
+ }
+
+ /**
+ * Get the command to execute the shell.
+ *
+ * @return the command
+ */
+ String getShellCommand()
+ {
+ return shellCommand;
+ }
+
+ /**
+ * Set the shell arguments when calling a command line (not the executable arguments)
+ * (e.g. /X /C for CMD.EXE).
+ *
+ * @param shellArgs the arguments to the shell
+ */
+ void setShellArgs( String[] shellArgs )
+ {
+ this.shellArgs.clear();
+ this.shellArgs.addAll( Arrays.asList( shellArgs ) );
+ }
+
+ /**
+ * Get the shell arguments
+ *
+ * @return the arguments
+ */
+ String[] getShellArgs()
+ {
+ if ( shellArgs.isEmpty() )
+ {
+ return null;
+ }
+ else
+ {
+ return shellArgs.toArray( new String[0] );
+ }
+ }
+
+ protected String quoteOneItem( String inputString, boolean isExecutable )
+ {
+ char[] escapeChars = getEscapeChars( isSingleQuotedExecutableEscaped(), isDoubleQuotedExecutableEscaped() );
+ return StringUtils.quoteAndEscape(
+ inputString,
+ isExecutable ? getExecutableQuoteDelimiter() : getArgumentQuoteDelimiter(),
+ escapeChars,
+ getQuotingTriggerChars(),
+ '\\',
+ unconditionalQuoting
+ );
+ }
+
+ /**
+ * Get the command line for the provided executable and arguments in this shell
+ *
+ * @param executableParameter executable that the shell has to call
+ * @param argumentsParameter arguments for the executable, not the shell
+ * @return list with one String object with executable and arguments quoted as needed
+ */
+ List getCommandLine( String executableParameter, String... argumentsParameter )
+ {
+ return getRawCommandLine( executableParameter, argumentsParameter );
+ }
+
+ /**
+ * @param executableParameter Executable
+ * @param argumentsParameter the arguments for the executable
+ * @return the list on command line
+ */
+ List getRawCommandLine( String executableParameter, String... argumentsParameter )
+ {
+ List commandLine = new ArrayList<>();
+ StringBuilder sb = new StringBuilder();
+
+ if ( executableParameter != null )
+ {
+ String preamble = getExecutionPreamble();
+ if ( preamble != null )
+ {
+ sb.append( preamble );
+ }
+
+ if ( isQuotedExecutableEnabled() )
+ {
+ sb.append( quoteOneItem( executableParameter, true ) );
+ }
+ else
+ {
+ sb.append( executableParameter );
+ }
+ }
+ for ( String argument : argumentsParameter )
+ {
+ if ( sb.length() > 0 )
+ {
+ sb.append( ' ' );
+ }
+
+ if ( isQuotedArgumentsEnabled() )
+ {
+ sb.append( quoteOneItem( argument, false ) );
+ }
+ else
+ {
+ sb.append( argument );
+ }
+ }
+
+ commandLine.add( sb.toString() );
+
+ return commandLine;
+ }
+
+ char[] getQuotingTriggerChars()
+ {
+ return DEFAULT_QUOTING_TRIGGER_CHARS;
+ }
+
+ String getExecutionPreamble()
+ {
+ return null;
+ }
+
+ char[] getEscapeChars( boolean includeSingleQuote, boolean includeDoubleQuote )
+ {
+ StringBuilder buf = new StringBuilder( 2 );
+ if ( includeSingleQuote )
+ {
+ buf.append( '\'' );
+ }
+
+ if ( includeDoubleQuote )
+ {
+ buf.append( '\"' );
+ }
+
+ char[] result = new char[buf.length()];
+ buf.getChars( 0, buf.length(), result, 0 );
+
+ return result;
+ }
+
+ /**
+ * @return false in all cases
+ */
+ protected boolean isDoubleQuotedArgumentEscaped()
+ {
+ return false;
+ }
+
+ /**
+ * @return {@link #singleQuotedArgumentEscaped}
+ */
+ protected boolean isSingleQuotedArgumentEscaped()
+ {
+ return singleQuotedArgumentEscaped;
+ }
+
+ boolean isDoubleQuotedExecutableEscaped()
+ {
+ return false;
+ }
+
+ boolean isSingleQuotedExecutableEscaped()
+ {
+ return singleQuotedExecutableEscaped;
+ }
+
+ /**
+ * @param argQuoteDelimiterParameter {@link #argQuoteDelimiter}
+ */
+ void setArgumentQuoteDelimiter( char argQuoteDelimiterParameter )
+ {
+ this.argQuoteDelimiter = argQuoteDelimiterParameter;
+ }
+
+ char getArgumentQuoteDelimiter()
+ {
+ return argQuoteDelimiter;
+ }
+
+ /**
+ * @param exeQuoteDelimiterParameter {@link #exeQuoteDelimiter}
+ */
+ void setExecutableQuoteDelimiter( char exeQuoteDelimiterParameter )
+ {
+ this.exeQuoteDelimiter = exeQuoteDelimiterParameter;
+ }
+
+ char getExecutableQuoteDelimiter()
+ {
+ return exeQuoteDelimiter;
+ }
+
+ /**
+ * Get the full command line to execute, including shell command, shell arguments,
+ * executable and executable arguments
+ *
+ * @param arguments arguments for the executable, not the shell
+ * @return List of String objects, whose array version is suitable to be used as argument
+ * of Runtime.getRuntime().exec()
+ */
+ public List getShellCommandLine( String... arguments )
+ {
+
+ List commandLine = new ArrayList<>();
+
+ if ( getShellCommand() != null )
+ {
+ commandLine.add( getShellCommand() );
+ }
+
+ if ( getShellArgs() != null )
+ {
+ commandLine.addAll( getShellArgsList() );
+ }
+
+ commandLine.addAll( getCommandLine( executable, arguments ) );
+
+ return commandLine;
+
+ }
+
+ List getShellArgsList()
+ {
+ return shellArgs;
+ }
+
+ /**
+ * @param quotedArgumentsEnabled {@link #quotedArgumentsEnabled}
+ */
+ public void setQuotedArgumentsEnabled( boolean quotedArgumentsEnabled )
+ {
+ this.quotedArgumentsEnabled = quotedArgumentsEnabled;
+ }
+
+ boolean isQuotedArgumentsEnabled()
+ {
+ return quotedArgumentsEnabled;
+ }
+
+ void setQuotedExecutableEnabled( boolean quotedExecutableEnabled )
+ {
+ this.quotedExecutableEnabled = quotedExecutableEnabled;
+ }
+
+ boolean isQuotedExecutableEnabled()
+ {
+ return quotedExecutableEnabled;
+ }
+
+ /**
+ * Sets the executable to run.
+ * @param executable The executable.
+ */
+ public void setExecutable( String executable )
+ {
+ if ( ( executable == null ) || ( executable.length() == 0 ) )
+ {
+ return;
+ }
+ this.executable = executable.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ }
+
+ /**
+ * @return The executable.
+ */
+ public String getExecutable()
+ {
+ return executable;
+ }
+
+ /**
+ * Sets execution directory.
+ * @param path The path which should be used as working directory.
+ */
+ public void setWorkingDirectory( String path )
+ {
+ if ( path != null )
+ {
+ this.workingDir = path;
+ }
+ }
+
+ /**
+ * Sets execution directory.
+ *
+ * @param workingDirectory the working directory
+ */
+ public void setWorkingDirectory( File workingDirectory )
+ {
+ if ( workingDirectory != null )
+ {
+ this.workingDir = workingDirectory.getAbsolutePath();
+ }
+ }
+
+ /**
+ * @return the working directory
+ */
+ public File getWorkingDirectory()
+ {
+ return workingDir == null ? null : new File( workingDir );
+ }
+
+ String getWorkingDirectoryAsString()
+ {
+ return workingDir;
+ }
+
+ /** {@inheritDoc} */
+ public Object clone()
+ {
+ throw new RuntimeException( "Do we ever clone this?" );
+/* Shell shell = new Shell();
+ shell.setExecutable( getExecutable() );
+ shell.setWorkingDirectory( getWorkingDirectory() );
+ shell.setShellArgs( getShellArgs() );
+ return shell;*/
+ }
+
+ void setSingleQuotedArgumentEscaped( boolean singleQuotedArgumentEscaped )
+ {
+ this.singleQuotedArgumentEscaped = singleQuotedArgumentEscaped;
+ }
+
+ void setSingleQuotedExecutableEscaped( boolean singleQuotedExecutableEscaped )
+ {
+ this.singleQuotedExecutableEscaped = singleQuotedExecutableEscaped;
+ }
+
+ public boolean isUnconditionalQuoting()
+ {
+ return unconditionalQuoting;
+ }
+
+ public void setUnconditionalQuoting( boolean unconditionalQuoting )
+ {
+ this.unconditionalQuoting = unconditionalQuoting;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java
new file mode 100644
index 000000000..19b17322d
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ClassMap.java
@@ -0,0 +1,538 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * 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 java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.Hashtable;
+import java.util.Map;
+
+/**
+ * A cache of introspection information for a specific class instance.
+ * Keys {@link java.lang.reflect.Method} objects by a concatenation of the
+ * method name and the names of classes that make up the parameters.
+ *
+ * @author Jason van Zyl
+ * @author Bob McWhirter
+ * @author Attila Szegedi
+ * @author Geir Magnusson Jr.
+ *
+ */
+public class ClassMap
+{
+ private static final class CacheMiss
+ {
+ }
+
+ private static final CacheMiss CACHE_MISS = new CacheMiss();
+
+ private static final Object OBJECT = new Object();
+
+ /**
+ * Class passed into the constructor used to as
+ * the basis for the Method map.
+ */
+
+ private final Class> clazz;
+
+ /**
+ * Cache of Methods, or CACHE_MISS, keyed by method
+ * name and actual arguments used to find it.
+ */
+ private final Map methodCache = new Hashtable();
+
+ private MethodMap methodMap = new MethodMap();
+
+ /**
+ * Standard constructor
+ * @param clazz The class.
+ */
+ public ClassMap( Class> clazz )
+ {
+ this.clazz = clazz;
+ populateMethodCache();
+ }
+
+ /**
+ * @return the class object whose methods are cached by this map.
+ */
+ Class> getCachedClass()
+ {
+ return clazz;
+ }
+
+ /**
+ * Find a Method using the methodKey
+ * provided.
+ *
+ * Look in the methodMap for an entry. If found,
+ * it'll either be a CACHE_MISS, in which case we
+ * simply give up, or it'll be a Method, in which
+ * case, we return it.
+ *
+ * If nothing is found, then we must actually go
+ * and introspect the method from the MethodMap.
+ * @param name Method name.
+ * @param params Method parameters.
+ * @return The found method.
+ * @throws MethodMap.AmbiguousException in case of duplicate methods.
+ */
+ public Method findMethod( String name, Object... params )
+ throws MethodMap.AmbiguousException
+ {
+ String methodKey = makeMethodKey( name, params );
+ Object cacheEntry = methodCache.get( methodKey );
+
+ if ( cacheEntry == CACHE_MISS )
+ {
+ return null;
+ }
+
+ if ( cacheEntry == null )
+ {
+ try
+ {
+ cacheEntry = methodMap.find( name, params );
+ }
+ catch ( MethodMap.AmbiguousException ae )
+ {
+ /*
+ * that's a miss :)
+ */
+
+ methodCache.put( methodKey, CACHE_MISS );
+
+ throw ae;
+ }
+
+ if ( cacheEntry == null )
+ {
+ methodCache.put( methodKey, CACHE_MISS );
+ }
+ else
+ {
+ methodCache.put( methodKey, cacheEntry );
+ }
+ }
+
+ // Yes, this might just be null.
+
+ return (Method) cacheEntry;
+ }
+
+ /**
+ * Populate the Map of direct hits. These
+ * are taken from all the public methods
+ * that our class provides.
+ */
+ private void populateMethodCache()
+ {
+
+ /*
+ * get all publicly accessible methods
+ */
+
+ Method[] methods = getAccessibleMethods( clazz );
+
+ /*
+ * map and cache them
+ */
+
+ for ( Method method : methods )
+ {
+ /*
+ * now get the 'public method', the method declared by a
+ * public interface or class. (because the actual implementing
+ * class may be a facade...
+ */
+
+ Method publicMethod = getPublicMethod( method );
+
+ /*
+ * it is entirely possible that there is no public method for
+ * the methods of this class (i.e. in the facade, a method
+ * that isn't on any of the interfaces or superclass
+ * in which case, ignore it. Otherwise, map and cache
+ */
+
+ if ( publicMethod != null )
+ {
+ methodMap.add( publicMethod );
+ methodCache.put( makeMethodKey( publicMethod ), publicMethod );
+ }
+ }
+ }
+
+ /**
+ * Make a methodKey for the given method using
+ * the concatenation of the name and the
+ * types of the method parameters.
+ */
+ private String makeMethodKey( Method method )
+ {
+ Class>[] parameterTypes = method.getParameterTypes();
+
+ StringBuilder methodKey = new StringBuilder( method.getName() );
+
+ for ( Class> parameterType : parameterTypes )
+ {
+ /*
+ * If the argument type is primitive then we want
+ * to convert our primitive type signature to the
+ * corresponding Object type so introspection for
+ * methods with primitive types will work correctly.
+ */
+ if ( parameterType.isPrimitive() )
+ {
+ if ( parameterType.equals( Boolean.TYPE ) )
+ {
+ methodKey.append( "java.lang.Boolean" );
+ }
+ else if ( parameterType.equals( Byte.TYPE ) )
+ {
+ methodKey.append( "java.lang.Byte" );
+ }
+ else if ( parameterType.equals( Character.TYPE ) )
+ {
+ methodKey.append( "java.lang.Character" );
+ }
+ else if ( parameterType.equals( Double.TYPE ) )
+ {
+ methodKey.append( "java.lang.Double" );
+ }
+ else if ( parameterType.equals( Float.TYPE ) )
+ {
+ methodKey.append( "java.lang.Float" );
+ }
+ else if ( parameterType.equals( Integer.TYPE ) )
+ {
+ methodKey.append( "java.lang.Integer" );
+ }
+ else if ( parameterType.equals( Long.TYPE ) )
+ {
+ methodKey.append( "java.lang.Long" );
+ }
+ else if ( parameterType.equals( Short.TYPE ) )
+ {
+ methodKey.append( "java.lang.Short" );
+ }
+ }
+ else
+ {
+ methodKey.append( parameterType.getName() );
+ }
+ }
+
+ return methodKey.toString();
+ }
+
+ private static String makeMethodKey( String method, Object... params )
+ {
+ StringBuilder methodKey = new StringBuilder().append( method );
+
+ for ( Object param : params )
+ {
+ Object arg = param;
+
+ if ( arg == null )
+ {
+ arg = OBJECT;
+ }
+
+ methodKey.append( arg.getClass().getName() );
+ }
+
+ return methodKey.toString();
+ }
+
+ /**
+ * Retrieves public methods for a class. In case the class is not
+ * public, retrieves methods with same signature as its public methods
+ * from public superclasses and interfaces (if they exist). Basically
+ * upcasts every method to the nearest acccessible method.
+ */
+ private static Method[] getAccessibleMethods( Class> clazz )
+ {
+ Method[] methods = clazz.getMethods();
+
+ /*
+ * Short circuit for the (hopefully) majority of cases where the
+ * clazz is public
+ */
+
+ if ( Modifier.isPublic( clazz.getModifiers() ) )
+ {
+ return methods;
+ }
+
+ /*
+ * No luck - the class is not public, so we're going the longer way.
+ */
+
+ MethodInfo[] methodInfos = new MethodInfo[methods.length];
+
+ for ( int i = methods.length; i-- > 0; )
+ {
+ methodInfos[i] = new MethodInfo( methods[i] );
+ }
+
+ int upcastCount = getAccessibleMethods( clazz, methodInfos, 0 );
+
+ /*
+ * Reallocate array in case some method had no accessible counterpart.
+ */
+
+ if ( upcastCount < methods.length )
+ {
+ methods = new Method[upcastCount];
+ }
+
+ int j = 0;
+ for ( MethodInfo methodInfo : methodInfos )
+ {
+ if ( methodInfo.upcast )
+ {
+ methods[j++] = methodInfo.method;
+ }
+ }
+ return methods;
+ }
+
+ /**
+ * Recursively finds a match for each method, starting with the class, and then
+ * searching the superclass and interfaces.
+ *
+ * @param clazz Class to check
+ * @param methodInfos array of methods we are searching to match
+ * @param upcastCount current number of methods we have matched
+ * @return count of matched methods
+ */
+ private static int getAccessibleMethods( Class> clazz, MethodInfo[] methodInfos, int upcastCount )
+ {
+ int l = methodInfos.length;
+
+ /*
+ * if this class is public, then check each of the currently
+ * 'non-upcasted' methods to see if we have a match
+ */
+
+ if ( Modifier.isPublic( clazz.getModifiers() ) )
+ {
+ for ( int i = 0; i < l && upcastCount < l; ++i )
+ {
+ try
+ {
+ MethodInfo methodInfo = methodInfos[i];
+
+ if ( !methodInfo.upcast )
+ {
+ methodInfo.tryUpcasting( clazz );
+ upcastCount++;
+ }
+ }
+ catch ( NoSuchMethodException e )
+ {
+ /*
+ * Intentionally ignored - it means
+ * it wasn't found in the current class
+ */
+ }
+ }
+
+ /*
+ * Short circuit if all methods were upcast
+ */
+
+ if ( upcastCount == l )
+ {
+ return upcastCount;
+ }
+ }
+
+ /*
+ * Examine superclass
+ */
+
+ Class> superclazz = clazz.getSuperclass();
+
+ if ( superclazz != null )
+ {
+ upcastCount = getAccessibleMethods( superclazz, methodInfos, upcastCount );
+
+ /*
+ * Short circuit if all methods were upcast
+ */
+
+ if ( upcastCount == l )
+ {
+ return upcastCount;
+ }
+ }
+
+ /*
+ * Examine interfaces. Note we do it even if superclazz == null.
+ * This is redundant as currently java.lang.Object does not implement
+ * any interfaces, however nothing guarantees it will not in future.
+ */
+
+ Class>[] interfaces = clazz.getInterfaces();
+
+ for ( int i = interfaces.length; i-- > 0; )
+ {
+ upcastCount = getAccessibleMethods( interfaces[i], methodInfos, upcastCount );
+
+ /*
+ * Short circuit if all methods were upcast
+ */
+
+ if ( upcastCount == l )
+ {
+ return upcastCount;
+ }
+ }
+
+ return upcastCount;
+ }
+
+ /**
+ * For a given method, retrieves its publicly accessible counterpart.
+ * This method will look for a method with same name
+ * and signature declared in a public superclass or implemented interface of this
+ * method's declaring class. This counterpart method is publicly callable.
+ *
+ * @param method a method whose publicly callable counterpart is requested.
+ * @return the publicly callable counterpart method. Note that if the parameter
+ * method is itself declared by a public class, this method is an identity
+ * function.
+ */
+ private static Method getPublicMethod( Method method )
+ {
+ Class> clazz = method.getDeclaringClass();
+
+ /*
+ * Short circuit for (hopefully the majority of) cases where the declaring
+ * class is public.
+ */
+
+ if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
+ {
+ return method;
+ }
+
+ return getPublicMethod( clazz, method.getName(), method.getParameterTypes() );
+ }
+
+ /**
+ * Looks up the method with specified name and signature in the first public
+ * superclass or implemented interface of the class.
+ *
+ * @param clazz the class whose method is sought
+ * @param name the name of the method
+ * @param paramTypes the classes of method parameters
+ */
+ private static Method getPublicMethod( Class> clazz, String name, Class>... paramTypes )
+ {
+ /*
+ * if this class is public, then try to get it
+ */
+
+ if ( ( clazz.getModifiers() & Modifier.PUBLIC ) != 0 )
+ {
+ try
+ {
+ return clazz.getMethod( name, paramTypes );
+ }
+ catch ( NoSuchMethodException e )
+ {
+ /*
+ * If the class does not have the method, then neither its
+ * superclass nor any of its interfaces has it so quickly return
+ * null.
+ */
+ return null;
+ }
+ }
+
+ /*
+ * try the superclass
+ */
+
+ Class> superclazz = clazz.getSuperclass();
+
+ if ( superclazz != null )
+ {
+ Method superclazzMethod = getPublicMethod( superclazz, name, paramTypes );
+
+ if ( superclazzMethod != null )
+ {
+ return superclazzMethod;
+ }
+ }
+
+ /*
+ * and interfaces
+ */
+
+ Class>[] interfaces = clazz.getInterfaces();
+
+ for ( Class> anInterface : interfaces )
+ {
+ Method interfaceMethod = getPublicMethod( anInterface, name, paramTypes );
+
+ if ( interfaceMethod != null )
+ {
+ return interfaceMethod;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Used for the iterative discovery process for public methods.
+ */
+ private static final class MethodInfo
+ {
+ Method method;
+
+ String name;
+
+ Class>[] parameterTypes;
+
+ boolean upcast;
+
+ MethodInfo( Method method )
+ {
+ this.method = null;
+ name = method.getName();
+ parameterTypes = method.getParameterTypes();
+ upcast = false;
+ }
+
+ void tryUpcasting( Class> clazz )
+ throws NoSuchMethodException
+ {
+ method = clazz.getMethod( name, parameterTypes );
+ name = null;
+ parameterTypes = null;
+ upcast = true;
+ }
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/IntrospectionException.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/IntrospectionException.java
new file mode 100644
index 000000000..924137c58
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/IntrospectionException.java
@@ -0,0 +1,41 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * 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.
+ */
+
+class IntrospectionException
+ extends Exception
+{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = -6090771282553728784L;
+
+ IntrospectionException( String message )
+ {
+ super( message );
+ }
+
+ IntrospectionException( Throwable cause )
+ {
+ super( cause );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java
new file mode 100644
index 000000000..8cf80f8d4
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/MethodMap.java
@@ -0,0 +1,479 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * 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 java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Jason van Zyl
+ * @author Bob McWhirter
+ * @author Christoph Reck
+ * @author Geir Magnusson Jr.
+ * @author Attila Szegedi
+ *
+ */
+class MethodMap
+{
+ private static final int MORE_SPECIFIC = 0;
+
+ private static final int LESS_SPECIFIC = 1;
+
+ private static final int INCOMPARABLE = 2;
+
+ /**
+ * Keep track of all methods with the same name.
+ */
+ private final Map> methodByNameMap = new Hashtable>();
+
+ /**
+ * Add a method to a list of methods by name.
+ * For a particular class we are keeping track
+ * of all the methods with the same name.
+ *
+ * @param method The method
+ */
+ void add( Method method )
+ {
+ String methodName = method.getName();
+
+ List l = get( methodName );
+
+ if ( l == null )
+ {
+ l = new ArrayList();
+ methodByNameMap.put( methodName, l );
+ }
+
+ l.add( method );
+ }
+
+ /**
+ * Return a list of methods with the same name.
+ *
+ * @param key The name of the method.
+ * @return List list of methods
+ */
+ List get( String key )
+ {
+ return methodByNameMap.get( key );
+ }
+
+ /**
+ *
+ * Find a method. Attempts to find the
+ * most specific applicable method using the
+ * algorithm described in the JLS section
+ * 15.12.2 (with the exception that it can't
+ * distinguish a primitive type argument from
+ * an object type argument, since in reflection
+ * primitive type arguments are represented by
+ * their object counterparts, so for an argument of
+ * type (say) java.lang.Integer, it will not be able
+ * to decide between a method that takes int and a
+ * method that takes java.lang.Integer as a parameter.
+ *
+ *
+ *
+ * This turns out to be a relatively rare case
+ * where this is needed - however, functionality
+ * like this is needed.
+ *
+ *
+ * @param methodName name of method
+ * @param args the actual arguments with which the method is called
+ * @return the most specific applicable method, or null if no
+ * method is applicable.
+ * @throws AmbiguousException if there is more than one maximally
+ * specific applicable method
+ */
+ Method find( String methodName, Object... args )
+ throws AmbiguousException
+ {
+ List methodList = get( methodName );
+
+ if ( methodList == null )
+ {
+ return null;
+ }
+
+ int l = args.length;
+ Class>[] classes = new Class[l];
+
+ for ( int i = 0; i < l; ++i )
+ {
+ Object arg = args[i];
+
+ /*
+ * if we are careful down below, a null argument goes in there
+ * so we can know that the null was passed to the method
+ */
+ classes[i] = arg == null ? null : arg.getClass();
+ }
+
+ return getMostSpecific( methodList, classes );
+ }
+
+ /**
+ * simple distinguishable exception, used when
+ * we run across ambiguous overloading
+ */
+ static class AmbiguousException
+ extends Exception
+ {
+
+ private static final long serialVersionUID = 751688436639650618L;
+ }
+
+
+ private static Method getMostSpecific( List methods, Class>... classes )
+ throws AmbiguousException
+ {
+ LinkedList applicables = getApplicables( methods, classes );
+
+ if ( applicables.isEmpty() )
+ {
+ return null;
+ }
+
+ if ( applicables.size() == 1 )
+ {
+ return applicables.getFirst();
+ }
+
+ /*
+ * This list will contain the maximally specific methods. Hopefully at
+ * the end of the below loop, the list will contain exactly one method,
+ * (the most specific method) otherwise we have ambiguity.
+ */
+
+ LinkedList maximals = new LinkedList();
+
+ for ( Method app : applicables )
+ {
+ Class>[] appArgs = app.getParameterTypes();
+ boolean lessSpecific = false;
+
+ for ( Iterator maximal = maximals.iterator(); !lessSpecific && maximal.hasNext(); )
+ {
+ Method max = maximal.next();
+
+ switch ( moreSpecific( appArgs, max.getParameterTypes() ) )
+ {
+ case MORE_SPECIFIC:
+ /*
+ * This method is more specific than the previously
+ * known maximally specific, so remove the old maximum.
+ */
+
+ maximal.remove();
+ break;
+
+ case LESS_SPECIFIC:
+ /*
+ * This method is less specific than some of the
+ * currently known maximally specific methods, so we
+ * won't add it into the set of maximally specific
+ * methods
+ */
+
+ lessSpecific = true;
+ break;
+
+ default:
+ }
+ }
+
+ if ( !lessSpecific )
+ {
+ maximals.addLast( app );
+ }
+ }
+
+ if ( maximals.size() > 1 )
+ {
+ // We have more than one maximally specific method
+ throw new AmbiguousException();
+ }
+
+ return maximals.getFirst();
+ }
+
+ /**
+ * Determines which method signature (represented by a class array) is more
+ * specific. This defines a partial ordering on the method signatures.
+ *
+ * @param c1 first signature to compare
+ * @param c2 second signature to compare
+ * @return MORE_SPECIFIC if c1 is more specific than c2, LESS_SPECIFIC if
+ * c1 is less specific than c2, INCOMPARABLE if they are incomparable.
+ */
+ private static int moreSpecific( Class>[] c1, Class>[] c2 )
+ {
+ boolean c1MoreSpecific = false;
+ boolean c2MoreSpecific = false;
+
+ for ( int i = 0; i < c1.length; ++i )
+ {
+ if ( c1[i] != c2[i] )
+ {
+ c1MoreSpecific = c1MoreSpecific || isStrictMethodInvocationConvertible( c2[i], c1[i] );
+ c2MoreSpecific = c2MoreSpecific || isStrictMethodInvocationConvertible( c1[i], c2[i] );
+ }
+ }
+
+ if ( c1MoreSpecific )
+ {
+ if ( c2MoreSpecific )
+ {
+ /*
+ * Incomparable due to cross-assignable arguments (i.e.
+ * foo(String, Object) vs. foo(Object, String))
+ */
+
+ return INCOMPARABLE;
+ }
+
+ return MORE_SPECIFIC;
+ }
+
+ if ( c2MoreSpecific )
+ {
+ return LESS_SPECIFIC;
+ }
+
+ /*
+ * Incomparable due to non-related arguments (i.e.
+ * foo(Runnable) vs. foo(Serializable))
+ */
+
+ return INCOMPARABLE;
+ }
+
+ /**
+ * Returns all methods that are applicable to actual argument types.
+ *
+ * @param methods list of all candidate methods
+ * @param classes the actual types of the arguments
+ * @return a list that contains only applicable methods (number of
+ * formal and actual arguments matches, and argument types are assignable
+ * to formal types through a method invocation conversion).
+ */
+ private static LinkedList getApplicables( List methods, Class>... classes )
+ {
+ LinkedList list = new LinkedList();
+
+ for ( Method method : methods )
+ {
+ if ( isApplicable( method, classes ) )
+ {
+ list.add( method );
+ }
+ }
+ return list;
+ }
+
+ /**
+ * Returns true if the supplied method is applicable to actual
+ * argument types.
+ *
+ * @param method The method to check for applicability
+ * @param classes The arguments
+ * @return true if the method applies to the parameter types
+ */
+ private static boolean isApplicable( Method method, Class>... classes )
+ {
+ Class>[] methodArgs = method.getParameterTypes();
+
+ if ( methodArgs.length != classes.length )
+ {
+ return false;
+ }
+
+ for ( int i = 0; i < classes.length; ++i )
+ {
+ if ( !isMethodInvocationConvertible( methodArgs[i], classes[i] ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, treating object types of primitive
+ * types as if they were primitive types (that is, a Boolean actual
+ * parameter type matches boolean primitive formal type). This behavior
+ * is because this method is used to determine applicable methods for
+ * an actual parameter list, and primitive types are represented by
+ * their object duals in reflective method calls.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @return true if either formal type is assignable from actual type,
+ * or formal is a primitive type and actual is its corresponding object
+ * type or an object type of a primitive type that can be converted to
+ * the formal type.
+ */
+ private static boolean isMethodInvocationConvertible( Class> formal, Class> actual )
+ {
+ /*
+ * if it's a null, it means the arg was null
+ */
+ if ( actual == null && !formal.isPrimitive() )
+ {
+ return true;
+ }
+
+ /*
+ * Check for identity or widening reference conversion
+ */
+
+ if ( actual != null && formal.isAssignableFrom( actual ) )
+ {
+ return true;
+ }
+
+ /*
+ * Check for boxing with widening primitive conversion. Note that
+ * actual parameters are never primitives.
+ */
+
+ if ( formal.isPrimitive() )
+ {
+ if ( formal == Boolean.TYPE && actual == Boolean.class )
+ {
+ return true;
+ }
+ if ( formal == Character.TYPE && actual == Character.class )
+ {
+ return true;
+ }
+ if ( formal == Byte.TYPE && actual == Byte.class )
+ {
+ return true;
+ }
+ if ( formal == Short.TYPE && ( actual == Short.class || actual == Byte.class ) )
+ {
+ return true;
+ }
+ if ( formal == Integer.TYPE
+ && ( actual == Integer.class || actual == Short.class || actual == Byte.class ) )
+ {
+ return true;
+ }
+ if ( formal == Long.TYPE
+ && ( actual == Long.class || actual == Integer.class || actual == Short.class
+ || actual == Byte.class ) )
+ {
+ return true;
+ }
+ if ( formal == Float.TYPE
+ && ( actual == Float.class || actual == Long.class || actual == Integer.class
+ || actual == Short.class || actual == Byte.class ) )
+ {
+ return true;
+ }
+ if ( formal == Double.TYPE
+ && ( actual == Double.class || actual == Float.class || actual == Long.class || actual == Integer.class
+ || actual == Short.class || actual == Byte.class ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Determines whether a type represented by a class object is
+ * convertible to another type represented by a class object using a
+ * method invocation conversion, without matching object and primitive
+ * types. This method is used to determine the more specific type when
+ * comparing signatures of methods.
+ *
+ * @param formal the formal parameter type to which the actual
+ * parameter type should be convertible
+ * @param actual the actual parameter type.
+ * @return true if either formal type is assignable from actual type,
+ * or formal and actual are both primitive types and actual can be
+ * subject to widening conversion to formal.
+ */
+ private static boolean isStrictMethodInvocationConvertible( Class> formal, Class> actual )
+ {
+ /*
+ * we shouldn't get a null into, but if so
+ */
+ if ( actual == null && !formal.isPrimitive() )
+ {
+ return true;
+ }
+
+ /*
+ * Check for identity or widening reference conversion
+ */
+
+ if ( formal.isAssignableFrom( actual ) )
+ {
+ return true;
+ }
+
+ /*
+ * Check for widening primitive conversion.
+ */
+
+ if ( formal.isPrimitive() )
+ {
+ if ( formal == Short.TYPE && ( actual == Byte.TYPE ) )
+ {
+ return true;
+ }
+ if ( formal == Integer.TYPE && ( actual == Short.TYPE || actual == Byte.TYPE ) )
+ {
+ return true;
+ }
+ if ( formal == Long.TYPE && ( actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE ) )
+ {
+ return true;
+ }
+ if ( formal == Float.TYPE
+ && ( actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE || actual == Byte.TYPE ) )
+ {
+ return true;
+ }
+ if ( formal == Double.TYPE
+ && ( actual == Float.TYPE || actual == Long.TYPE || actual == Integer.TYPE || actual == Short.TYPE
+ || actual == Byte.TYPE ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java
new file mode 100644
index 000000000..963c98291
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/introspection/ReflectionValueExtractor.java
@@ -0,0 +1,401 @@
+package org.apache.maven.shared.utils.introspection;
+
+/*
+ * 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 java.lang.reflect.Array;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.Map;
+import java.util.WeakHashMap;
+
+import org.apache.maven.shared.utils.StringUtils;
+import org.apache.maven.shared.utils.introspection.MethodMap.AmbiguousException;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+
+/**
+ *
Using simple dotted expressions to extract the values from an Object instance,
+ * For example we might want to extract a value like: project.build.sourceDirectory
+ *
+ *
The implementation supports indexed, nested and mapped properties similar to the JSP way.
+ *
+ * @author Jason van Zyl
+ * @author Vincent Siveton
+ *
+ * @see
+ * http://struts.apache.org/1.x/struts-taglib/indexedprops.html
+ */
+public class ReflectionValueExtractor
+{
+ private static final Class>[] CLASS_ARGS = new Class[0];
+
+ private static final Object[] OBJECT_ARGS = new Object[0];
+
+ /**
+ * Use a WeakHashMap here, so the keys (Class objects) can be garbage collected.
+ * This approach prevents permgen space overflows due to retention of discarded
+ * classloaders.
+ */
+ private static final Map, ClassMap> CLASS_MAPS = new WeakHashMap, ClassMap>();
+
+ static final int EOF = -1;
+
+ static final char PROPERTY_START = '.';
+
+ static final char INDEXED_START = '[';
+
+ static final char INDEXED_END = ']';
+
+ static final char MAPPED_START = '(';
+
+ static final char MAPPED_END = ')';
+
+ static class Tokenizer
+ {
+ final String expression;
+
+ int idx;
+
+ Tokenizer( String expression )
+ {
+ this.expression = expression;
+ }
+
+ public int peekChar()
+ {
+ return idx < expression.length() ? expression.charAt( idx ) : EOF;
+ }
+
+ public int skipChar()
+ {
+ return idx < expression.length() ? expression.charAt( idx++ ) : EOF;
+ }
+
+ public String nextToken( char delimiter )
+ {
+ int start = idx;
+
+ while ( idx < expression.length() && delimiter != expression.charAt( idx ) )
+ {
+ idx++;
+ }
+
+ // delimiter MUST be present
+ if ( idx <= start || idx >= expression.length() )
+ {
+ return null;
+ }
+
+ return expression.substring( start, idx++ );
+ }
+
+ public String nextPropertyName()
+ {
+ final int start = idx;
+
+ while ( idx < expression.length() && Character.isJavaIdentifierPart( expression.charAt( idx ) ) )
+ {
+ idx++;
+ }
+
+ // property name does not require delimiter
+ if ( idx <= start || idx > expression.length() )
+ {
+ return null;
+ }
+
+ return expression.substring( start, idx );
+ }
+
+ public int getPosition()
+ {
+ return idx < expression.length() ? idx : EOF;
+ }
+
+ // to make tokenizer look pretty in debugger
+ @Override
+ public String toString()
+ {
+ return idx < expression.length() ? expression.substring( idx ) : "";
+ }
+ }
+
+ private ReflectionValueExtractor()
+ {
+ }
+
+ /**
+ *
The implementation supports indexed, nested and mapped properties.
+ *
+ *
+ *
nested properties should be defined by a dot, i.e. "user.address.street"
+ *
indexed properties (java.util.List or array instance) should be contains (\\w+)\\[(\\d+)\\]
+ * pattern, i.e. "user.addresses[1].street"
+ *
mapped properties should be contains (\\w+)\\((.+)\\) pattern,
+ * i.e. "user.addresses(myAddress).street"
+ *
+ *
+ * @param expression not null expression
+ * @param root not null object
+ * @return the object defined by the expression
+ * @throws IntrospectionException if any
+ */
+ public static Object evaluate( @Nonnull String expression, @Nullable Object root )
+ throws IntrospectionException
+ {
+ return evaluate( expression, root, true );
+ }
+
+ /**
+ *
+ * The implementation supports indexed, nested and mapped properties.
+ *
+ *
+ *
+ *
nested properties should be defined by a dot, i.e. "user.address.street"
+ *
indexed properties (java.util.List or array instance) should be contains (\\w+)\\[(\\d+)\\]
+ * pattern, i.e. "user.addresses[1].street"
+ *
mapped properties should be contains (\\w+)\\((.+)\\) pattern, i.e.
+ * "user.addresses(myAddress).street"
+ *
+ *
+ * @param expression not null expression
+ * @param root not null object
+ * @param trimRootToken trim root token yes/no.
+ * @return the object defined by the expression
+ * @throws IntrospectionException if any
+ */
+ public static Object evaluate( @Nonnull String expression, @Nullable Object root, boolean trimRootToken )
+ throws IntrospectionException
+ {
+ Object value = root;
+
+ // ----------------------------------------------------------------------
+ // Walk the dots and retrieve the ultimate value desired from the
+ // MavenProject instance.
+ // ----------------------------------------------------------------------
+
+ if ( StringUtils.isEmpty( expression ) || !Character.isJavaIdentifierStart( expression.charAt( 0 ) ) )
+ {
+ return null;
+ }
+
+ boolean hasDots = expression.indexOf( PROPERTY_START ) >= 0;
+
+ final Tokenizer tokenizer;
+ if ( trimRootToken && hasDots )
+ {
+ tokenizer = new Tokenizer( expression );
+ tokenizer.nextPropertyName();
+ if ( tokenizer.getPosition() == EOF )
+ {
+ return null;
+ }
+ }
+ else
+ {
+ tokenizer = new Tokenizer( "." + expression );
+ }
+
+ int propertyPosition = tokenizer.getPosition();
+ while ( value != null && tokenizer.peekChar() != EOF )
+ {
+ switch ( tokenizer.skipChar() )
+ {
+ case INDEXED_START:
+ value =
+ getIndexedValue( expression, propertyPosition, tokenizer.getPosition(), value,
+ tokenizer.nextToken( INDEXED_END ) );
+ break;
+ case MAPPED_START:
+ value =
+ getMappedValue( expression, propertyPosition, tokenizer.getPosition(), value,
+ tokenizer.nextToken( MAPPED_END ) );
+ break;
+ case PROPERTY_START:
+ propertyPosition = tokenizer.getPosition();
+ value = getPropertyValue( value, tokenizer.nextPropertyName() );
+ break;
+ default:
+ // could not parse expression
+ return null;
+ }
+ }
+
+ return value;
+ }
+
+ private static Object getMappedValue( final String expression, final int from, final int to, final Object value,
+ final String key )
+ throws IntrospectionException
+ {
+ if ( value == null || key == null )
+ {
+ return null;
+ }
+
+ if ( value instanceof Map )
+ {
+ Object[] localParams = new Object[] { key };
+ ClassMap classMap = getClassMap( value.getClass() );
+ try
+ {
+ Method method = classMap.findMethod( "get", localParams );
+ return method.invoke( value, localParams );
+ }
+ catch ( AmbiguousException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ catch ( IllegalAccessException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ catch ( InvocationTargetException e )
+ {
+ throw new IntrospectionException( e.getTargetException() );
+ }
+
+ }
+
+ final String message =
+ String.format( "The token '%s' at position '%d' refers to a java.util.Map, but the value "
+ + "seems is an instance of '%s'", expression.subSequence( from, to ), from, value.getClass() );
+
+ throw new IntrospectionException( message );
+ }
+
+ private static Object getIndexedValue( final String expression, final int from, final int to, final Object value,
+ final String indexStr )
+ throws IntrospectionException
+ {
+ try
+ {
+ int index = Integer.parseInt( indexStr );
+
+ if ( value.getClass().isArray() )
+ {
+ return Array.get( value, index );
+ }
+
+ if ( value instanceof List )
+ {
+ ClassMap classMap = getClassMap( value.getClass() );
+ // use get method on List interface
+ Object[] localParams = new Object[] { index };
+ Method method = null;
+ try
+ {
+ method = classMap.findMethod( "get", localParams );
+ return method.invoke( value, localParams );
+ }
+ catch ( AmbiguousException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ catch ( IllegalAccessException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ }
+ }
+ catch ( NumberFormatException e )
+ {
+ return null;
+ }
+ catch ( InvocationTargetException e )
+ {
+ // catch array index issues gracefully, otherwise release
+ if ( e.getCause() instanceof IndexOutOfBoundsException )
+ {
+ return null;
+ }
+
+ throw new IntrospectionException( e.getTargetException() );
+ }
+
+ final String message =
+ String.format( "The token '%s' at position '%d' refers to a java.util.List or an array, but the value "
+ + "seems is an instance of '%s'", expression.subSequence( from, to ), from, value.getClass() );
+
+ throw new IntrospectionException( message );
+ }
+
+ private static Object getPropertyValue( Object value, String property )
+ throws IntrospectionException
+ {
+ if ( value == null || property == null )
+ {
+ return null;
+ }
+
+ ClassMap classMap = getClassMap( value.getClass() );
+ String methodBase = StringUtils.capitalizeFirstLetter( property );
+ String methodName = "get" + methodBase;
+ try
+ {
+ Method method = classMap.findMethod( methodName, CLASS_ARGS );
+
+ if ( method == null )
+ {
+ // perhaps this is a boolean property??
+ methodName = "is" + methodBase;
+
+ method = classMap.findMethod( methodName, CLASS_ARGS );
+ }
+
+ if ( method == null )
+ {
+ return null;
+ }
+
+ return method.invoke( value, OBJECT_ARGS );
+ }
+ catch ( InvocationTargetException e )
+ {
+ throw new IntrospectionException( e.getTargetException() );
+ }
+ catch ( AmbiguousException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ catch ( IllegalAccessException e )
+ {
+ throw new IntrospectionException( e );
+ }
+ }
+
+ private static ClassMap getClassMap( Class> clazz )
+ {
+ ClassMap classMap = CLASS_MAPS.get( clazz );
+
+ if ( classMap == null )
+ {
+ classMap = new ClassMap( clazz );
+
+ CLASS_MAPS.put( clazz, classMap );
+ }
+
+ return classMap;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanResult.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanResult.java
new file mode 100644
index 000000000..ae5de32bd
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanResult.java
@@ -0,0 +1,62 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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.
+ */
+
+
+/**
+ * Scan for files in a directory at a given time and reports removed and added files
+ * between captures.
+ *
+ * @deprecated use {@code java.nio.file.DirectoryStream} and related classes
+ */
+@Deprecated
+public class DirectoryScanResult
+{
+ private final String[] filesAdded;
+
+ private final String[] filesRemoved;
+
+ /**
+ * @param filesAdded Added files.
+ * @param filesRemoved Removed files.
+ */
+ public DirectoryScanResult( String[] filesAdded, String[] filesRemoved )
+ {
+ this.filesAdded = filesAdded;
+ this.filesRemoved = filesRemoved;
+ }
+
+ /**
+ * @return all files which got detected as being added between 2 capture calls
+ */
+ public String[] getFilesAdded()
+ {
+ return filesAdded;
+ }
+
+ /**
+ * @return all files which got detected as being removed between 2 capture calls
+ */
+ public String[] getFilesRemoved()
+ {
+ return filesRemoved;
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java
new file mode 100644
index 000000000..5d03525e7
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryScanner.java
@@ -0,0 +1,932 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+/**
+ * Class for scanning a directory for files/directories which match certain criteria.
+ *
+ * These criteria consist of selectors and patterns which have been specified. With the selectors you can select which
+ * files you want to have included. Files which are not selected are excluded. With patterns you can include or exclude
+ * files based on their filename.
+ *
+ * The idea is simple. A given directory is recursively scanned for all files and directories. Each file/directory is
+ * matched against a set of selectors, including special support for matching against filenames with include and and
+ * exclude patterns. Only files/directories which match at least one pattern of the include pattern list or other file
+ * selector, and don't match any pattern of the exclude pattern list or fail to match against a required selector will
+ * be placed in the list of files/directories found.
+ *
+ * When no list of include patterns is supplied, "**" will be used, which means that everything will be matched. When no
+ * list of exclude patterns is supplied, an empty list is used, such that nothing will be excluded. When no selectors
+ * are supplied, none are applied.
+ *
+ * The filename pattern matching is done as follows: The name to be matched is split up in path segments. A path segment
+ * is the name of a directory or file, which is bounded by File.separator ('/' under UNIX, '\' under
+ * Windows). For example, "abc/def/ghi/xyz.java" is split up in the segments "abc", "def","ghi" and "xyz.java". The same
+ * is done for the pattern against which should be matched.
+ *
+ * The segments of the name and the pattern are then matched against each other. When '**' is used for a path segment in
+ * the pattern, it matches zero or more path segments of the name.
+ *
+ * There is a special case regarding the use of File.separators at the beginning of the pattern and the
+ * string to match:
+ * When a pattern starts with a File.separator, the string to match must also start with a
+ * File.separator. When a pattern does not start with a File.separator, the string to match
+ * may not start with a File.separator. When one of these rules is not obeyed, the string will not match.
+ *
+ * When a name path segment is matched against a pattern path segment, the following special characters can be used:
+ * '*' matches zero or more characters
+ * '?' matches one character.
+ *
+ * Examples:
+ *
+ * "**\*.class" matches all .class files/dirs in a directory tree.
+ *
+ * "test\a??.java" matches all files/dirs which start with an 'a', then two more characters and then ".java", in a
+ * directory called test.
+ *
+ * "**" matches everything in a directory tree.
+ *
+ * "**\test\**\XYZ*" matches all files/dirs which start with "XYZ" and where there is a parent directory called test
+ * (e.g. "abc\test\def\ghi\XYZ123").
+ *
+ * Case sensitivity may be turned off if necessary. By default, it is turned on.
+ *
+ * Example of usage:
+ *
+ *
+ *
+ * This will scan a directory called test for .class files, but excludes all files in all proper subdirectories of a
+ * directory called "modules"
+ *
+ * This class must not be used from multiple Threads concurrently!
+ *
+ * @author Arnout J. Kuiper ajkuiper@wxs.nl
+ * @author Magesh Umasankar
+ * @author Bruce Atherton
+ * @author Antoine Levy-Lambert
+ * @deprecated use {@code java.nio.file.DirectoryStream} and related classes
+ */
+@Deprecated
+public class DirectoryScanner
+{
+ /**
+ * Patterns which should be excluded by default.
+ *
+ * @see #addDefaultExcludes()
+ */
+ public static final String[] DEFAULTEXCLUDES = {
+ // Miscellaneous typical temporary files
+ "**/*~", "**/#*#", "**/.#*", "**/%*%", "**/._*",
+
+ // CVS
+ "**/CVS", "**/CVS/**", "**/.cvsignore",
+
+ // Subversion
+ "**/.svn", "**/.svn/**",
+
+ // Arch
+ "**/.arch-ids", "**/.arch-ids/**",
+
+ // Bazaar
+ "**/.bzr", "**/.bzr/**",
+
+ // SurroundSCM
+ "**/.MySCMServerInfo",
+
+ // Mac
+ "**/.DS_Store",
+
+ // Serena Dimensions Version 10
+ "**/.metadata", "**/.metadata/**",
+
+ // Mercurial
+ "**/.hg", "**/.hg/**",
+
+ // git
+ "**/.git", "**/.git/**",
+
+ // BitKeeper
+ "**/BitKeeper", "**/BitKeeper/**", "**/ChangeSet", "**/ChangeSet/**",
+
+ // darcs
+ "**/_darcs", "**/_darcs/**", "**/.darcsrepo", "**/.darcsrepo/**", "**/-darcs-backup*", "**/.darcs-temp-mail" };
+
+ /**
+ * The base directory to be scanned.
+ */
+ private File basedir;
+
+ /**
+ * The patterns for the files to be included.
+ */
+ private String[] includes;
+
+ /**
+ * The patterns for the files to be excluded.
+ */
+ private String[] excludes;
+
+ private MatchPatterns excludesPatterns;
+
+ private MatchPatterns includesPatterns;
+
+
+ /**
+ * The files which matched at least one include and no excludes and were selected.
+ */
+ private List filesIncluded;
+
+ /**
+ * The files which did not match any includes or selectors.
+ */
+ private List filesNotIncluded;
+
+ /**
+ * The files which matched at least one include and at least one exclude.
+ */
+ private List filesExcluded;
+
+ /**
+ * The directories which matched at least one include and no excludes and were selected.
+ */
+ private List dirsIncluded;
+
+ /**
+ * The directories which were found and did not match any includes.
+ */
+ private List dirsNotIncluded;
+
+ /**
+ * The directories which matched at least one include and at least one exclude.
+ */
+ private List dirsExcluded;
+
+ /**
+ * Whether or not our results were built by a slow scan.
+ */
+ private boolean haveSlowResults = false;
+
+ /**
+ * Whether or not the file system should be treated as a case sensitive one.
+ */
+ private boolean isCaseSensitive = true;
+
+ /**
+ * Whether or not symbolic links should be followed.
+ *
+ *
+ */
+ private boolean followSymlinks = true;
+
+
+ /**
+ * A {@link ScanConductor} an control the scanning process.
+ */
+ private ScanConductor scanConductor = null;
+
+ /**
+ * The last ScanAction. We need to store this in the instance as the scan() method doesn't return
+ */
+ private ScanConductor.ScanAction scanAction = null;
+
+ /**
+ * Sole constructor.
+ */
+ public DirectoryScanner()
+ {
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is scanned recursively. All '/' and '\'
+ * characters are replaced by File.separatorChar, so the separator used need not match
+ * File.separatorChar.
+ *
+ * @param basedir The base directory to scan. Must not be null.
+ */
+ public void setBasedir( final String basedir )
+ {
+ setBasedir( new File( basedir.replace( '/', File.separatorChar ).replace( '\\', File.separatorChar ) ) );
+ }
+
+ /**
+ * Sets the base directory to be scanned. This is the directory which is scanned recursively.
+ *
+ * @param basedir The base directory for scanning. Should not be null.
+ */
+ public void setBasedir( @Nonnull final File basedir )
+ {
+ this.basedir = basedir;
+ }
+
+ /**
+ * Returns the base directory to be scanned. This is the directory which is scanned recursively.
+ *
+ * @return the base directory to be scanned
+ */
+ public File getBasedir()
+ {
+ return basedir;
+ }
+
+ /**
+ * Sets whether or not the file system should be regarded as case sensitive.
+ *
+ * @param isCaseSensitiveParameter whether or not the file system should be regarded as a case sensitive one
+ */
+ public void setCaseSensitive( final boolean isCaseSensitiveParameter )
+ {
+ this.isCaseSensitive = isCaseSensitiveParameter;
+ }
+
+ /**
+ * Sets whether or not symbolic links should be followed.
+ *
+ * @param followSymlinks whether or not symbolic links should be followed
+ */
+ public void setFollowSymlinks( final boolean followSymlinks )
+ {
+ this.followSymlinks = followSymlinks;
+ }
+
+ /**
+ * Sets the list of include patterns to use. All '/' and '\' characters are replaced by
+ * File.separatorChar, so the separator used need not match File.separatorChar.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param includes A list of include patterns. May be null, indicating that all files should be
+ * included. If a non-null list is given, all elements must be non-null.
+ */
+ public void setIncludes( final String... includes )
+ {
+ if ( includes == null )
+ {
+ this.includes = null;
+ }
+ else
+ {
+ this.includes = new String[includes.length];
+ for ( int i = 0; i < includes.length; i++ )
+ {
+ String pattern;
+ pattern = includes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.includes[i] = pattern;
+ }
+ }
+ }
+
+ /**
+ * Sets the list of exclude patterns to use. All '/' and '\' characters are replaced by
+ * File.separatorChar, so the separator used need not match File.separatorChar.
+ *
+ * When a pattern ends with a '/' or '\', "**" is appended.
+ *
+ * @param excludes A list of exclude patterns. May be null, indicating that no files should be
+ * excluded. If a non-null list is given, all elements must be non-null.
+ */
+ public void setExcludes( final String... excludes )
+ {
+ if ( excludes == null )
+ {
+ this.excludes = null;
+ }
+ else
+ {
+ this.excludes = new String[excludes.length];
+ for ( int i = 0; i < excludes.length; i++ )
+ {
+ String pattern;
+ pattern = excludes[i].trim().replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ if ( pattern.endsWith( File.separator ) )
+ {
+ pattern += "**";
+ }
+ this.excludes[i] = pattern;
+ }
+ }
+ }
+
+ /**
+ * @param scanConductor {@link #scanConductor}
+ */
+ public void setScanConductor( final ScanConductor scanConductor )
+ {
+ this.scanConductor = scanConductor;
+ }
+
+ /**
+ * Scans the base directory for files which match at least one include pattern and don't match any exclude patterns.
+ * If there are selectors then the files must pass muster there, as well.
+ *
+ * @throws IllegalStateException if the base directory was set incorrectly (i.e. if it is null,
+ * doesn't exist, or isn't a directory).
+ */
+ public void scan()
+ throws IllegalStateException
+ {
+ if ( basedir == null )
+ {
+ throw new IllegalStateException( "No basedir set" );
+ }
+ if ( !basedir.exists() )
+ {
+ throw new IllegalStateException( "basedir " + basedir + " does not exist" );
+ }
+ if ( !basedir.isDirectory() )
+ {
+ throw new IllegalStateException( "basedir " + basedir + " is not a directory" );
+ }
+
+ setupDefaultFilters();
+ setupMatchPatterns();
+
+ filesIncluded = new ArrayList();
+ filesNotIncluded = new ArrayList();
+ filesExcluded = new ArrayList();
+ dirsIncluded = new ArrayList();
+ dirsNotIncluded = new ArrayList();
+ dirsExcluded = new ArrayList();
+ scanAction = ScanConductor.ScanAction.CONTINUE;
+
+ if ( isIncluded( "" ) )
+ {
+ if ( !isExcluded( "" ) )
+ {
+ if ( scanConductor != null )
+ {
+ scanAction = scanConductor.visitDirectory( "", basedir );
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction )
+ || ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
+ {
+ return;
+ }
+ }
+
+ dirsIncluded.add( "" );
+ }
+ else
+ {
+ dirsExcluded.add( "" );
+ }
+ }
+ else
+ {
+ dirsNotIncluded.add( "" );
+ }
+ scandir( basedir, "", true );
+ }
+
+ /**
+ * Determine the file differences between the currently included files and
+ * a previously captured list of files.
+ * This method will not look for a changed in content but sole in the
+ * list of files given.
+ *
+ * The method will compare the given array of file Strings with the result
+ * of the last directory scan. It will execute a {@link #scan()} if no
+ * result of a previous scan could be found.
+ *
+ * The result of the diff can be queried by the methods
+ * {@link DirectoryScanResult#getFilesAdded()} and {@link DirectoryScanResult#getFilesRemoved()}
+ *
+ * @param oldFiles the list of previously captured files names.
+ * @return the result of the directory scan.
+ */
+ public DirectoryScanResult diffIncludedFiles( String... oldFiles )
+ {
+ if ( filesIncluded == null )
+ {
+ // perform a scan if the directory didn't got scanned yet
+ scan();
+ }
+
+ return diffFiles( oldFiles, filesIncluded.toArray( new String[filesIncluded.size()] ) );
+ }
+
+ /**
+ * @param oldFiles array of old files
+ * @param newFiles array of new files
+ * @return calculated difference
+ */
+ public static DirectoryScanResult diffFiles( @Nullable String[] oldFiles, @Nullable String[] newFiles )
+ {
+ Set oldFileSet = arrayAsHashSet( oldFiles );
+ Set newFileSet = arrayAsHashSet( newFiles );
+
+ List added = new ArrayList();
+ List removed = new ArrayList();
+
+ for ( String oldFile : oldFileSet )
+ {
+ if ( !newFileSet.contains( oldFile ) )
+ {
+ removed.add( oldFile );
+ }
+ }
+
+ for ( String newFile : newFileSet )
+ {
+ if ( !oldFileSet.contains( newFile ) )
+ {
+ added.add( newFile );
+ }
+ }
+
+ String[] filesAdded = added.toArray( new String[added.size()] );
+ String[] filesRemoved = removed.toArray( new String[removed.size()] );
+
+ return new DirectoryScanResult( filesAdded, filesRemoved );
+ }
+
+
+ /**
+ * Take an array of type T and convert it into a HashSet of type T.
+ * If null or an empty array gets passed, an empty Set will be returned.
+ *
+ * @param array The array
+ * @return the filled HashSet of type T
+ */
+ private static Set arrayAsHashSet( @Nullable T[] array )
+ {
+ if ( array == null || array.length == 0 )
+ {
+ return Collections.emptySet();
+ }
+
+ Set set = new HashSet( array.length );
+ Collections.addAll( set, array );
+
+ return set;
+ }
+
+ /**
+ * Top level invocation for a slow scan. A slow scan builds up a full list of excluded/included files/directories,
+ * whereas a fast scan will only have full results for included files, as it ignores directories which can't
+ * possibly hold any included files/directories.
+ *
+ * Returns immediately if a slow scan has already been completed.
+ */
+ void slowScan()
+ {
+ if ( haveSlowResults )
+ {
+ return;
+ }
+
+ final String[] excl = dirsExcluded.toArray( new String[dirsExcluded.size()] );
+
+ final String[] notIncl = dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
+
+ for ( String anExcl : excl )
+ {
+ if ( !couldHoldIncluded( anExcl ) )
+ {
+ scandir( new File( basedir, anExcl ), anExcl + File.separator, false );
+ }
+ }
+
+ for ( String aNotIncl : notIncl )
+ {
+ if ( !couldHoldIncluded( aNotIncl ) )
+ {
+ scandir( new File( basedir, aNotIncl ), aNotIncl + File.separator, false );
+ }
+ }
+
+ haveSlowResults = true;
+ }
+
+ /**
+ * Scans the given directory for files and directories. Found files and directories are placed in their respective
+ * collections, based on the matching of includes, excludes, and the selectors. When a directory is found, it is
+ * scanned recursively.
+ *
+ * @param dir The directory to scan. Must not be null.
+ * @param vpath The path relative to the base directory (needed to prevent problems with an absolute path when using
+ * dir). Must not be null.
+ * @param fast Whether or not this call is part of a fast scan.
+ * @see #filesIncluded
+ * @see #filesNotIncluded
+ * @see #filesExcluded
+ * @see #dirsIncluded
+ * @see #dirsNotIncluded
+ * @see #dirsExcluded
+ * @see #slowScan
+ */
+ void scandir( @Nonnull final File dir, @Nonnull final String vpath, final boolean fast )
+ {
+ String[] newfiles = dir.list();
+
+ if ( newfiles == null )
+ {
+ /*
+ * two reasons are mentioned in the API docs for File.list (1) dir is not a directory. This is impossible as
+ * we wouldn't get here in this case. (2) an IO error occurred (why doesn't it throw an exception then???)
+ */
+
+ /*
+ * [jdcasey] (2) is apparently happening to me, as this is killing one of my tests... this is affecting the
+ * assembly plugin, fwiw. I will initialize the newfiles array as zero-length for now. NOTE: I can't find
+ * the problematic code, as it appears to come from a native method in UnixFileSystem...
+ */
+ newfiles = new String[0];
+
+ // throw new IOException( "IO error scanning directory " + dir.getAbsolutePath() );
+ }
+
+ if ( !followSymlinks )
+ {
+ newfiles = doNotFollowSymbolicLinks( dir, vpath, newfiles );
+ }
+
+ for ( final String newfile : newfiles )
+ {
+ final String name = vpath + newfile;
+ final File file = new File( dir, newfile );
+ if ( file.isDirectory() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( scanConductor != null )
+ {
+ scanAction = scanConductor.visitDirectory( name, file );
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return;
+ }
+ }
+
+ if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
+ {
+ dirsIncluded.add( name );
+ if ( fast )
+ {
+ scandir( file, name + File.separator, fast );
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
+ {
+ return;
+ }
+ }
+ }
+ scanAction = null;
+
+ }
+ else
+ {
+ dirsExcluded.add( name );
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ scandir( file, name + File.separator, fast );
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
+ {
+ return;
+ }
+ scanAction = null;
+ }
+ }
+ }
+ else
+ {
+ if ( fast && couldHoldIncluded( name ) )
+ {
+ if ( scanConductor != null )
+ {
+ scanAction = scanConductor.visitDirectory( name, file );
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return;
+ }
+ }
+ if ( !ScanConductor.ScanAction.NO_RECURSE.equals( scanAction ) )
+ {
+ dirsNotIncluded.add( name );
+
+ scandir( file, name + File.separator, fast );
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
+ {
+ return;
+ }
+ }
+ scanAction = null;
+ }
+ }
+ if ( !fast )
+ {
+ scandir( file, name + File.separator, fast );
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction ) )
+ {
+ return;
+ }
+ scanAction = null;
+ }
+ }
+ else if ( file.isFile() )
+ {
+ if ( isIncluded( name ) )
+ {
+ if ( !isExcluded( name ) )
+ {
+ if ( scanConductor != null )
+ {
+ scanAction = scanConductor.visitFile( name, file );
+ }
+
+ if ( ScanConductor.ScanAction.ABORT.equals( scanAction )
+ || ScanConductor.ScanAction.ABORT_DIRECTORY.equals( scanAction ) )
+ {
+ return;
+ }
+
+ filesIncluded.add( name );
+ }
+ else
+ {
+ filesExcluded.add( name );
+ }
+ }
+ else
+ {
+ filesNotIncluded.add( name );
+ }
+ }
+ }
+ }
+
+ private String[] doNotFollowSymbolicLinks( final File dir, final String vpath, String[] newfiles )
+ {
+ final List noLinks = new ArrayList();
+ for ( final String newfile : newfiles )
+ {
+ try
+ {
+ if ( isSymbolicLink( dir, newfile ) )
+ {
+ final String name = vpath + newfile;
+ final File file = new File( dir, newfile );
+ if ( file.isDirectory() )
+ {
+ dirsExcluded.add( name );
+ }
+ else
+ {
+ filesExcluded.add( name );
+ }
+ }
+ else
+ {
+ noLinks.add( newfile );
+ }
+ }
+ catch ( final IOException ioe )
+ {
+ final String msg =
+ "IOException caught while checking " + "for links, couldn't get cannonical path!";
+ // will be caught and redirected to Ant's logging system
+ System.err.println( msg );
+ noLinks.add( newfile );
+ }
+ }
+ newfiles = noLinks.toArray( new String[noLinks.size()] );
+ return newfiles;
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one include pattern.
+ *
+ * @param name The name to match. Must not be null.
+ * @return true when the name matches against at least one include pattern, or false
+ * otherwise.
+ */
+ boolean isIncluded( final String name )
+ {
+ return includesPatterns.matches( name, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a name matches the start of at least one include pattern.
+ *
+ * @param name The name to match. Must not be null.
+ * @return true when the name matches against the start of at least one include pattern, or
+ * false otherwise.
+ */
+ boolean couldHoldIncluded( @Nonnull final String name )
+ {
+ return includesPatterns.matchesPatternStart( name, isCaseSensitive );
+ }
+
+ /**
+ * Tests whether or not a name matches against at least one exclude pattern.
+ *
+ * @param name The name to match. Must not be null.
+ * @return true when the name matches against at least one exclude pattern, or false
+ * otherwise.
+ */
+ boolean isExcluded( @Nonnull final String name )
+ {
+ return excludesPatterns.matches( name, isCaseSensitive );
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the include patterns and none of the exclude
+ * patterns. The names are relative to the base directory.
+ *
+ * @deprecated this method does not work correctly on Windows.
+ * @return the names of the files which matched at least one of the include patterns and none of the exclude
+ * patterns. May also contain symbolic links to files.
+ */
+ @Deprecated
+ public String[] getIncludedFiles()
+ {
+ if ( filesIncluded == null )
+ {
+ return new String[0];
+ }
+ return filesIncluded.toArray( new String[filesIncluded.size()] );
+ }
+
+ /**
+ * Returns the names of the files which matched none of the include patterns. The names are relative to the base
+ * directory. This involves performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the files which matched none of the include patterns.
+ * @see #slowScan
+ */
+ public String[] getNotIncludedFiles()
+ {
+ slowScan();
+ return filesNotIncluded.toArray( new String[filesNotIncluded.size()] );
+ }
+
+ /**
+ * Returns the names of the files which matched at least one of the include patterns and at least one of the exclude
+ * patterns. The names are relative to the base directory. This involves performing a slow scan if one has not
+ * already been completed.
+ *
+ * @return the names of the files which matched at least one of the include patterns and at at least one of the
+ * exclude patterns.
+ * @see #slowScan
+ */
+ public String[] getExcludedFiles()
+ {
+ slowScan();
+ return filesExcluded.toArray( new String[filesExcluded.size()] );
+ }
+
+ /**
+ * Returns the names of the directories which matched at least one of the include patterns and none of the exclude
+ * patterns. The names are relative to the base directory.
+ *
+ * @deprecated this method is buggy. Do not depend on it.
+ * @return the names of the directories which matched at least one of the include patterns and none of the exclude
+ * patterns. May also contain symbolic links to directories.
+ */
+ @Deprecated
+ public String[] getIncludedDirectories()
+ {
+ return dirsIncluded.toArray( new String[dirsIncluded.size()] );
+ }
+
+ /**
+ * Returns the names of the directories which matched none of the include patterns. The names are relative to the
+ * base directory. This involves performing a slow scan if one has not already been completed.
+ *
+ * @return the names of the directories which matched none of the include patterns.
+ * @see #slowScan
+ */
+ public String[] getNotIncludedDirectories()
+ {
+ slowScan();
+ return dirsNotIncluded.toArray( new String[dirsNotIncluded.size()] );
+ }
+
+ /**
+ * Returns the names of the directories which matched at least one of the include patterns and at least one of the
+ * exclude patterns. The names are relative to the base directory. This involves performing a slow scan if one has
+ * not already been completed.
+ *
+ * @return the names of the directories which matched at least one of the include patterns and at least one of the
+ * exclude patterns.
+ * @see #slowScan
+ */
+ public String[] getExcludedDirectories()
+ {
+ slowScan();
+ return dirsExcluded.toArray( new String[dirsExcluded.size()] );
+ }
+
+ /**
+ * Adds default exclusions to the current exclusions set.
+ */
+ public void addDefaultExcludes()
+ {
+ final int excludesLength = excludes == null ? 0 : excludes.length;
+ String[] newExcludes;
+ newExcludes = new String[excludesLength + DEFAULTEXCLUDES.length];
+ if ( excludesLength > 0 )
+ {
+ System.arraycopy( excludes, 0, newExcludes, 0, excludesLength );
+ }
+ for ( int i = 0; i < DEFAULTEXCLUDES.length; i++ )
+ {
+ newExcludes[i + excludesLength] =
+ DEFAULTEXCLUDES[i].replace( '/', File.separatorChar ).replace( '\\', File.separatorChar );
+ }
+ excludes = newExcludes;
+ }
+
+ /**
+ * Checks whether a given file is a symbolic link.
+ *
+ * It doesn't really test for symbolic links but whether the canonical and absolute paths of the file are identical
+ * - this may lead to false positives on some platforms.
+ *
+ *
+ * @param parent the parent directory of the file to test
+ * @param name the name of the file to test.
+ *
+ */
+ boolean isSymbolicLink( final File parent, final String name )
+ throws IOException
+ {
+ return Files.isSymbolicLink( parent.toPath() );
+ }
+
+ private void setupDefaultFilters()
+ {
+ if ( includes == null )
+ {
+ // No includes supplied, so set it to 'matches all'
+ includes = new String[1];
+ includes[0] = "**";
+ }
+ if ( excludes == null )
+ {
+ excludes = new String[0];
+ }
+ }
+
+
+ private void setupMatchPatterns()
+ {
+ includesPatterns = MatchPatterns.from( includes );
+ excludesPatterns = MatchPatterns.from( excludes );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryWalkListener.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryWalkListener.java
new file mode 100644
index 000000000..bb142b5d0
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/DirectoryWalkListener.java
@@ -0,0 +1,56 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+
+/**
+ * DirectoryWalkListener.
+ *
+ * @deprecated use {@code java.nio.file.FileVisitor} and related classes
+ */
+@Deprecated
+public interface DirectoryWalkListener
+{
+ /**
+ * The directory walking has begun.
+ *
+ * @param basedir the basedir that walk started in
+ */
+ void directoryWalkStarting( File basedir );
+
+ /**
+ * The included entry that was encountered.
+ *
+ * @param percentage rough percentage of the walk completed. (inaccurate)
+ * @param file the file that was included
+ */
+ void directoryWalkStep( int percentage, File file );
+
+ /**
+ * The directory walking has finished.
+ */
+ void directoryWalkFinished();
+
+ /**
+ * @param message the message for the debugging output
+ */
+ void debug( String message );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/FileUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/FileUtils.java
new file mode 100644
index 000000000..a3be324c1
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/FileUtils.java
@@ -0,0 +1,2146 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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.apache.maven.shared.utils.Os;
+import org.apache.maven.shared.utils.StringUtils;
+
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import javax.annotation.WillClose;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.io.Reader;
+import java.io.Writer;
+import java.net.URL;
+import java.nio.Buffer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.SecureRandom;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * This class provides basic facilities for manipulating files and file paths.
+ *
+ *
Path-related methods
+ *
+ *
Methods exist to retrieve the components of a typical file path. For example
+ * /www/hosted/mysite/index.html, can be broken into:
+ *
+ *
/www/hosted/mysite/index -- retrievable through {@link #removeExtension}
+ *
html -- retrievable through {@link #getExtension}
+ *
+ *
+ *
+ *
File-related methods
+ *
+ * There are methods to create a {@link #toFile File from a URL}, copy a
+ * copy a {@link #copyFile File to another File},
+ * copy a {@link #copyURLToFile URL's contents to a File},
+ * as well as methods to {@link #deleteDirectory(File) delete} and {@link #cleanDirectory(File)
+ * clean} a directory.
+ *
+ *
+ * Common {@link java.io.File} manipulation routines.
+ *
+ * Taken from the commons-utils repo.
+ * Also code from Alexandria's FileUtils.
+ * And from Avalon Excalibur's IO.
+ * And from Ant.
+ *
+ * @author Kevin A. Burton
+ * @author Scott Sanders
+ * @author Daniel Rall
+ * @author Christoph.Reck
+ * @author Peter Donald
+ * @author Jeff Turner
+ *
+ */
+public class FileUtils
+{
+ /**
+ * protected constructor.
+ */
+ protected FileUtils()
+ {
+ // This is a utility class. Normally don't instantiate
+ }
+
+ /**
+ * The number of bytes in a kilobyte.
+ */
+ private static final int ONE_KB = 1024;
+
+ /**
+ * The number of bytes in a megabyte.
+ */
+ private static final int ONE_MB = ONE_KB * ONE_KB;
+
+ /**
+ * The file copy buffer size (30 MB)
+ */
+ private static final int FILE_COPY_BUFFER_SIZE = ONE_MB * 30;
+
+ /**
+ * The vm line separator
+ */
+ private static final String FS = System.getProperty( "file.separator" );
+
+ /**
+ * Non-valid Characters for naming files, folders under Windows: ":", "*", "?", "\"", "<", ">", "|"
+ *
+ * @see
+ * http://support.microsoft.com/?scid=kb%3Ben-us%3B177506&x=12&y=13
+ */
+ private static final String[] INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME = { ":", "*", "?", "\"", "<", ">", "|" };
+
+ /**
+ * @return the default excludes pattern
+ * @see DirectoryScanner#DEFAULTEXCLUDES
+ */
+ @Nonnull public static String[] getDefaultExcludes()
+ {
+ return DirectoryScanner.DEFAULTEXCLUDES;
+ }
+
+ /**
+ * @return the default excludes pattern as list
+ * @see #getDefaultExcludes()
+ */
+ @Nonnull public static List getDefaultExcludesAsList()
+ {
+ return Arrays.asList( getDefaultExcludes() );
+ }
+
+ /**
+ * @return the default excludes pattern as comma separated string.
+ * @see DirectoryScanner#DEFAULTEXCLUDES
+ * @see StringUtils#join(Object[], String)
+ */
+ @Nonnull public static String getDefaultExcludesAsString()
+ {
+ return StringUtils.join( DirectoryScanner.DEFAULTEXCLUDES, "," );
+ }
+
+ /**
+ * Returns the directory path portion of a file specification string.
+ * Matches the equally named unix command.
+ *
+ * @param path the file path
+ * @return the directory portion excluding the ending file separator
+ * @deprecated use {@code Paths.get(path).getParent().getName()}
+ */
+ @Deprecated
+ @Nonnull public static String dirname( @Nonnull String path )
+ {
+ int i = path.lastIndexOf( File.separator );
+ return ( i >= 0 ? path.substring( 0, i ) : "" );
+ }
+
+ /**
+ * Returns the filename portion of a path.
+ *
+ * @param path the file path
+ * @return the filename string with extension
+ * @deprecated use {@code Paths.get(path).getName()}
+ */
+ @Deprecated
+ @Nonnull public static String filename( @Nonnull String path )
+ {
+ int i = path.lastIndexOf( File.separator );
+ return ( i >= 0 ? path.substring( i + 1 ) : path );
+ }
+
+ /**
+ * Returns the extension portion of a file path.
+ * This is everything after the last dot '.' in the path (NOT including the dot).
+ *
+ * @param path the file path
+ * @return the extension of the file
+ * @deprecated use {@code org.apache.commons.io.FilenameUtils.getExtension}
+ */
+ @Deprecated
+ @Nonnull public static String extension( @Nonnull String path )
+ {
+ // Ensure the last dot is after the last file separator
+ int lastSep = path.lastIndexOf( File.separatorChar );
+ int lastDot;
+ if ( lastSep < 0 )
+ {
+ lastDot = path.lastIndexOf( '.' );
+ }
+ else
+ {
+ lastDot = path.substring( lastSep + 1 ).lastIndexOf( '.' );
+ if ( lastDot >= 0 )
+ {
+ lastDot += lastSep + 1;
+ }
+ }
+
+ if ( lastDot >= 0 && lastDot > lastSep )
+ {
+ return path.substring( lastDot + 1 );
+ }
+
+ return "";
+ }
+
+ /**
+ * Check if a file exists.
+ *
+ * @param fileName the file path
+ * @return true if file exists
+ * @deprecated use {@code java.io.File.exists()}
+ */
+ @Deprecated
+ public static boolean fileExists( @Nonnull String fileName )
+ {
+ File file = new File( fileName );
+ return file.exists();
+ }
+
+ /**
+ * Note: the file content is read with platform encoding.
+ *
+ * @param file the file path
+ * @return the file content using the platform encoding
+ * @throws IOException if any
+ * @deprecated use {@code new String(java.nio.files.Files.readAllBytes(file))}
+ */
+ @Deprecated
+ @Nonnull public static String fileRead( @Nonnull String file )
+ throws IOException
+ {
+ return fileRead( file, null );
+ }
+
+ /**
+ * @param file the file path
+ * @param encoding the wanted encoding
+ * @return the file content using the specified encoding
+ * @throws IOException if any
+ * @deprecated use {@code new String(java.nio.files.Files.readAllBytes(Paths.get(file)), encoding)}
+ */
+ @Deprecated
+ @Nonnull private static String fileRead( @Nonnull String file, @Nullable String encoding )
+ throws IOException
+ {
+ return fileRead( new File( file ), encoding );
+ }
+
+ /**
+ * Note: the file content is read with platform encoding.
+ *
+ * @param file the file path
+ * @return the file content using the platform encoding
+ * @throws IOException if any
+ * @deprecated use {@code new String(java.nio.files.Files.readAllBytes(file.toPath()))}
+ */
+ @Deprecated
+ @Nonnull public static String fileRead( @Nonnull File file )
+ throws IOException
+ {
+ return fileRead( file, null );
+ }
+
+ /**
+ * @param file the file path
+ * @param encoding the wanted encoding
+ * @return the file content using the specified encoding
+ * @throws IOException if any
+ * @deprecated use {@code new String(java.nio.files.Files.readAllBytes(file.toPath()), encoding)}
+ */
+ @Deprecated
+ @Nonnull public static String fileRead( @Nonnull File file, @Nullable String encoding )
+ throws IOException
+ {
+ Charset charset = charset( encoding );
+
+ StringBuilder buf = new StringBuilder();
+
+
+ try ( Reader reader = Files.newBufferedReader( file.toPath(), charset ) )
+ {
+ int count;
+ char[] b = new char[512];
+ while ( ( count = reader.read( b ) ) >= 0 ) // blocking read
+ {
+ buf.append( b, 0, count );
+ }
+ }
+
+ return buf.toString();
+ }
+
+ /**
+ * @param file the file path
+ * @return the file content lines as String[] using the system default encoding.
+ * An empty List if the file doesn't exist.
+ * @throws IOException in case of failure
+ * @deprecated use {@code java.nio.files.Files.readAllLines()}
+ */
+ @Deprecated
+ @Nonnull public static String[] fileReadArray( @Nonnull File file )
+ throws IOException
+ {
+ List lines = loadFile( file );
+
+ return lines.toArray( new String[lines.size()] );
+ }
+
+ /**
+ * Appends data to a file. The file is created if it does not exist.
+ * Note: the data is written with platform encoding.
+ *
+ * @param fileName the path of the file to write
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(),
+ * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileAppend( @Nonnull String fileName, @Nonnull String data )
+ throws IOException
+ {
+ fileAppend( fileName, null, data );
+ }
+
+ /**
+ * Appends data to a file. The file will be created if it does not exist.
+ *
+ * @param fileName the path of the file to write
+ * @param encoding the encoding of the file
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(filename, data.getBytes(encoding),
+ * StandardOpenOption.APPEND, StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileAppend( @Nonnull String fileName, @Nullable String encoding, @Nonnull String data )
+ throws IOException
+ {
+ Charset charset = charset( encoding );
+
+ try ( OutputStream out = new FileOutputStream( fileName, true ) )
+ {
+ out.write( data.getBytes( charset ) );
+ }
+ }
+
+ /**
+ * Writes data to a file. The file will be created if it does not exist.
+ * Note: the data is written with platform encoding
+ *
+ * @param fileName the path of the file to write
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(filename,
+ * data.getBytes(), StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileWrite( @Nonnull String fileName, @Nonnull String data )
+ throws IOException
+ {
+ fileWrite( fileName, null, data );
+ }
+
+ /**
+ * Writes data to a file. The file will be created if it does not exist.
+ *
+ * @param fileName the path of the file to write
+ * @param encoding the encoding of the file
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(Paths.get(filename),
+ * data.getBytes(encoding), StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileWrite( @Nonnull String fileName, @Nullable String encoding, @Nonnull String data )
+ throws IOException
+ {
+ File file = new File( fileName );
+ fileWrite( file, encoding, data );
+ }
+
+ /**
+ * Writes data to a file. The file will be created if it does not exist.
+ *
+ * @param file the path of the file to write
+ * @param encoding the encoding of the file
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(file.toPath(),
+ * data.getBytes(encoding), StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileWrite( @Nonnull File file, @Nullable String encoding, @Nonnull String data )
+ throws IOException
+ {
+ Charset charset = charset( encoding );
+
+ try ( Writer writer = Files.newBufferedWriter( file.toPath(), charset ) )
+ {
+ writer.write( data );
+ }
+ }
+
+ /**
+ * Writes String array data to a file in the systems default encoding.
+ * The file will be created if it does not exist.
+ *
+ * @param file the path of the file to write
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(file.toPath(),
+ * data.getBytes(encoding), StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileWriteArray( @Nonnull File file, @Nullable String... data )
+ throws IOException
+ {
+ fileWriteArray( file, null, data );
+ }
+
+ /**
+ * Writes String array data to a file. The file is created if it does not exist.
+ *
+ * @param file the path of the file to write
+ * @param encoding the encoding of the file
+ * @param data the content to write to the file
+ * @throws IOException if any
+ * @deprecated use {@code java.nio.files.Files.write(file.toPath(),
+ * data.getBytes(encoding), StandardOpenOption.CREATE)}
+ */
+ @Deprecated
+ public static void fileWriteArray( @Nonnull File file, @Nullable String encoding, @Nullable String... data )
+ throws IOException
+ {
+ Charset charset = charset( encoding );
+
+ try ( Writer writer = Files.newBufferedWriter( file.toPath(), charset ) )
+ {
+ for ( int i = 0; data != null && i < data.length; i++ )
+ {
+ writer.write( data[i] );
+ if ( i < data.length )
+ {
+ writer.write( "\n" );
+ }
+ }
+ }
+ }
+
+ /**
+ * Deletes a file.
+ *
+ * @param fileName the path of the file to delete
+ * @deprecated use {@code Files.delete(Paths.get(fileName))}
+ */
+ @Deprecated
+ public static void fileDelete( @Nonnull String fileName )
+ {
+ File file = new File( fileName );
+ //noinspection ResultOfMethodCallIgnored
+ deleteLegacyStyle( file );
+ }
+
+ /**
+ * Given a directory and an array of extensions return an array of compliant files.
+ *
+ * The given extensions should be like "java" and not like ".java".
+ *
+ * @param directory the path of the directory
+ * @param extensions an array of expected extensions
+ * @return an array of files for the wanted extensions
+ */
+ public static String[] getFilesFromExtension( @Nonnull String directory, @Nonnull String... extensions )
+ {
+ List files = new ArrayList();
+
+ File currentDir = new File( directory );
+
+ String[] unknownFiles = currentDir.list();
+
+ if ( unknownFiles == null )
+ {
+ return new String[0];
+ }
+
+ for ( String unknownFile : unknownFiles )
+ {
+ String currentFileName = directory + System.getProperty( "file.separator" ) + unknownFile;
+ File currentFile = new File( currentFileName );
+
+ if ( currentFile.isDirectory() )
+ {
+ // ignore all CVS directories...
+ if ( currentFile.getName().equals( "CVS" ) )
+ {
+ continue;
+ }
+
+ // ok... traverse into this directory and get all the files... then combine
+ // them with the current list.
+
+ String[] fetchFiles = getFilesFromExtension( currentFileName, extensions );
+ files = blendFilesToList( files, fetchFiles );
+ }
+ else
+ {
+ // ok... add the file
+
+ String add = currentFile.getAbsolutePath();
+ if ( isValidFile( add, extensions ) )
+ {
+ files.add( add );
+ }
+ }
+ }
+
+ // ok... move the Vector into the files list...
+ String[] foundFiles = new String[files.size()];
+ files.toArray( foundFiles );
+
+ return foundFiles;
+ }
+
+ /**
+ * Private helper method for getFilesFromExtension()
+ */
+ @Nonnull private static List blendFilesToList( @Nonnull List v, @Nonnull String... files )
+ {
+ Collections.addAll( v, files );
+
+ return v;
+ }
+
+ /**
+ * Checks to see if a file is of a particular type(s).
+ * Note that if the file does not have an extension, an empty string
+ * ("") is matched for.
+ */
+ private static boolean isValidFile( @Nonnull String file, @Nonnull String... extensions )
+ {
+ String extension = extension( file );
+
+ // ok.. now that we have the "extension" go through the current know
+ // excepted extensions and determine if this one is OK.
+
+ for ( String extension1 : extensions )
+ {
+ if ( extension1.equals( extension ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+
+ }
+
+ /**
+ * Simple way to make a directory.
+ *
+ * @param dir the directory to create
+ * @throws IllegalArgumentException if the dir contains illegal Windows characters under Windows OS
+ * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
+ * @deprecated use {@code java.nio.file.Files.createDirectories(Paths.get(dir))}
+ */
+ @Deprecated
+ public static void mkdir( @Nonnull String dir )
+ {
+ File file = new File( dir );
+
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) )
+ {
+ throw new IllegalArgumentException(
+ "The file (" + dir + ") cannot contain any of the following characters: \n" + StringUtils.join(
+ INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) );
+ }
+
+ if ( !file.exists() )
+ {
+ //noinspection ResultOfMethodCallIgnored
+ file.mkdirs();
+ }
+ }
+
+ /**
+ * Compare the contents of two files to determine if they are equal or not.
+ *
+ * @param file1 the first file
+ * @param file2 the second file
+ * @return true if the content of the files are equal or they both don't exist, false otherwise
+ * @throws IOException if any
+ */
+ public static boolean contentEquals( @Nonnull final File file1, @Nonnull final File file2 )
+ throws IOException
+ {
+ final boolean file1Exists = file1.exists();
+ if ( file1Exists != file2.exists() )
+ {
+ return false;
+ }
+
+ if ( !file1Exists )
+ {
+ // two not existing files are equal
+ return true;
+ }
+
+ if ( file1.isDirectory() || file2.isDirectory() )
+ {
+ // don't want to compare directory contents
+ return false;
+ }
+
+ try ( InputStream input1 = new FileInputStream( file1 );
+ InputStream input2 = new FileInputStream( file2 ) )
+ {
+ return IOUtil.contentEquals( input1, input2 );
+ }
+ }
+
+ /**
+ * Convert from a URL to a File.
+ *
+ * @param url file URL
+ * @return the equivalent File object, or null if the URL's protocol
+ * is not file
+ */
+ @Nullable public static File toFile( @Nullable final URL url )
+ {
+ if ( url == null || !url.getProtocol().equalsIgnoreCase( "file" ) )
+ {
+ return null;
+ }
+
+ String filename = url.getFile().replace( '/', File.separatorChar );
+ int pos = -1;
+ while ( ( pos = filename.indexOf( '%', pos + 1 ) ) >= 0 )
+ {
+ if ( pos + 2 < filename.length() )
+ {
+ String hexStr = filename.substring( pos + 1, pos + 3 );
+ char ch = (char) Integer.parseInt( hexStr, 16 );
+ filename = filename.substring( 0, pos ) + ch + filename.substring( pos + 3 );
+ }
+ }
+ return new File( filename );
+ }
+
+ /**
+ * Convert the array of Files into a list of URLs.
+ *
+ * @param files the array of files
+ * @return the array of URLs
+ * @throws IOException if an error occurs
+ */
+ @Nonnull public static URL[] toURLs( @Nonnull final File... files )
+ throws IOException
+ {
+ final URL[] urls = new URL[files.length];
+
+ for ( int i = 0; i < urls.length; i++ )
+ {
+ urls[i] = files[i].toURI().toURL();
+ }
+
+ return urls;
+ }
+
+ /**
+ * Remove extension from a path. E.g.
+ *
+ *
+ * @param filename the path of the file
+ * @return the extension of filename or "" if none
+ * @deprecated use {@code org.apache.commons.io.FilenameUtils.getExtension()}
+ */
+ @Deprecated
+ @Nonnull public static String getExtension( @Nonnull final String filename )
+ {
+ return extension( filename );
+ }
+
+ /**
+ * Copy file from source to destination. If destinationDirectory does not exist, it
+ * (and any parent directories) will be created. If a file source in
+ * destinationDirectory exists, it will be overwritten.
+ *
+ * @param source an existing File to copy
+ * @param destinationDirectory a directory to copy source into
+ * @throws java.io.FileNotFoundException if source isn't a normal file
+ * @throws IllegalArgumentException if destinationDirectory isn't a directory
+ * @throws IOException if source does not exist, the file in
+ * destinationDirectory cannot be written to, or an IO error
+ * occurs during copying.
+ * @deprecated use {@code org.apache.commons.io.FileUtils.copyFileToDirectory()}
+ */
+ @Deprecated
+ public static void copyFileToDirectory( @Nonnull final File source, @Nonnull final File destinationDirectory )
+ throws IOException
+ {
+ if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
+ {
+ throw new IllegalArgumentException( "Destination is not a directory" );
+ }
+
+ copyFile( source, new File( destinationDirectory, source.getName() ) );
+ }
+
+ /**
+ * Copy file from source to destination only if source is newer than the target file.
+ * If destinationDirectory does not exist, it
+ * (and any parent directories) is created. If a file source in
+ * destinationDirectory exists, it is overwritten.
+ *
+ * @param source an existing File to copy
+ * @param destinationDirectory a directory to copy source into
+ * @throws java.io.FileNotFoundException if source isn't a normal file
+ * @throws IllegalArgumentException if destinationDirectory isn't a directory
+ * @throws IOException if source does not exist, the file in
+ * destinationDirectory cannot be written to, or an IO error
+ * occurs during copying
+ */
+ private static void copyFileToDirectoryIfModified( @Nonnull final File source,
+ @Nonnull final File destinationDirectory )
+ throws IOException
+ {
+ if ( destinationDirectory.exists() && !destinationDirectory.isDirectory() )
+ {
+ throw new IllegalArgumentException( "Destination is not a directory" );
+ }
+
+ copyFileIfModified( source, new File( destinationDirectory, source.getName() ) );
+ }
+
+
+ /**
+ * Copy file from source to destination. The directories up to destination will be
+ * created if they don't already exist. destination will be overwritten if it
+ * already exists.
+ *
+ * @param source an existing non-directory File to copy bytes from
+ * @param destination a non-directory File to write bytes to (possibly
+ * overwriting)
+ * @throws IOException if source does not exist, destination cannot be
+ * written to, or an IO error occurs during copying
+ * @throws java.io.FileNotFoundException if destination is a directory
+ * @deprecated use {@code java.nio.Files.copy(source.toPath(), destination.toPath(), CopyOptions.NOFOLLOW_LINKS,
+ * CopyOptions.REPLACE_EXISTING)}
+ */
+ @Deprecated
+ public static void copyFile( @Nonnull final File source, @Nonnull final File destination )
+ throws IOException
+ {
+ //check source exists
+ if ( !source.exists() )
+ {
+ final String message = "File " + source + " does not exist";
+ throw new IOException( message );
+ }
+ if ( Files.isSymbolicLink( source.toPath() ) )
+ {
+ File target = Files.readSymbolicLink( source.toPath() ).toFile();
+ createSymbolicLink( destination, target );
+ return;
+ }
+
+ //check source != destination, see PLXUTILS-10
+ if ( source.getCanonicalPath().equals( destination.getCanonicalPath() ) )
+ {
+ //if they are equal, we can exit the method without doing any work
+ return;
+ }
+
+ mkdirsFor( destination );
+
+ doCopyFile( source, destination );
+
+ if ( source.length() != destination.length() )
+ {
+ final String message = "Failed to copy full contents from " + source + " to " + destination;
+ throw new IOException( message );
+ }
+ }
+
+ private static void mkdirsFor( @Nonnull File destination )
+ {
+ //does destination directory exist ?
+ if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
+ {
+ //noinspection ResultOfMethodCallIgnored
+ destination.getParentFile().mkdirs();
+ }
+ }
+
+ private static void doCopyFile( @Nonnull File source, @Nonnull File destination )
+ throws IOException
+ {
+
+ try ( FileInputStream fis = new FileInputStream( source );
+ FileOutputStream fos = new FileOutputStream( destination );
+ FileChannel input = fis.getChannel();
+ FileChannel output = fos.getChannel() )
+ {
+
+ long size = input.size();
+ long pos = 0;
+ long count;
+ while ( pos < size )
+ {
+ count = size - pos > FILE_COPY_BUFFER_SIZE ? FILE_COPY_BUFFER_SIZE : size - pos;
+ pos += output.transferFrom( input, pos, count );
+ }
+ }
+
+ copyFilePermissions( source, destination );
+ }
+
+ /**
+ * Copy file from source to destination only if source timestamp is later than the destination timestamp.
+ * The directories up to destination will be created if they don't already exist.
+ * destination will be overwritten if it already exists.
+ *
+ * @param source An existing non-directory File to copy bytes from.
+ * @param destination A non-directory File to write bytes to (possibly
+ * overwriting).
+ * @return true if no problem occured
+ * @throws IOException if source does not exist, destination cannot be
+ * written to, or an IO error occurs during copying.
+ */
+ private static boolean copyFileIfModified( @Nonnull final File source, @Nonnull final File destination )
+ throws IOException
+ {
+ if ( destination.lastModified() < source.lastModified() )
+ {
+ copyFile( source, destination );
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Copies bytes from the URL source to a file destination.
+ * The directories up to destination will be created if they don't already exist.
+ * destination will be overwritten if it already exists.
+ *
+ * @param source A URL to copy bytes from.
+ * @param destination A non-directory File to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if
+ *
+ *
source URL cannot be opened
+ *
destination cannot be written to
+ *
an IO error occurs during copying
+ *
+ * @deprecated use {@code java.nio.Files.copy(source.openStream(), destination.toPath(),
+ * CopyOptions.REPLACE_EXISTING)}
+ */
+ public static void copyURLToFile( @Nonnull final URL source, @Nonnull final File destination )
+ throws IOException
+ {
+ copyStreamToFile( source.openStream(), destination );
+ }
+
+ /**
+ * Copies bytes from the {@link InputStream} source to a file destination.
+ * The directories up to destination will be created if they don't already exist.
+ * destination will be overwritten if it already exists.
+ *
+ * @param source An {@link InputStream} to copy bytes from. This stream is
+ * guaranteed to be closed.
+ * @param destination A non-directory File to write bytes to (possibly
+ * overwriting).
+ * @throws IOException if
+ *
+ *
source cannot be opened
+ *
destination cannot be written to
+ *
an I/O error occurs during copying
+ *
+ * @deprecated use {@code java.nio.Files.copy(source, destination.toPath(),
+ * CopyOptions.REPLACE_EXISTING)}
+ */
+ @Deprecated
+ private static void copyStreamToFile( @Nonnull @WillClose final InputStream source,
+ @Nonnull final File destination )
+ throws IOException
+ {
+ // does destination directory exist ?
+ if ( destination.getParentFile() != null && !destination.getParentFile().exists() )
+ {
+ // noinspection ResultOfMethodCallIgnored
+ destination.getParentFile().mkdirs();
+ }
+
+ // make sure we can write to destination
+ if ( destination.exists() && !destination.canWrite() )
+ {
+ final String message = "Unable to open file " + destination + " for writing.";
+ throw new IOException( message );
+ }
+
+ try ( OutputStream out = new FileOutputStream( destination ); InputStream in = source )
+ {
+ IOUtil.copy( in, out );
+ }
+ }
+
+ /**
+ * Normalize a path.
+ * Eliminates "/../" and "/./" in a string. Returns null if the ..'s went past the
+ * root.
+ * Eg:
+ *
+ *
+ * @param path the path to normalize
+ * @return the normalized String, or null if too many ..'s.
+ * @deprecated use {@code org.apache.commons.io.FileNameUtils.normalize()}
+ */
+ @Deprecated
+ @Nonnull public static String normalize( @Nonnull final String path )
+ {
+ String normalized = path;
+ // Resolve occurrences of "//" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "//" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 1 );
+ }
+
+ // Resolve occurrences of "/./" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/./" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ normalized = normalized.substring( 0, index ) + normalized.substring( index + 2 );
+ }
+
+ // Resolve occurrences of "/../" in the normalized path
+ while ( true )
+ {
+ int index = normalized.indexOf( "/../" );
+ if ( index < 0 )
+ {
+ break;
+ }
+ if ( index == 0 )
+ {
+ return null; // Trying to go outside our context
+ }
+ int index2 = normalized.lastIndexOf( '/', index - 1 );
+ normalized = normalized.substring( 0, index2 ) + normalized.substring( index + 3 );
+ }
+
+ // Return the normalized path that we have completed
+ return normalized;
+ }
+
+ /**
+ * Resolve a file filename to it's canonical form. If filename is
+ * relative (doesn't start with /), it will be resolved relative to
+ * baseFile, otherwise it is treated as a normal root-relative path.
+ *
+ * @param baseFile Where to resolve filename from, if filename is
+ * relative.
+ * @param filename absolute or relative file path to resolve
+ * @return the canonical File of filename
+ */
+ @Nonnull public static File resolveFile( final File baseFile, @Nonnull String filename )
+ {
+ String filenm = filename;
+ if ( '/' != File.separatorChar )
+ {
+ filenm = filename.replace( '/', File.separatorChar );
+ }
+
+ if ( '\\' != File.separatorChar )
+ {
+ filenm = filename.replace( '\\', File.separatorChar );
+ }
+
+ // deal with absolute files
+ if ( filenm.startsWith( File.separator ) || ( Os.isFamily( Os.FAMILY_WINDOWS ) && filenm.indexOf( ":" ) > 0 ) )
+ {
+ File file = new File( filenm );
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ // nop
+ }
+
+ return file;
+ }
+ // FIXME: I'm almost certain this // removal is unnecessary, as getAbsoluteFile() strips
+ // them. However, I'm not sure about this UNC stuff. (JT)
+ final char[] chars = filename.toCharArray();
+ final StringBuilder sb = new StringBuilder();
+
+ //remove duplicate file separators in succession - except
+ //on win32 at start of filename as UNC filenames can
+ //be \\AComputer\AShare\myfile.txt
+ int start = 0;
+ if ( '\\' == File.separatorChar )
+ {
+ sb.append( filenm.charAt( 0 ) );
+ start++;
+ }
+
+ for ( int i = start; i < chars.length; i++ )
+ {
+ final boolean doubleSeparator = File.separatorChar == chars[i] && File.separatorChar == chars[i - 1];
+
+ if ( !doubleSeparator )
+ {
+ sb.append( chars[i] );
+ }
+ }
+
+ filenm = sb.toString();
+
+ //must be relative
+ File file = ( new File( baseFile, filenm ) ).getAbsoluteFile();
+
+ try
+ {
+ file = file.getCanonicalFile();
+ }
+ catch ( final IOException ioe )
+ {
+ // nop
+ }
+
+ return file;
+ }
+
+ /**
+ * Delete a file. If file is directory, delete it and all sub-directories.
+ *
+ * @param file the file path
+ * @throws IOException if any
+ * @deprecated use {@code org.apache.commons.io.FileUtils.deleteQuietly()}
+ */
+ @Deprecated
+ public static void forceDelete( @Nonnull final String file )
+ throws IOException
+ {
+ forceDelete( new File( file ) );
+ }
+
+ /**
+ * Delete a file. If file is directory, delete it and all sub-directories.
+ *
+ * @param file a file
+ * @throws IOException if any
+ * @deprecated use {@code org.apache.commons.io.FileUtils.deleteQuietly()}
+ */
+ @Deprecated
+ public static void forceDelete( @Nonnull final File file )
+ throws IOException
+ {
+ if ( file.isDirectory() )
+ {
+ deleteDirectory( file );
+ }
+ else
+ {
+ /*
+ * NOTE: Always try to delete the file even if it appears to be non-existent. This will ensure that a
+ * symlink whose target does not exist is deleted, too.
+ */
+ boolean filePresent = file.getCanonicalFile().exists();
+ if ( !deleteFile( file ) && filePresent )
+ {
+ final String message = "File " + file + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Deletes a file.
+ *
+ * @param file the file to delete
+ * @throws IOException if the file cannot be deleted
+ * @deprecated use {@code java.nio.files.Files.delete(file.toPath())}
+ */
+ @Deprecated
+ public static void delete( @Nonnull File file )
+ throws IOException
+ {
+ Files.delete( file.toPath() );
+ }
+
+ /**
+ * @param file the file
+ * @return true / false
+ * @deprecated use {@code java.nio.files.Files.delete(file.toPath())}
+ */
+ @Deprecated
+ public static boolean deleteLegacyStyle( @Nonnull File file )
+ {
+ try
+ {
+ Files.delete( file.toPath() );
+ return true;
+ }
+ catch ( IOException e )
+ {
+ return false;
+ }
+ }
+
+ /**
+ * Accommodate Windows bug encountered in both Sun and IBM JDKs.
+ * Others possible. If the delete does not work, call System.gc(),
+ * wait a little and try again.
+ *
+ * @param file a file
+ * @throws IOException if any
+ */
+ private static boolean deleteFile( @Nonnull File file )
+ throws IOException
+ {
+ if ( file.isDirectory() )
+ {
+ throw new IOException( "File " + file + " isn't a file." );
+ }
+
+ if ( !deleteLegacyStyle( file ) )
+ {
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ file = file.getCanonicalFile();
+ }
+
+ try
+ {
+ Thread.sleep( 10 );
+ return deleteLegacyStyle( file );
+ }
+ catch ( InterruptedException ex )
+ {
+ return deleteLegacyStyle( file );
+ }
+ }
+
+ return true;
+ }
+
+
+ /**
+ * Make a directory.
+ *
+ * @param file not null
+ * @throws IOException if a file already exists with the specified name or the directory is unable to be created
+ * @throws IllegalArgumentException if the file contains illegal Windows characters under Windows OS.
+ * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
+ */
+ public static void forceMkdir( @Nonnull final File file )
+ throws IOException
+ {
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) && !isValidWindowsFileName( file ) )
+ {
+ throw new IllegalArgumentException(
+ "The file (" + file.getAbsolutePath() + ") cannot contain any of the following characters: \n"
+ + StringUtils.join( INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME, " " ) );
+ }
+
+ if ( file.exists() )
+ {
+ if ( file.isFile() )
+ {
+ final String message =
+ "File " + file + " exists and is " + "not a directory. Unable to create directory.";
+ throw new IOException( message );
+ }
+ }
+ else
+ {
+ if ( !file.mkdirs() )
+ {
+ final String message = "Unable to create directory " + file;
+ throw new IOException( message );
+ }
+ }
+ }
+
+ /**
+ * Recursively delete a directory.
+ *
+ * @param directory a directory
+ * @throws IOException if any
+ * @deprecated use {@code org.apache.commons.io.FileUtils.deleteDirectory()}
+ */
+ @Deprecated
+ public static void deleteDirectory( @Nonnull final String directory )
+ throws IOException
+ {
+ deleteDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively delete a directory.
+ *
+ * @param directory a directory
+ * @throws IOException if any
+ * @deprecated use {@code org.apache.commons.io.FileUtils.deleteDirectory()}
+ */
+ @Deprecated
+ public static void deleteDirectory( @Nonnull final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ return;
+ }
+
+ /* try delete the directory before its contents, which will take
+ * care of any directories that are really symbolic links.
+ */
+ if ( deleteLegacyStyle( directory ) )
+ {
+ return;
+ }
+
+ cleanDirectory( directory );
+ if ( !deleteLegacyStyle( directory ) )
+ {
+ final String message = "Directory " + directory + " unable to be deleted.";
+ throw new IOException( message );
+ }
+ }
+
+ /**
+ * Remove all files from a directory without deleting it.
+ *
+ * @param directory a directory
+ * @throws IOException if any. This can leave cleaning in a half-finished state where
+ * some but not all files have been deleted.
+ * @deprecated use {@code org.apache.commons.io.FileUtils.cleanDirectory()}
+ */
+ @Deprecated
+ public static void cleanDirectory( @Nonnull final File directory )
+ throws IOException
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ IOException exception = null;
+
+ final File[] files = directory.listFiles();
+
+ if ( files == null )
+ {
+ return;
+ }
+
+ for ( final File file : files )
+ {
+ try
+ {
+ forceDelete( file );
+ }
+ catch ( final IOException ioe )
+ {
+ exception = ioe;
+ }
+ }
+
+ if ( null != exception )
+ {
+ throw exception;
+ }
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @param directory a directory
+ * @return size of directory in bytes
+ * @deprecated use {@code org.apache.commons.io.FileUtils.sizeOf()}
+ */
+ @Deprecated
+ public static long sizeOfDirectory( @Nonnull final String directory )
+ {
+ return sizeOfDirectory( new File( directory ) );
+ }
+
+ /**
+ * Recursively count size of a directory.
+ *
+ * @param directory a directory
+ * @return size of directory in bytes
+ * @deprecated use {@code org.apache.commons.io.FileUtils.sizeOf()}
+ */
+ @Deprecated
+ public static long sizeOfDirectory( @Nonnull final File directory )
+ {
+ if ( !directory.exists() )
+ {
+ final String message = directory + " does not exist";
+ throw new IllegalArgumentException( message );
+ }
+
+ if ( !directory.isDirectory() )
+ {
+ final String message = directory + " is not a directory";
+ throw new IllegalArgumentException( message );
+ }
+
+ long size = 0;
+
+ final File[] files = directory.listFiles();
+ if ( files == null )
+ {
+ throw new IllegalArgumentException( "Problems reading directory" );
+ }
+
+ for ( final File file : files )
+ {
+ if ( file.isDirectory() )
+ {
+ size += sizeOfDirectory( file );
+ }
+ else
+ {
+ size += file.length();
+ }
+ }
+
+ return size;
+ }
+
+ /**
+ * Return the files contained in the directory, using inclusion and exclusion Ant patterns,
+ * including the directory name in each of the files
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @return a list of File objects
+ * @throws IOException in case of failure.
+ * @see #getFileNames(File, String, String, boolean)
+ */
+ @Nonnull
+ public static List getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes )
+ throws IOException
+ {
+ return getFiles( directory, includes, excludes, true );
+ }
+
+ /**
+ * Return the files contained in the directory, using inclusion and exclusion Ant patterns
+ *
+ * @param directory the directory to scan
+ * @param includes the includes pattern, comma separated
+ * @param excludes the excludes pattern, comma separated
+ * @param includeBasedir true to include the base dir in each file
+ * @return a list of File objects
+ * @throws IOException in case of failure.
+ * @see #getFileNames(File, String, String, boolean)
+ */
+ @Nonnull
+ public static List getFiles( @Nonnull File directory, @Nullable String includes, @Nullable String excludes,
+ boolean includeBasedir )
+ throws IOException
+ {
+ List fileNames = getFileNames( directory, includes, excludes, includeBasedir );
+
+ List files = new ArrayList();
+
+ for ( String filename : fileNames )
+ {
+ files.add( new File( filename ) );
+ }
+
+ return files;
+ }
+
+ /**
+ * Return a list of files as String depending options.
+ * This method use case sensitive file name.
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @param includeBasedir true to include the base directory in each String of file
+ * @return a list of file names
+ * @throws IOException in case of failure
+ */
+ @Nonnull public static List getFileNames( @Nonnull File directory, @Nullable String includes,
+ @Nullable String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ return getFileNames( directory, includes, excludes, includeBasedir, true );
+ }
+
+ /**
+ * Return a list of files as String depending options.
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @param includeBasedir true to include the base dir in each String of file
+ * @param isCaseSensitive true if case sensitive
+ * @return a list of files as String
+ * @throws IOException
+ */
+ @Nonnull private static List getFileNames( @Nonnull File directory, @Nullable String includes,
+ @Nullable String excludes, boolean includeBasedir,
+ boolean isCaseSensitive )
+ throws IOException
+ {
+ return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, true, false );
+ }
+
+ /**
+ * Return a list of directories as String depending options.
+ * This method use case sensitive file name.
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @param includeBasedir true to include the base dir in each String of file
+ * @return a list of directories as String
+ * @throws IOException in case of failure.
+ */
+ @Nonnull public static List getDirectoryNames( @Nonnull File directory, @Nullable String includes,
+ @Nullable String excludes, boolean includeBasedir )
+ throws IOException
+ {
+ return getDirectoryNames( directory, includes, excludes, includeBasedir, true );
+ }
+
+ /**
+ * Return a list of directories as Strings.
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @param includeBasedir true to include the base directory in each String of file
+ * @param isCaseSensitive true if case sensitive
+ * @return a list of directories as String
+ * @throws IOException in case of failure
+ */
+ @Nonnull public static List getDirectoryNames( @Nonnull File directory, @Nullable String includes,
+ @Nullable String excludes, boolean includeBasedir,
+ boolean isCaseSensitive )
+ throws IOException
+ {
+ return getFileAndDirectoryNames( directory, includes, excludes, includeBasedir, isCaseSensitive, false, true );
+ }
+
+ /**
+ * Return a list of file names as Strings.
+ *
+ * @param directory the directory to scan
+ * @param includes the Ant includes pattern, comma separated
+ * @param excludes the Ant excludes pattern, comma separated
+ * @param includeBasedir true to include the base directory in each String of file
+ * @param isCaseSensitive true if case sensitive
+ * @param getFiles true to include regular files
+ * @param getDirectories true to include directories
+ * @return a list of file names
+ */
+ @Nonnull public static List getFileAndDirectoryNames( File directory, @Nullable String includes,
+ @Nullable String excludes, boolean includeBasedir,
+ boolean isCaseSensitive, boolean getFiles,
+ boolean getDirectories )
+ {
+ DirectoryScanner scanner = new DirectoryScanner();
+
+ scanner.setBasedir( directory );
+
+ if ( includes != null )
+ {
+ scanner.setIncludes( StringUtils.split( includes, "," ) );
+ }
+
+ if ( excludes != null )
+ {
+ scanner.setExcludes( StringUtils.split( excludes, "," ) );
+ }
+
+ scanner.setCaseSensitive( isCaseSensitive );
+
+ scanner.scan();
+
+ List list = new ArrayList();
+
+ if ( getFiles )
+ {
+ String[] files = scanner.getIncludedFiles();
+
+ for ( String file : files )
+ {
+ if ( includeBasedir )
+ {
+ list.add( directory + FileUtils.FS + file );
+ }
+ else
+ {
+ list.add( file );
+ }
+ }
+ }
+
+ if ( getDirectories )
+ {
+ String[] directories = scanner.getIncludedDirectories();
+
+ for ( String directory1 : directories )
+ {
+ if ( includeBasedir )
+ {
+ list.add( directory + FileUtils.FS + directory1 );
+ }
+ else
+ {
+ list.add( directory1 );
+ }
+ }
+ }
+
+ return list;
+ }
+
+ /**
+ * Copy the contents of a directory into another one.
+ *
+ * @param sourceDirectory the source directory
+ * @param destinationDirectory the target directory
+ * @throws IOException if any
+ */
+ public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
+ throws IOException
+ {
+ copyDirectory( sourceDirectory, destinationDirectory, "**", null );
+ }
+
+ /**
+ * Copy the contents of a directory into another one.
+ *
+ * @param sourceDirectory the source directory
+ * @param destinationDirectory the target directory
+ * @param includes Ant include pattern
+ * @param excludes Ant exclude pattern
+ * @throws IOException if any
+ * @see #getFiles(File, String, String)
+ */
+ public static void copyDirectory( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
+ @Nullable String includes, @Nullable String excludes )
+ throws IOException
+ {
+ if ( !sourceDirectory.exists() )
+ {
+ return;
+ }
+
+ List files = getFiles( sourceDirectory, includes, excludes );
+
+ for ( File file : files )
+ {
+ copyFileToDirectory( file, destinationDirectory );
+ }
+ }
+
+ /**
+ * Copies an entire directory structure.
+ *
+ * Note:
+ *
+ *
It will include empty directories.
+ *
The sourceDirectory must exist.
+ *
+ *
+ * @param sourceDirectory the source dir
+ * @param destinationDirectory the target dir
+ * @throws IOException if any
+ * @deprecated use {@code org.apache.commons.io.FileUtils.copyDirectory()}
+ */
+ @Deprecated
+ public static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory )
+ throws IOException
+ {
+ copyDirectoryStructure( sourceDirectory, destinationDirectory, destinationDirectory, false );
+ }
+
+ private static void copyDirectoryStructure( @Nonnull File sourceDirectory, @Nonnull File destinationDirectory,
+ File rootDestinationDirectory, boolean onlyModifiedFiles )
+ throws IOException
+ {
+ //noinspection ConstantConditions
+ if ( sourceDirectory == null )
+ {
+ throw new IOException( "source directory can't be null." );
+ }
+
+ //noinspection ConstantConditions
+ if ( destinationDirectory == null )
+ {
+ throw new IOException( "destination directory can't be null." );
+ }
+
+ if ( sourceDirectory.equals( destinationDirectory ) )
+ {
+ throw new IOException( "source and destination are the same directory." );
+ }
+
+ if ( !sourceDirectory.exists() )
+ {
+ throw new IOException( "Source directory doesn't exists (" + sourceDirectory.getAbsolutePath() + ")." );
+ }
+
+ File[] files = sourceDirectory.listFiles();
+
+ if ( files == null )
+ {
+ return;
+ }
+
+ String sourcePath = sourceDirectory.getAbsolutePath();
+
+ for ( File file : files )
+ {
+ if ( file.equals( rootDestinationDirectory ) )
+ {
+ // We don't copy the destination directory in itself
+ continue;
+ }
+
+ String dest = file.getAbsolutePath();
+
+ dest = dest.substring( sourcePath.length() + 1 );
+
+ File destination = new File( destinationDirectory, dest );
+
+ if ( file.isFile() )
+ {
+ destination = destination.getParentFile();
+
+ if ( onlyModifiedFiles )
+ {
+ copyFileToDirectoryIfModified( file, destination );
+ }
+ else
+ {
+ copyFileToDirectory( file, destination );
+ }
+ }
+ else if ( file.isDirectory() )
+ {
+ if ( !destination.exists() && !destination.mkdirs() )
+ {
+ throw new IOException(
+ "Could not create destination directory '" + destination.getAbsolutePath() + "'." );
+ }
+
+ copyDirectoryStructure( file, destination, rootDestinationDirectory, onlyModifiedFiles );
+ }
+ else
+ {
+ throw new IOException( "Unknown file type: " + file.getAbsolutePath() );
+ }
+ }
+ }
+
+ /**
+ * Renames a file, even if that involves crossing file system boundaries.
+ *
+ *
This will remove to (if it exists), ensure that
+ * to's parent directory exists and move
+ * from, which involves deleting from as
+ * well.
+ *
+ * @param from the file to move
+ * @param to the new file name
+ * @throws IOException if anything bad happens during this process.
+ * Note that to may have been deleted already when this happens.
+ * @deprecated use {@code java.nio.Files.move()}
+ */
+ @Deprecated
+ public static void rename( @Nonnull File from, @Nonnull File to )
+ throws IOException
+ {
+ if ( to.exists() && !deleteLegacyStyle( to ) )
+ {
+ throw new IOException( "Failed to delete " + to + " while trying to rename " + from );
+ }
+
+ File parent = to.getParentFile();
+ if ( parent != null && !parent.exists() && !parent.mkdirs() )
+ {
+ throw new IOException( "Failed to create directory " + parent + " while trying to rename " + from );
+ }
+
+ if ( !from.renameTo( to ) )
+ {
+ copyFile( from, to );
+ if ( !deleteLegacyStyle( from ) )
+ {
+ throw new IOException( "Failed to delete " + from + " while trying to rename it." );
+ }
+ }
+ }
+
+ /**
+ * Create a temporary file in a given directory.
+ *
+ *
The file denoted by the returned abstract pathname did not
+ * exist before this method was invoked, any subsequent invocation
+ * of this method will yield a different file name.
+ *
+ * The filename is prefixNNNNNsuffix where NNNN is a random number
+ *
+ *
This method is different to {@link File#createTempFile(String, String, File)}
+ * as it doesn't create the file itself.
+ * It uses the location pointed to by java.io.tmpdir
+ * when the parentDir attribute is null.
+ *
To automatically delete the file created by this method, use the
+ * {@link File#deleteOnExit()} method.
+ *
+ * @param prefix prefix before the random number
+ * @param suffix file extension; include the '.'
+ * @param parentDir directory to create the temporary file in -java.io.tmpdir
+ * used if not specified
+ * @return a File reference to the new temporary file.
+ * @deprecated use {@code java.nio.Files.createTempFile()}
+ */
+ @Deprecated
+ public static File createTempFile( @Nonnull String prefix, @Nonnull String suffix, @Nullable File parentDir )
+ {
+ File result;
+ String parent = System.getProperty( "java.io.tmpdir" );
+ if ( parentDir != null )
+ {
+ parent = parentDir.getPath();
+ }
+ DecimalFormat fmt = new DecimalFormat( "#####" );
+ SecureRandom secureRandom = new SecureRandom();
+ long secureInitializer = secureRandom.nextLong();
+ Random rand = new Random( secureInitializer + Runtime.getRuntime().freeMemory() );
+ do
+ {
+ result = new File( parent, prefix + fmt.format( positiveRandom( rand ) ) + suffix );
+ }
+ while ( result.exists() );
+
+ return result;
+ }
+
+ private static int positiveRandom( Random rand )
+ {
+ int a = rand.nextInt();
+ while ( a == Integer.MIN_VALUE )
+ {
+ a = rand.nextInt();
+ }
+ return Math.abs( a );
+ }
+
+ /**
+ * If wrappers is null or empty, the file will be copied only if to.lastModified() < from.lastModified()
+ *
+ * @param from the file to copy
+ * @param to the destination file
+ * @param encoding the file output encoding (only if wrappers is not empty)
+ * @param wrappers array of {@link FilterWrapper}
+ * @throws IOException if an IO error occurs during copying or filtering
+ */
+ public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
+ @Nullable FilterWrapper... wrappers )
+ throws IOException
+ {
+ copyFile( from, to, encoding, wrappers, false );
+ }
+
+ /**
+ * Wrapper class for Filter.
+ */
+ public abstract static class FilterWrapper
+ {
+ /**
+ * @param fileReader {@link Reader}
+ * @return the Reader instance
+ */
+ public abstract Reader getReader( Reader fileReader );
+ }
+
+ /**
+ * If wrappers is null or empty, the file will be copy only if to.lastModified() < from.lastModified() or if
+ * overwrite is true
+ *
+ * @param from the file to copy
+ * @param to the destination file
+ * @param encoding the file output encoding (only if wrappers is not empty)
+ * @param wrappers array of {@link FilterWrapper}
+ * @param overwrite if true and wrappers is null or empty, the file will be copied even if
+ * to.lastModified() < from.lastModified()
+ * @throws IOException if an IO error occurs during copying or filtering
+ */
+ public static void copyFile( @Nonnull File from, @Nonnull File to, @Nullable String encoding,
+ @Nullable FilterWrapper[] wrappers, boolean overwrite )
+ throws IOException
+ {
+ if ( wrappers == null || wrappers.length == 0 )
+ {
+ if ( overwrite || to.lastModified() < from.lastModified() )
+ {
+ copyFile( from, to );
+ }
+ }
+ else
+ {
+ Charset charset = charset( encoding );
+
+ // buffer so it isn't reading a byte at a time!
+ try ( Reader fileReader = Files.newBufferedReader( from.toPath(), charset ) )
+ {
+ Reader wrapped = fileReader;
+ for ( FilterWrapper wrapper : wrappers )
+ {
+ wrapped = wrapper.getReader( wrapped );
+ }
+
+ if ( overwrite || !to.exists() )
+ {
+ try ( Writer fileWriter = Files.newBufferedWriter( to.toPath(), charset ) )
+ {
+ IOUtil.copy( wrapped, fileWriter );
+ }
+ }
+ else
+ {
+ CharsetEncoder encoder = charset.newEncoder();
+
+ int totalBufferSize = FILE_COPY_BUFFER_SIZE;
+
+ int charBufferSize = ( int ) Math.floor( totalBufferSize / ( 2 + 2 * encoder.maxBytesPerChar() ) );
+ int byteBufferSize = ( int ) Math.ceil( charBufferSize * encoder.maxBytesPerChar() );
+
+ CharBuffer newChars = CharBuffer.allocate( charBufferSize );
+ ByteBuffer newBytes = ByteBuffer.allocate( byteBufferSize );
+ ByteBuffer existingBytes = ByteBuffer.allocate( byteBufferSize );
+
+ CoderResult coderResult;
+ int existingRead;
+ boolean writing = false;
+
+ try ( final RandomAccessFile existing = new RandomAccessFile( to, "rw" ) )
+ {
+ int n;
+ while ( -1 != ( n = wrapped.read( newChars ) ) )
+ {
+ ( ( Buffer ) newChars ).flip();
+
+ coderResult = encoder.encode( newChars, newBytes, n != 0 );
+ if ( coderResult.isError() )
+ {
+ coderResult.throwException();
+ }
+
+ ( ( Buffer ) newBytes ).flip();
+
+ if ( !writing )
+ {
+ existingRead = existing.read( existingBytes.array(), 0, newBytes.remaining() );
+ ( ( Buffer ) existingBytes ).position( existingRead );
+ ( ( Buffer ) existingBytes ).flip();
+
+ if ( newBytes.compareTo( existingBytes ) != 0 )
+ {
+ writing = true;
+ if ( existingRead > 0 )
+ {
+ existing.seek( existing.getFilePointer() - existingRead );
+ }
+ }
+ }
+
+ if ( writing )
+ {
+ existing.write( newBytes.array(), 0, newBytes.remaining() );
+ }
+
+ ( ( Buffer ) newChars ).clear();
+ ( ( Buffer ) newBytes ).clear();
+ ( ( Buffer ) existingBytes ).clear();
+ }
+
+ if ( existing.length() > existing.getFilePointer() )
+ {
+ existing.setLength( existing.getFilePointer() );
+ }
+ }
+ }
+ }
+ }
+
+ copyFilePermissions( from, to );
+ }
+
+ /**
+ * Attempts to copy file permissions from the source to the destination file.
+ * Initially attempts to copy posix file permissions, assuming that the files are both on posix filesystems.
+ * If the initial attempts fail then a second attempt using less precise permissions model.
+ * Note that permissions are copied on a best-efforts basis,
+ * failure to copy permissions will not result in an exception.
+ *
+ * @param source the file to copy permissions from.
+ * @param destination the file to copy permissions to.
+ */
+ private static void copyFilePermissions( @Nonnull File source, @Nonnull File destination )
+ throws IOException
+ {
+ try
+ {
+ // attempt to copy posix file permissions
+ Files.setPosixFilePermissions(
+ destination.toPath(),
+ Files.getPosixFilePermissions( source.toPath() )
+ );
+ }
+ catch ( UnsupportedOperationException e )
+ {
+ // fallback to setting partial permissions
+ destination.setExecutable( source.canExecute() );
+ destination.setReadable( source.canRead() );
+ destination.setWritable( source.canWrite() );
+ }
+ }
+
+ /**
+ * Note: the file content is read with platform encoding.
+ *
+ * @param file the file
+ * @return a List containing every every line not starting with # and not empty
+ * @throws IOException if any
+ * @deprecated assumes the platform default character set
+ */
+ @Deprecated
+ @Nonnull public static List loadFile( @Nonnull File file )
+ throws IOException
+ {
+ List lines = new ArrayList();
+
+ if ( file.exists() )
+ {
+ try ( BufferedReader reader = Files.newBufferedReader( file.toPath(), Charset.defaultCharset() ) )
+ {
+ for ( String line = reader.readLine(); line != null; line = reader.readLine() )
+ {
+ line = line.trim();
+ if ( !line.startsWith( "#" ) && line.length() != 0 )
+ {
+ lines.add( line );
+ }
+ }
+ }
+ }
+
+ return lines;
+
+ }
+
+ /**
+ * Returns the named charset or the default charset.
+ * @param encoding the name or alias of the charset, null or empty
+ * @return A charset object for the named or default charset.
+ */
+ private static Charset charset( String encoding )
+ {
+ if ( encoding == null || encoding.isEmpty() )
+ {
+ return Charset.defaultCharset();
+ }
+ else
+ {
+ return Charset.forName( encoding );
+ }
+ }
+
+ /**
+ * For Windows OS, check if the file name contains any of the following characters:
+ * ":", "*", "?", "\"", "<", ">", "|"
+ *
+ * @param f not null file
+ * @return false if the file path contains any of forbidden Windows characters,
+ * true if the Os is not Windows or if the file path respect the Windows constraints.
+ * @see #INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME
+ */
+ private static boolean isValidWindowsFileName( @Nonnull File f )
+ {
+ if ( Os.isFamily( Os.FAMILY_WINDOWS ) )
+ {
+ if ( StringUtils.indexOfAny( f.getName(), INVALID_CHARACTERS_FOR_WINDOWS_FILE_NAME ) != -1 )
+ {
+ return false;
+ }
+
+ if ( f.getParentFile() != null )
+ {
+ return isValidWindowsFileName( f.getParentFile() );
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Checks whether a given file is a symbolic link.
+ *
+ * @param file the file to check
+ * @throws IOException in case of failure.
+ * @return true if symbolic link false otherwise.
+ * @deprecated use {@code java.nio.file.Files.isSymbolicLink(file.toPath())}
+ */
+ @Deprecated
+ public static boolean isSymbolicLink( @Nonnull final File file )
+ throws IOException
+ {
+ return Files.isSymbolicLink( file.toPath() );
+ }
+
+ /**
+ * Checks whether a given file is a symbolic link.
+ *
+ * @param file the file to check
+ * @return true if and only if we reliably can say this is a symlink
+ *
+ * @throws IOException in case of failure
+ * @deprecated use {@code java.nio.file.Files.isSymbolicLink(file.toPath())}
+ */
+ @Deprecated
+ public static boolean isSymbolicLinkForSure( @Nonnull final File file )
+ throws IOException
+ {
+ return Files.isSymbolicLink( file.toPath() );
+ }
+
+ /**
+ * Create a new symbolic link, possibly replacing an existing symbolic link.
+ *
+ * @param symlink the link name
+ * @param target the target
+ * @return the linked file
+ * @throws IOException in case of an error
+ * @see {@code java.nio.file.Files.createSymbolicLink(Path)} which creates a new
+ * symbolic link but does not replace exsiting symbolic links
+ */
+ @Nonnull public static File createSymbolicLink( @Nonnull File symlink, @Nonnull File target )
+ throws IOException
+ {
+ final Path symlinkPath = symlink.toPath();
+
+ if ( Files.exists( symlinkPath ) )
+ {
+ if ( target.equals( Files.readSymbolicLink( symlinkPath ).toFile() ) )
+ {
+ return symlink;
+ }
+
+ Files.delete( symlinkPath );
+ }
+
+ return Files.createSymbolicLink( symlinkPath, target.toPath() ).toFile();
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/IOUtil.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/IOUtil.java
new file mode 100644
index 000000000..11f1bdd4e
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/IOUtil.java
@@ -0,0 +1,1368 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.channels.Channel;
+
+/**
+ * General IO Stream manipulation.
+ *
+ * This class provides static utility methods for input/output operations, particularly buffered
+ * copying between sources (InputStream, Reader, String and
+ * byte[]) and destinations (OutputStream, Writer,
+ * String and byte[]).
+ *
+ *
Unless otherwise noted, these copy methods do not flush or close the
+ * streams. Often, doing so would require making non-portable assumptions about the streams' origin
+ * and further use. This means that both streams' close() methods must be called after
+ * copying. if one omits this step, then the stream resources (sockets, file descriptors) are
+ * released when the associated Stream is garbage-collected. It is not a good idea to rely on this
+ * mechanism.
+ *
+ *
For each copy method, a variant is provided that allows the caller to specify the
+ * buffer size (the default is 4k). As the buffer size can have a fairly large impact on speed, this
+ * may be worth tweaking. Often "large buffer -> faster" does not hold, even for large data
+ * transfers.
+ *
For byte-to-char methods, a copy variant allows the encoding to be selected
+ * (otherwise the platform default is used).
+ *
The copy methods use an internal buffer when copying. It is therefore advisable
+ * not to deliberately wrap the stream arguments to the copy methods in
+ * Buffered* streams. For example, don't do the
+ * following:
+ *
+ * copy( new BufferedInputStream( in ), new BufferedOutputStream( out ) );
+ *
+ *
The rationale is as follows:
+ *
+ *
Imagine that an InputStream's read() is a very expensive operation, which would usually suggest
+ * wrapping in a BufferedInputStream. The BufferedInputStream works by issuing infrequent
+ * {@link java.io.InputStream#read(byte[] b, int off, int len)} requests on the underlying InputStream, to
+ * fill an internal buffer, from which further read requests can inexpensively get
+ * their data (until the buffer runs out).
+ *
However, the copy methods do the same thing, keeping an internal buffer,
+ * populated by {@link InputStream#read(byte[] b, int off, int len)} requests. Having two buffers
+ * (or three if the destination stream is also buffered) is pointless, and the unnecessary buffer
+ * management hurts performance slightly (about 3%, according to some simple experiments).
+ *
+ * @author Peter Donald
+ * @author Jeff Turner
+ * @version CVS $Revision$ $Date$
+ *
+ */
+public final class IOUtil
+/*
+ * Behold, intrepid explorers; a map of this class:
+ *
+ * Method Input Output Dependency
+ * ------ ----- ------ -------
+ * 1 copy InputStream OutputStream (primitive)
+ * 2 copy Reader Writer (primitive)
+ *
+ * 3 copy InputStream Writer 2
+ * 4 toString InputStream String 3
+ * 5 toByteArray InputStream byte[] 1
+ *
+ * 6 copy Reader OutputStream 2
+ * 7 toString Reader String 2
+ * 8 toByteArray Reader byte[] 6
+ *
+ * 9 copy String OutputStream 2
+ * 10 copy String Writer (trivial)
+ * 11 toByteArray String byte[] 9
+ *
+ * 12 copy byte[] Writer 3
+ * 13 toString byte[] String 12
+ * 14 copy byte[] OutputStream (trivial)
+ *
+ *
+ * Note that only the first two methods shuffle bytes; the rest use these two, or (if possible) copy
+ * using native Java copy methods. As there are method variants to specify buffer size and encoding,
+ * each row may correspond to up to 4 methods.
+ *
+ */
+{
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ /**
+ * Private constructor to prevent instantiation.
+ */
+ private IOUtil()
+ {
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Core copy methods
+ ///////////////////////////////////////////////////////////////
+
+ /**
+ * Copy bytes from an InputStream to an OutputStream.
+ *
+ * @param input the stream to read from
+ * @param output the stream to write to
+ * @throws IOException in case of an error
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()} or in
+ * Java 9 and later {@code InputStream.transferTo()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy bytes from an InputStream to an OutputStream.
+ *
+ * In Java 9 and later this is replaced by {@code InputStream.transferTo()}.
+ *
+ * @param input the stream to read from
+ * @param output the stream to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of an error
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()} or in
+ * Java 9 and later {@code InputStream.transferTo()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final OutputStream output,
+ final int bufferSize )
+ throws IOException
+ {
+ final byte[] buffer = new byte[bufferSize];
+ int n;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ }
+
+ /**
+ * Copy chars from a Reader to a Writer.
+ *
+ * @param input the reader to read from
+ * @param output the writer to write to
+ * @throws IOException in case of failure * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final Reader input, @Nonnull final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy chars from a Reader to a Writer.
+ *
+ * @param input the reader to read from
+ * @param output the writer to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final Reader input, @Nonnull final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final char[] buffer = new char[bufferSize];
+ int n;
+ while ( -1 != ( n = input.read( buffer ) ) )
+ {
+ output.write( buffer, 0, n );
+ }
+ output.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // InputStream -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> Writer
+
+ /**
+ * Copy and convert bytes from an InputStream to chars on a
+ * Writer.
+ *
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the reader to read from
+ * @param output the writer to write to
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream to chars on a
+ * Writer.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the input stream to read from
+ * @param output the writer to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream to chars on a
+ * Writer, using the specified encoding.
+ *
+ * @param input the input stream to read from
+ * @param output the writer to write to
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output,
+ @Nonnull final String encoding )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output );
+ }
+
+ /**
+ * Copy and convert bytes from an InputStream to chars on a
+ * Writer, using the specified encoding.
+ *
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param input the input stream to read from
+ * @param output the writer to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.copy()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final InputStream input, @Nonnull final Writer output,
+ @Nonnull final String encoding, final int bufferSize )
+ throws IOException
+ {
+ final InputStreamReader in = new InputStreamReader( input, encoding );
+ copy( in, output, bufferSize );
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> String
+
+ /**
+ * Get the contents of an InputStream as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the InputStream to read from
+ * @return the resulting string
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final InputStream input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the InputStream to read from
+ * @param bufferSize size of internal buffer
+ * @return the resulting string
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of an InputStream as a String.
+ *
+ * @param input the InputStream to read from
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @return the converted string
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.toString()}.
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream as a String.
+ *
+ * @param input the InputStream to read from
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize size of internal buffer
+ * @return The converted string.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.toString()}.
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final InputStream input, @Nonnull final String encoding,
+ final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // InputStream -> byte[]
+
+ /**
+ * Get the contents of an InputStream as a byte[].
+ *
+ * @param input the InputStream to read from
+ * @return the resulting byte array.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.readFully()}.
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of an InputStream as a byte[].
+ *
+ * @param input the InputStream to read from
+ * @param bufferSize size of internal buffer
+ * @return the resulting byte array.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.readFully()}.
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final InputStream input, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // Reader -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> OutputStream
+
+ /**
+ * Serialize chars from a Reader to bytes on an OutputStream, and
+ * flush the OutputStream.
+ *
+ * @param input the InputStream to read from
+ * @param output the output stream to write to
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a Reader to bytes on an OutputStream, and
+ * flush the OutputStream.
+ *
+ * @param input the InputStream to read from
+ * @param output the output to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final Reader input, @Nonnull final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( input, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> String
+
+ /**
+ * Get the contents of a Reader as a String.
+ * @param input the InputStream to read from
+ * @return The converted string.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.toString()}.
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final Reader input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader as a String.
+ *
+ * @param input the reader to read from
+ * @param bufferSize size of internal buffer
+ * @return the resulting byte array.
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.toString()}.
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final Reader input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Reader -> byte[]
+
+ /**
+ * Get the contents of a Reader as a byte[].
+ *
+ * @param input the InputStream to read from
+ * @return the resulting byte array.
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final Reader input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a Reader as a byte[].
+ *
+ * @param input the InputStream to read from
+ * @param bufferSize size of internal buffer
+ * @return the resulting byte array.
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final Reader input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // String -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // String -> OutputStream
+
+ /**
+ * Serialize chars from a String to bytes on an OutputStream, and
+ * flush the OutputStream.
+ * @param input the InputStream to read from
+ * @param output the output to write to
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final String input, @Nonnull final OutputStream output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Serialize chars from a String to bytes on an OutputStream, and
+ * flush the OutputStream.
+ *
+ * @param input the InputStream to read from
+ * @param output the output to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final String input, @Nonnull final OutputStream output, final int bufferSize )
+ throws IOException
+ {
+ final StringReader in = new StringReader( input );
+ final OutputStreamWriter out = new OutputStreamWriter( output );
+ copy( in, out, bufferSize );
+ // NOTE: Unless anyone is planning on rewriting OutputStreamWriter, we have to flush
+ // here.
+ out.flush();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // String -> Writer
+
+ /**
+ * Copy chars from a String to a Writer.
+ *
+ * @param input the string to write
+ * @param output resulting output {@link Writer}
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.write()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final String input, @Nonnull final Writer output )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // String -> byte[]
+
+ /**
+ * Get the contents of a String as a byte[].
+ *
+ * @param input the String to read from
+ * @return the resulting byte array
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final String input )
+ throws IOException
+ {
+ return toByteArray( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a String as a byte[].
+ *
+ * @param input the InputStream to read from
+ * @param bufferSize size of internal buffer
+ * @return the resulting byte array
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static byte[] toByteArray( @Nonnull final String input, final int bufferSize )
+ throws IOException
+ {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ copy( input, output, bufferSize );
+ return output.toByteArray();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // Derived copy methods
+ // byte[] -> *
+ ///////////////////////////////////////////////////////////////
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> Writer
+
+ /**
+ * Copy and convert bytes from a byte[] to chars on a
+ * Writer.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the InputStream to read from
+ * @param output the output to write to
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output )
+ throws IOException
+ {
+ copy( input, output, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[] to chars on a
+ * Writer.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param input the InputStream to read from
+ * @param output the output to write to
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, bufferSize );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[] to chars on a
+ * Writer, using the specified encoding.
+ *
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param input the data to write
+ * @param output the writer to write to
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.write()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, final String encoding )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding );
+ }
+
+ /**
+ * Copy and convert bytes from a byte[] to chars on a
+ * Writer, using the specified encoding.
+ *
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param input the input bytes
+ * @param output The output buffer {@link Writer}
+ * @param bufferSize size of internal buffer
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.write()}.
+ */
+ @Deprecated
+ public static void copy( @Nonnull final byte[] input, @Nonnull final Writer output, @Nonnull final String encoding,
+ final int bufferSize )
+ throws IOException
+ {
+ final ByteArrayInputStream in = new ByteArrayInputStream( input );
+ copy( in, output, encoding, bufferSize );
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> String
+
+ /**
+ * Get the contents of a byte[] as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ * @param input the input bytes
+ * @return the resulting string
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final byte[] input )
+ throws IOException
+ {
+ return toString( input, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[] as a String.
+ * The platform's default encoding is used for the byte-to-char conversion.
+ *
+ * @param bufferSize size of internal buffer
+ * @param input the input bytes
+ * @return the created string
+ * @throws IOException in case of failure
+ * @deprecated always specify a character encoding
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final byte[] input, final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, bufferSize );
+ return sw.toString();
+ }
+
+ /**
+ * Get the contents of a byte[] as a String.
+ *
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param input the input bytes
+ * @return the resulting string
+ * @throws IOException in case of failure
+ * @deprecated use {@code new String(input, encoding)}
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding )
+ throws IOException
+ {
+ return toString( input, encoding, DEFAULT_BUFFER_SIZE );
+ }
+
+ /**
+ * Get the contents of a byte[] as a String.
+ *
+ * @param encoding the name of a supported character encoding. See the
+ * IANA
+ * Charset Registry for a list of valid encoding types.
+ * @param bufferSize size of internal buffer
+ * @param input input bytes
+ * @return the resulting string
+ * @throws IOException in case of failure
+ * @deprecated use {@code new String(input, encoding)}
+ */
+ @Deprecated
+ @Nonnull public static String toString( @Nonnull final byte[] input, @Nonnull final String encoding,
+ final int bufferSize )
+ throws IOException
+ {
+ final StringWriter sw = new StringWriter();
+ copy( input, sw, encoding, bufferSize );
+ return sw.toString();
+ }
+
+ ///////////////////////////////////////////////////////////////
+ // byte[] -> OutputStream
+
+ /**
+ * Copy bytes from a byte[] to an OutputStream.
+ *
+ * @param input Input byte array.
+ * @param output output stream {@link OutputStream}
+ * @throws IOException in case of failure
+ * @deprecated inline this method
+ */
+ @Deprecated
+ public static void copy( @Nonnull final byte[] input, @Nonnull final OutputStream output )
+ throws IOException
+ {
+ output.write( input );
+ }
+
+ /**
+ * Compare the contents of two streams to determine if they are equal or not.
+ *
+ * @param input1 the first stream
+ * @param input2 the second stream
+ * @return true if the content of the streams are equal or they both don't exist, false otherwise
+ * @throws IOException in case of failure
+ * @deprecated use {@code org.apache.commons.io.IOUtils.contentEquals()}
+ */
+ @Deprecated
+ public static boolean contentEquals( @Nonnull final InputStream input1, @Nonnull final InputStream input2 )
+ throws IOException
+ {
+ final InputStream bufferedInput1 = new BufferedInputStream( input1 );
+ final InputStream bufferedInput2 = new BufferedInputStream( input2 );
+
+ int ch = bufferedInput1.read();
+ while ( -1 != ch )
+ {
+ final int ch2 = bufferedInput2.read();
+ if ( ch != ch2 )
+ {
+ return false;
+ }
+ ch = bufferedInput1.read();
+ }
+
+ final int ch2 = bufferedInput2.read();
+ return -1 == ch2;
+ }
+
+ // ----------------------------------------------------------------------
+ // closeXXX()
+ // ----------------------------------------------------------------------
+
+ /**
+ * Closes a {@code Channel} suppressing any {@code IOException}.
+ *
+ * Note: The use case justifying this method is a shortcoming of the Java language up to but not including
+ * Java 7. For any code targeting Java 7 or later use of this method is highly discouraged and the
+ * {@code try-with-resources} statement should be used instead. Care must be taken to not use this method in a way
+ * {@code IOException}s get suppressed incorrectly.
+ * You must close all resources in use inside the {@code try} block to not suppress exceptions in the
+ * {@code finally} block incorrectly by using this method.
+ *
+ *
+ * Example:
+ *
+ * // Introduce variables for the resources and initialize them to null. This cannot throw an exception.
+ * Closeable resource1 = null;
+ * Closeable resource2 = null;
+ * try
+ * {
+ * // Obtain a resource object and assign it to variable resource1. This may throw an exception.
+ * // If successful, resource1 != null.
+ * resource1 = ...
+ *
+ * // Obtain a resource object and assign it to variable resource2. This may throw an exception.
+ * // If successful, resource2 != null. Not reached if an exception has been thrown above.
+ * resource2 = ...
+ *
+ * // Perform operations on the resources. This may throw an exception. Not reached if an exception has been
+ * // thrown above. Note: Treat the variables resource1 and resource2 the same way as if they would have been
+ * // declared with the final modifier - that is - do NOT write anyting like resource1 = something else or
+ * // resource2 = something else here.
+ * resource1 ...
+ * resource2 ...
+ *
+ * // Finally, close the resources and set the variables to null indicating successful completion.
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource1.close();
+ * resource1 = null;
+ * // Not reached if an exception has been thrown above.
+ * resource2.close();
+ * resource2 = null;
+ *
+ * // All resources are closed at this point and all operations (up to here) completed successfully without
+ * // throwing an exception we would need to handle (by letting it propagate or by catching and handling it).
+ * }
+ * finally
+ * {
+ * // Cleanup any resource not closed in the try block due to an exception having been thrown and suppress any
+ * // exception this may produce to not stop the exception from the try block to be propagated. If the try
+ * // block completed successfully, all variables will have been set to null there and this will not do
+ * // anything. This is just to cleanup properly in case of an exception.
+ *
+ * IOUtil.close( resource1 );
+ * IOUtil.close( resource2 );
+ *
+ * // Without that utility method you would need to write the following:
+ * //
+ * // try
+ * // {
+ * // if ( resource1 != null )
+ * // {
+ * // resource1.close();
+ * // }
+ * // }
+ * // catch( IOException e )
+ * // {
+ * // Suppressed. If resource1 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // finally
+ * // {
+ * // try
+ * // {
+ * // if ( resource2 != null )
+ * // {
+ * // resource2.close();
+ * // }
+ * // }
+ * // catch ( IOException e )
+ * // {
+ * // Suppressed. If resource2 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // }
+ * }
+ *
+ *
+ *
+ * @param channel The channel to close or {@code null}.
+ * @deprecated use try-with-resources
+ */
+ @Deprecated
+ public static void close( @Nullable Channel channel )
+ {
+ try
+ {
+ if ( channel != null )
+ {
+ channel.close();
+ }
+ }
+ catch ( IOException ex )
+ {
+ // Suppressed
+ }
+ }
+
+ /**
+ * Closes an {@code InputStream} suppressing any {@code IOException}.
+ *
+ * Note: The use case justifying this method is a shortcoming of the Java language up to but not including
+ * Java 7. For any code targeting Java 7 or later use of this method is highly discouraged and the
+ * {@code try-with-resources} statement should be used instead. Care must be taken to not use this method in a way
+ * {@code IOException}s get suppressed incorrectly.
+ * You must close all resources in use inside the {@code try} block to not suppress exceptions in the
+ * {@code finally} block incorrectly by using this method.
+ *
+ *
+ * Example:
+ *
+ * // Introduce variables for the resources and initialize them to null. This cannot throw an exception.
+ * Closeable resource1 = null;
+ * Closeable resource2 = null;
+ * try
+ * {
+ * // Obtain a resource object and assign it to variable resource1. This may throw an exception.
+ * // If successful, resource1 != null.
+ * resource1 = ...
+ *
+ * // Obtain a resource object and assign it to variable resource2. This may throw an exception.
+ * // If successful, resource2 != null. Not reached if an exception has been thrown above.
+ * resource2 = ...
+ *
+ * // Perform operations on the resources. This may throw an exception. Not reached if an exception has been
+ * // thrown above. Note: Treat the variables resource1 and resource2 the same way as if they would have been
+ * // declared with the final modifier - that is - do NOT write anyting like resource1 = something else or
+ * // resource2 = something else here.
+ * resource1 ...
+ * resource2 ...
+ *
+ * // Finally, close the resources and set the variables to null indicating successful completion.
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource1.close();
+ * resource1 = null;
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource2.close();
+ * resource2 = null;
+ *
+ * // All resources are closed at this point and all operations (up to here) completed successfully without
+ * // throwing an exception we would need to handle (by letting it propagate or by catching and handling it).
+ * }
+ * finally
+ * {
+ * // Cleanup any resource not closed in the try block due to an exception having been thrown and suppress any
+ * // exception this may produce to not stop the exception from the try block to be propagated. If the try
+ * // block completed successfully, all variables will have been set to null there and this will not do
+ * // anything. This is just to cleanup properly in case of an exception.
+ *
+ * IOUtil.close( resource1 );
+ * IOUtil.close( resource2 );
+ *
+ * // Without that utility method you would need to write the following:
+ * //
+ * // try
+ * // {
+ * // if ( resource1 != null )
+ * // {
+ * // resource1.close();
+ * // }
+ * // }
+ * // catch( IOException e )
+ * // {
+ * // Suppressed. If resource1 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // finally
+ * // {
+ * // try
+ * // {
+ * // if ( resource2 != null )
+ * // {
+ * // resource2.close();
+ * // }
+ * // }
+ * // catch ( IOException e )
+ * // {
+ * // Suppressed. If resource2 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // }
+ * }
+ *
+ *
+ *
+ * @param inputStream The stream to close or {@code null}.
+ * @deprecated use try-with-resources
+ */
+ @Deprecated
+ public static void close( @Nullable InputStream inputStream )
+ {
+ try
+ {
+ if ( inputStream != null )
+ {
+ inputStream.close();
+ }
+ }
+ catch ( IOException ex )
+ {
+ // Suppressed
+ }
+ }
+
+ /**
+ * Closes an {@code OutputStream} suppressing any {@code IOException}.
+ *
+ * Note: The use case justifying this method is a shortcoming of the Java language up to but not including
+ * Java 7. For any code targeting Java 7 or later use of this method is highly discouraged and the
+ * {@code try-with-resources} statement should be used instead. Care must be taken to not use this method in a way
+ * {@code IOException}s get suppressed incorrectly.
+ * You must close all resources in use inside the {@code try} block to not suppress exceptions in the
+ * {@code finally} block incorrectly by using this method.
+ *
+ *
+ * Example:
+ *
+ * // Introduce variables for the resources and initialize them to null. This cannot throw an exception.
+ * Closeable resource1 = null;
+ * Closeable resource2 = null;
+ * try
+ * {
+ * // Obtain a resource object and assign it to variable resource1. This may throw an exception.
+ * // If successful, resource1 != null.
+ * resource1 = ...
+ *
+ * // Obtain a resource object and assign it to variable resource2. This may throw an exception.
+ * // If successful, resource2 != null. Not reached if an exception has been thrown above.
+ * resource2 = ...
+ *
+ * // Perform operations on the resources. This may throw an exception. Not reached if an exception has been
+ * // thrown above. Note: Treat the variables resource1 and resource2 the same way as if they would have been
+ * // declared with the final modifier - that is - do NOT write anyting like resource1 = something else or
+ * // resource2 = something else here.
+ * resource1 ...
+ * resource2 ...
+ *
+ * // Finally, close the resources and set the variables to null indicating successful completion.
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource1.close();
+ * resource1 = null;
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource2.close();
+ * resource2 = null;
+ *
+ * // All resources are closed at this point and all operations (up to here) completed successfully without
+ * // throwing an exception we would need to handle (by letting it propagate or by catching and handling it).
+ * }
+ * finally
+ * {
+ * // Cleanup any resource not closed in the try block due to an exception having been thrown and suppress any
+ * // exception this may produce to not stop the exception from the try block to be propagated. If the try
+ * // block completed successfully, all variables will have been set to null there and this will not do
+ * // anything. This is just to cleanup properly in case of an exception.
+ *
+ * IOUtil.close( resource1 );
+ * IOUtil.close( resource2 );
+ *
+ * // Without that utility method you would need to write the following:
+ * //
+ * // try
+ * // {
+ * // if ( resource1 != null )
+ * // {
+ * // resource1.close();
+ * // }
+ * // }
+ * // catch( IOException e )
+ * // {
+ * // Suppressed. If resource1 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // finally
+ * // {
+ * // try
+ * // {
+ * // if ( resource2 != null )
+ * // {
+ * // resource2.close();
+ * // }
+ * // }
+ * // catch ( IOException e )
+ * // {
+ * // Suppressed. If resource2 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // }
+ * }
+ *
+ *
+ *
+ * @param outputStream The stream to close or {@code null}.
+ * @deprecated use try-with-resources
+ */
+ @Deprecated
+ public static void close( @Nullable OutputStream outputStream )
+ {
+ try
+ {
+ if ( outputStream != null )
+ {
+ outputStream.close();
+ }
+ }
+ catch ( IOException ex )
+ {
+ // Suppressed
+ }
+ }
+
+ /**
+ * Closes a {@code Reader} suppressing any {@code IOException}.
+ *
+ * Note: The use case justifying this method is a shortcoming of the Java language up to but not including
+ * Java 7. For any code targeting Java 7 or later use of this method is highly discouraged and the
+ * {@code try-with-resources} statement should be used instead. Care must be taken to not use this method in a way
+ * {@code IOException}s get suppressed incorrectly.
+ * You must close all resources in use inside the {@code try} block to not suppress exceptions in the
+ * {@code finally} block incorrectly by using this method.
+ *
+ *
+ * Example:
+ *
+ * // Introduce variables for the resources and initialize them to null. This cannot throw an exception.
+ * Closeable resource1 = null;
+ * Closeable resource2 = null;
+ * try
+ * {
+ * // Obtain a resource object and assign it to variable resource1. This may throw an exception.
+ * // If successful, resource1 != null.
+ * resource1 = ...
+ *
+ * // Obtain a resource object and assign it to variable resource2. This may throw an exception.
+ * // If successful, resource2 != null. Not reached if an exception has been thrown above.
+ * resource2 = ...
+ *
+ * // Perform operations on the resources. This may throw an exception. Not reached if an exception has been
+ * // thrown above. Note: Treat the variables resource1 and resource2 the same way as if they would have been
+ * // declared with the final modifier - that is - do NOT write anyting like resource1 = something else or
+ * // resource2 = something else here.
+ * resource1 ...
+ * resource2 ...
+ *
+ * // Finally, close the resources and set the variables to null indicating successful completion.
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource1.close();
+ * resource1 = null;
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource2.close();
+ * resource2 = null;
+ *
+ * // All resources are closed at this point and all operations (up to here) completed successfully without
+ * // throwing an exception we would need to handle (by letting it propagate or by catching and handling it).
+ * }
+ * finally
+ * {
+ * // Cleanup any resource not closed in the try block due to an exception having been thrown and suppress any
+ * // exception this may produce to not stop the exception from the try block to be propagated. If the try
+ * // block completed successfully, all variables will have been set to null there and this will not do
+ * // anything. This is just to cleanup properly in case of an exception.
+ *
+ * IOUtil.close( resource1 );
+ * IOUtil.close( resource2 );
+ *
+ * // Without that utility method you would need to write the following:
+ * //
+ * // try
+ * // {
+ * // if ( resource1 != null )
+ * // {
+ * // resource1.close();
+ * // }
+ * // }
+ * // catch( IOException e )
+ * // {
+ * // Suppressed. If resource1 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // finally
+ * // {
+ * // try
+ * // {
+ * // if ( resource2 != null )
+ * // {
+ * // resource2.close();
+ * // }
+ * // }
+ * // catch ( IOException e )
+ * // {
+ * // Suppressed. If resource2 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // }
+ * }
+ *
+ *
+ *
+ * @param reader The reader to close or {@code null}.
+ * @deprecated use try-with-resources
+ */
+ @Deprecated
+ public static void close( @Nullable Reader reader )
+ {
+ try
+ {
+ if ( reader != null )
+ {
+ reader.close();
+ }
+ }
+ catch ( IOException ex )
+ {
+ // Suppressed
+ }
+ }
+
+ /**
+ * Closes a {@code Writer} suppressing any {@code IOException}.
+ *
+ * Note: The use case justifying this method is a shortcoming of the Java language up to but not including
+ * Java 7. For any code targeting Java 7 or later use of this method is highly discouraged and the
+ * {@code try-with-resources} statement should be used instead. Care must be taken to not use this method in a way
+ * {@code IOException}s get suppressed incorrectly.
+ * You must close all resources in use inside the {@code try} block to not suppress exceptions in the
+ * {@code finally} block incorrectly by using this method.
+ *
+ *
+ * Example:
+ *
+ * // Introduce variables for the resources and initialize them to null. This cannot throw an exception.
+ * Closeable resource1 = null;
+ * Closeable resource2 = null;
+ * try
+ * {
+ * // Obtain a resource object and assign it to variable resource1. This may throw an exception.
+ * // If successful, resource1 != null.
+ * resource1 = ...
+ *
+ * // Obtain a resource object and assign it to variable resource2. This may throw an exception.
+ * // If successful, resource2 != null. Not reached if an exception has been thrown above.
+ * resource2 = ...
+ *
+ * // Perform operations on the resources. This may throw an exception. Not reached if an exception has been
+ * // thrown above. Note: Treat the variables resource1 and resource2 the same way as if they would have been
+ * // declared with the final modifier - that is - do NOT write anyting like resource1 = something else or
+ * // resource2 = something else here.
+ * resource1 ...
+ * resource2 ...
+ *
+ * // Finally, close the resources and set the variables to null indicating successful completion.
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource1.close();
+ * resource1 = null;
+ * // This may throw an exception. Not reached if an exception has been thrown above.
+ * resource2.close();
+ * resource2 = null;
+ *
+ * // All resources are closed at this point and all operations (up to here) completed successfully without
+ * // throwing an exception we would need to handle (by letting it propagate or by catching and handling it).
+ * }
+ * finally
+ * {
+ * // Cleanup any resource not closed in the try block due to an exception having been thrown and suppress any
+ * // exception this may produce to not stop the exception from the try block to be propagated. If the try
+ * // block completed successfully, all variables will have been set to null there and this will not do
+ * // anything. This is just to cleanup properly in case of an exception.
+ *
+ * IOUtil.close( resource1 );
+ * IOUtil.close( resource2 );
+ *
+ * // Without that utility method you would need to write the following:
+ * //
+ * // try
+ * // {
+ * // if ( resource1 != null )
+ * // {
+ * // resource1.close();
+ * // }
+ * // }
+ * // catch( IOException e )
+ * // {
+ * // Suppressed. If resource1 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // finally
+ * // {
+ * // try
+ * // {
+ * // if ( resource2 != null )
+ * // {
+ * // resource2.close();
+ * // }
+ * // }
+ * // catch ( IOException e )
+ * // {
+ * // Suppressed. If resource2 != null, an exception has already been thrown in the try block we need to
+ * // propagate instead of this one.
+ * // }
+ * // }
+ * }
+ *
+ *
+ *
+ * @param writer The writer to close or {@code null}.
+ * @deprecated use try-with-resources
+ */
+ @Deprecated
+ public static void close( @Nullable Writer writer )
+ {
+ try
+ {
+ if ( writer != null )
+ {
+ writer.close();
+ }
+ }
+ catch ( IOException ex )
+ {
+ // Suppressed
+ }
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/Java7Support.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/Java7Support.java
new file mode 100644
index 000000000..a51d50229
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/Java7Support.java
@@ -0,0 +1,106 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 javax.annotation.Nonnull;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+
+/**
+ * Java7 feature detection
+ *
+ * @author Kristian Rosenvold
+ *
+ * @deprecated no longer needed, prefer to use {@link java.nio.file.Files} methods directly.
+ */
+@Deprecated
+public class Java7Support
+{
+ /**
+ * @param file The file to check for being a symbolic link.
+ * @return true if the file is a symlink false otherwise.
+ */
+ public static boolean isSymLink( @Nonnull File file )
+ {
+ return Files.isSymbolicLink( file.toPath() );
+ }
+
+ /**
+ * @param symlink The sym link.
+ * @return The file.
+ * @throws IOException in case of error.
+ */
+ @Nonnull public static File readSymbolicLink( @Nonnull File symlink )
+ throws IOException
+ {
+ return Files.readSymbolicLink( symlink.toPath() ).toFile();
+ }
+
+ /**
+ * @param file The file to check.
+ * @return true if exist false otherwise.
+ * @throws IOException in case of failure.
+ */
+ public static boolean exists( @Nonnull File file )
+ throws IOException
+ {
+ return Files.exists( file.toPath() );
+ }
+
+ /**
+ * @param symlink The link name.
+ * @param target The target.
+ * @return The linked file.
+ * @throws IOException in case of an error.
+ */
+ @Nonnull public static File createSymbolicLink( @Nonnull File symlink, @Nonnull File target )
+ throws IOException
+ {
+ return FileUtils.createSymbolicLink( symlink, target );
+ }
+
+ /**
+ * Performs a nio delete
+ * @param file the file to delete
+ * @throws IOException in case of error.
+ */
+ public static void delete( @Nonnull File file )
+ throws IOException
+ {
+ Files.delete( file.toPath() );
+ }
+
+ /**
+ * @return true in case of Java 7.
+ */
+ public static boolean isJava7()
+ {
+ return true;
+ }
+
+ /**
+ * @return true in case of Java7 or greater.
+ */
+ public static boolean isAtLeastJava7()
+ {
+ return true;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java
new file mode 100644
index 000000000..8abff427e
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPattern.java
@@ -0,0 +1,155 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.regex.Pattern;
+
+import javax.annotation.Nonnull;
+
+/**
+ * Describes a match target for SelectorUtils.
+ *
+ * Significantly more efficient than using strings, since re-evaluation and re-tokenizing is avoided.
+ *
+ * @author Kristian Rosenvold
+ * @deprecated use {@code java.nio.filejava.nio.file.DirectoryStream.Filter} and related classes
+ */
+@Deprecated
+public class MatchPattern
+{
+ private final String source;
+
+ private final String regexPattern;
+
+ private final Pattern regexPatternRegex;
+
+ private final String separator;
+
+ private final String[] tokenized;
+
+ private MatchPattern( @Nonnull String source, @Nonnull String separator )
+ {
+ regexPattern = SelectorUtils.isRegexPrefixedPattern( source ) ? source.substring(
+ SelectorUtils.REGEX_HANDLER_PREFIX.length(),
+ source.length() - SelectorUtils.PATTERN_HANDLER_SUFFIX.length() ) : null;
+ regexPatternRegex = regexPattern != null ? Pattern.compile( regexPattern ) : null;
+ this.source = SelectorUtils.isAntPrefixedPattern( source ) ? source.substring(
+ SelectorUtils.ANT_HANDLER_PREFIX.length(),
+ source.length() - SelectorUtils.PATTERN_HANDLER_SUFFIX.length() ) : source;
+ this.separator = separator;
+ tokenized = tokenizePathToString( this.source, separator );
+ }
+
+
+ /**
+ * @param str The string to match for.
+ * @param isCaseSensitive case sensitive true false otherwise.
+ * @return true if matches false otherwise.
+ */
+ public boolean matchPath( String str, boolean isCaseSensitive )
+ {
+ if ( regexPattern != null )
+ {
+ return regexPatternRegex.matcher( str ).matches();
+ }
+ else
+ {
+ return SelectorUtils.matchAntPathPattern( this, str, separator, isCaseSensitive );
+ }
+ }
+
+ boolean matchPath( String str, String[] strDirs, boolean isCaseSensitive )
+ {
+ if ( regexPattern != null )
+ {
+ return regexPatternRegex.matcher( str ).matches();
+ }
+ else
+ {
+ return SelectorUtils.matchAntPathPattern( getTokenizedPathString(), strDirs, isCaseSensitive );
+ }
+ }
+
+ /**
+ * @param str The string to check.
+ * @param isCaseSensitive Check case sensitive or not.
+ * @return true in case of matching pattern.
+ */
+ public boolean matchPatternStart( @Nonnull String str, boolean isCaseSensitive )
+ {
+ if ( regexPattern != null )
+ {
+ // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgment until we have
+ // a file to deal with, or we can definitely say this is an exclusion...
+ return true;
+ }
+ else
+ {
+ String altStr = source.replace( '\\', '/' );
+
+ return SelectorUtils.matchAntPathPatternStart( this, str, File.separator, isCaseSensitive )
+ || SelectorUtils.matchAntPathPatternStart( this, altStr, "/", isCaseSensitive );
+ }
+ }
+
+ /**
+ * @return Tokenized string.
+ */
+ public String[] getTokenizedPathString()
+ {
+ return tokenized;
+ }
+
+
+ /**
+ * @param string The part which will be checked to start with.
+ * @return true in case of starting with the string false otherwise.
+ */
+ public boolean startsWith( String string )
+ {
+ return source.startsWith( string );
+ }
+
+
+ static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
+ {
+ List ret = new ArrayList();
+ StringTokenizer st = new StringTokenizer( path, separator );
+ while ( st.hasMoreTokens() )
+ {
+ ret.add( st.nextToken() );
+ }
+ return ret.toArray( new String[ret.size()] );
+ }
+
+ /**
+ * @param source The source.
+ * @return The match pattern.
+ */
+ public static MatchPattern fromString( String source )
+ {
+ return new MatchPattern( source, File.separator );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java
new file mode 100644
index 000000000..693acb1cb
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/MatchPatterns.java
@@ -0,0 +1,96 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A list of patterns to be matched
+ *
+ * @author Kristian Rosenvold
+ * @deprecated use {@code java.nio.filejava.nio.file.DirectoryStream.Filter} and related classes
+ */
+@Deprecated
+public class MatchPatterns
+{
+ private final MatchPattern[] patterns;
+
+ private MatchPatterns( @Nonnull MatchPattern... patterns )
+ {
+ this.patterns = patterns;
+ }
+
+ /**
+ * Checks these MatchPatterns against a specified string.
+ *
+ * Uses far less string tokenization than any of the alternatives.
+ *
+ * @param name The name to look for
+ * @param isCaseSensitive If the comparison is case sensitive
+ * @return true if any of the supplied patterns match
+ */
+ public boolean matches( @Nonnull String name, boolean isCaseSensitive )
+ {
+ String[] tokenized = MatchPattern.tokenizePathToString( name, File.separator );
+ for ( MatchPattern pattern : patterns )
+ {
+ if ( pattern.matchPath( name, tokenized, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param name The name.
+ * @param isCaseSensitive being case sensetive.
+ * @return true if any of the supplied patterns match start.
+ */
+ public boolean matchesPatternStart( @Nonnull String name, boolean isCaseSensitive )
+ {
+ for ( MatchPattern includesPattern : patterns )
+ {
+ if ( includesPattern.matchPatternStart( name, isCaseSensitive ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * @param sources The sources
+ * @return Converted match patterns.
+ */
+ public static MatchPatterns from( @Nonnull String... sources )
+ {
+ final int length = sources.length;
+ MatchPattern[] result = new MatchPattern[length];
+ for ( int i = 0; i < length; i++ )
+ {
+ result[i] = MatchPattern.fromString( sources[i] );
+ }
+ return new MatchPatterns( result );
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java
new file mode 100644
index 000000000..08d7a1cd3
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/ScanConductor.java
@@ -0,0 +1,90 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+
+/**
+ *
Visitor pattern for the DirectoryScanner. A ScanConductor controls the scanning process.
+ *
+ *
Create an instance and pass it to
+ * {@link org.apache.maven.shared.utils.io.DirectoryScanner#setScanConductor(ScanConductor)}.
+ * You will get notified about every visited directory and file. You can use the {@link ScanAction}
+ * to control what should happen next.
+ *
+ *
A ScanConductor might also store own information but users must make sure that the state gets
+ * cleaned between two scan() invocations.
+ *
+ * @author Mark Struberg
+ *
+ * @deprecated use {@code java.nio.file.Files.walkFileTree()} and related classes
+ */
+@Deprecated
+public interface ScanConductor
+{
+ /**
+ *
+ */
+ enum ScanAction
+ {
+ /**
+ * Abort the whole scanning process. The current file will not
+ * be added anymore.
+ */
+ ABORT,
+
+ /**
+ * Continue the scanning with the next item in the list.
+ */
+ CONTINUE,
+
+ /**
+ * This response is only valid for {@link ScanConductor#visitDirectory(String, java.io.File)}.
+ * Do not recurse into the current directory. The current directory will not be added
+ * and the processing will be continued with the next item in the list.
+ */
+ NO_RECURSE,
+
+ /**
+ * Abort processing the current directory.
+ * The current file will not be added.
+ * The processing will continue it's scan in the parent directory if any.
+ */
+ ABORT_DIRECTORY
+ }
+
+ /**
+ * This method will get invoked for every detected directory.
+ *
+ * @param name the directory name (contains parent folders up to the pwd)
+ * @param directory The directory.
+ * @return the ScanAction to control how to proceed with the scanning
+ */
+ ScanAction visitDirectory( String name, File directory );
+
+ /**
+ * This method will get invoked for every detected file.
+ *
+ * @param name the file name (contains parent folders up to the pwd)
+ * @param file The file.
+ * @return the ScanAction to control how to proceed with the scanning
+ */
+ ScanAction visitFile( String name, File file );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java
new file mode 100644
index 000000000..b716e7c8a
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/SelectorUtils.java
@@ -0,0 +1,819 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.StringTokenizer;
+
+import javax.annotation.Nonnull;
+
+/**
+ *
This is a utility class used by selectors and DirectoryScanner. The
+ * functionality more properly belongs just to selectors, but unfortunately
+ * DirectoryScanner exposed these as protected methods. Thus we have to
+ * support any subclasses of DirectoryScanner that may access these methods.
+ *
+ *
This is a Singleton.
+ *
+ * @author Arnout J. Kuiper
+ * ajkuiper@wxs.nl
+ * @author Magesh Umasankar
+ * @author Bruce Atherton
+ *
+ * @deprecated use {@code java.nio.file.Files.walkFileTree()} and related classes
+ */
+@Deprecated
+public final class SelectorUtils
+{
+
+ /**
+ * Pattern handler prefix.
+ */
+ private static final String PATTERN_HANDLER_PREFIX = "[";
+
+ /**
+ * Pattern handler suffix.
+ */
+ public static final String PATTERN_HANDLER_SUFFIX = "]";
+
+ /**
+ * Regex start pattern.
+ */
+ public static final String REGEX_HANDLER_PREFIX = "%regex" + PATTERN_HANDLER_PREFIX;
+
+ /**
+ * ANT pattern prefix.
+ */
+ public static final String ANT_HANDLER_PREFIX = "%ant" + PATTERN_HANDLER_PREFIX;
+
+ /**
+ * Private Constructor
+ */
+ private SelectorUtils()
+ {
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b will yield true.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null.
+ * @param str The path to match, as a String. Must not be
+ * null.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str )
+ {
+ return matchPatternStart( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ *
+ * This is not a general purpose test and should only be used if you
+ * can live with false positives. For example, pattern=**\a
+ * and str=b will yield true.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null.
+ * @param str The path to match, as a String. Must not be
+ * null.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return whether or not a given path matches the start of a given
+ * pattern up to the first "**".
+ */
+ public static boolean matchPatternStart( String pattern, String str, boolean isCaseSensitive )
+ {
+ if ( isRegexPrefixedPattern( pattern ) )
+ {
+ // FIXME: ICK! But we can't do partial matches for regex, so we have to reserve judgement until we have
+ // a file to deal with, or we can definitely say this is an exclusion...
+ return true;
+ }
+ else
+ {
+ if ( isAntPrefixedPattern( pattern ) )
+ {
+ pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
+ pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
+ }
+
+ String altPattern = pattern.replace( '\\', '/' );
+ String altStr = str.replace( '\\', '/' );
+
+ return matchAntPathPatternStart( altPattern, altStr, "/", isCaseSensitive );
+ }
+ }
+
+ private static boolean matchAntPathPatternStart( String pattern, String str, String separator,
+ boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( separatorPatternStartSlashMismatch( pattern, str, separator ) )
+ {
+ return false;
+ }
+
+ List patDirs = tokenizePath( pattern, separator );
+ List strDirs = tokenizePath( str, separator );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs.get( patIdxStart );
+ if ( "**".equals( patDir ) )
+ {
+ break;
+ }
+ if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null.
+ * @param str The path to match, as a String. Must not be
+ * null.
+ * @return true if the pattern matches against the string,
+ * or false otherwise.
+ */
+ public static boolean matchPath( String pattern, String str )
+ {
+ return matchPath( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a given path matches a given pattern.
+ *
+ * @param pattern The pattern to match against. Must not be
+ * null.
+ * @param str The path to match, as a String. Must not be
+ * null.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true if the pattern matches against the string,
+ * or false otherwise.
+ */
+ public static boolean matchPath( String pattern, String str, boolean isCaseSensitive )
+ {
+ if ( pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
+ && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
+ {
+ pattern =
+ pattern.substring( REGEX_HANDLER_PREFIX.length(), pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
+
+ return str.matches( pattern );
+ }
+ else
+ {
+ if ( pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
+ && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX ) )
+ {
+ pattern = pattern.substring( ANT_HANDLER_PREFIX.length(),
+ pattern.length() - PATTERN_HANDLER_SUFFIX.length() );
+ }
+
+ return matchAntPathPattern( pattern, str, isCaseSensitive );
+ }
+ }
+
+ private static boolean matchAntPathPattern( String pattern, String str, boolean isCaseSensitive )
+ {
+ // When str starts with a File.separator, pattern has to start with a
+ // File.separator.
+ // When pattern starts with a File.separator, str has to start with a
+ // File.separator.
+ if ( str.startsWith( File.separator ) != pattern.startsWith( File.separator ) )
+ {
+ return false;
+ }
+
+ List patDirs = tokenizePath( pattern, File.separator );
+ List strDirs = tokenizePath( str, File.separator );
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.size() - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.size() - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs.get( patIdxStart );
+ if ( "**".equals( patDir ) )
+ {
+ break;
+ }
+ if ( !match( patDir, strDirs.get( strIdxStart ), isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !"**".equals( patDirs.get( i ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs.get( patIdxEnd );
+ if ( "**".equals( patDir ) )
+ {
+ break;
+ }
+ if ( !match( patDir, strDirs.get( strIdxEnd ), isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !"**".equals( patDirs.get( i ) ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( "**".equals( patDirs.get( i ) ) )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // '**/**' situation, so skip one
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ String subPat = patDirs.get( patIdxStart + j + 1 );
+ String subStr = strDirs.get( strIdxStart + i + j );
+ if ( !match( subPat, subStr, isCaseSensitive ) )
+ {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !"**".equals( patDirs.get( i ) ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null.
+ * @return true if the string matches against the pattern,
+ * or false otherwise.
+ */
+ public static boolean match( String pattern, String str )
+ {
+ return match( pattern, str, true );
+ }
+
+ /**
+ * Tests whether or not a string matches against a pattern.
+ * The pattern may contain two special characters:
+ * '*' means zero or more characters
+ * '?' means one and only one character
+ *
+ * @param pattern The pattern to match against.
+ * Must not be null.
+ * @param str The string which must be matched against the pattern.
+ * Must not be null.
+ * @param isCaseSensitive Whether or not matching should be performed
+ * case sensitively.
+ * @return true if the string matches against the pattern,
+ * or false otherwise.
+ */
+ public static boolean match( String pattern, String str, boolean isCaseSensitive )
+ {
+ char[] patArr = pattern.toCharArray();
+ char[] strArr = str.toCharArray();
+ int patIdxStart = 0;
+ int patIdxEnd = patArr.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strArr.length - 1;
+ char ch;
+
+ boolean containsStar = false;
+ for ( char aPatArr : patArr )
+ {
+ if ( aPatArr == '*' )
+ {
+ containsStar = true;
+ break;
+ }
+ }
+
+ if ( !containsStar )
+ {
+ // No '*'s, so we make a shortcut
+ if ( patIdxEnd != strIdxEnd )
+ {
+ return false; // Pattern and string do not have the same size
+ }
+ for ( int i = 0; i <= patIdxEnd; i++ )
+ {
+ ch = patArr[i];
+ if ( ch != '?' && !equals( ch, strArr[i], isCaseSensitive ) )
+ {
+ return false; // Character mismatch
+ }
+ }
+ return true; // String matches against pattern
+ }
+
+ if ( patIdxEnd == 0 )
+ {
+ return true; // Pattern contains only '*', which matches anything
+ }
+
+ // Process characters before first star
+ // CHECKSTYLE_OFF: InnerAssignment
+ while ( ( ch = patArr[patIdxStart] ) != '*' && strIdxStart <= strIdxEnd )
+ // CHECKSTYLE_ON: InnerAssignment
+ {
+ if ( ch != '?' && !equals( ch, strArr[strIdxStart], isCaseSensitive ) )
+ {
+ return false; // Character mismatch
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Process characters after last star
+ // CHECKSTYLE_OFF: InnerAssignment
+ while ( ( ch = patArr[patIdxEnd] ) != '*' && strIdxStart <= strIdxEnd )
+ // CHECKSTYLE_ON: InnerAssignment
+ {
+ if ( ch != '?' && !equals( ch, strArr[strIdxEnd], isCaseSensitive ) )
+ {
+ return false; // Character mismatch
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // All characters in the string are used. Check if only '*'s are
+ // left in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // process pattern between stars. padIdxStart and patIdxEnd point
+ // always to a '*'.
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] == '*' )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // Two stars next to each other, skip the first one.
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ ch = patArr[patIdxStart + j + 1];
+ if ( ch != '?' && !equals( ch, strArr[strIdxStart + i + j], isCaseSensitive ) )
+ {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ // All characters in the string are used. Check if only '*'s are left
+ // in the pattern. If so, we succeeded. Otherwise failure.
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( patArr[i] != '*' )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Tests whether two characters are equal.
+ */
+ private static boolean equals( char c1, char c2, boolean isCaseSensitive )
+ {
+ if ( c1 == c2 )
+ {
+ return true;
+ }
+ if ( !isCaseSensitive )
+ {
+ // NOTE: Try both upper case and lower case as done by String.equalsIgnoreCase()
+ if ( Character.toUpperCase( c1 ) == Character.toUpperCase( c2 )
+ || Character.toLowerCase( c1 ) == Character.toLowerCase( c2 ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Breaks a path up into a List of path elements, tokenizing on
+ * File.separator.
+ *
+ * @param path Path to tokenize. Must not be null.
+ * @param separator The separator to use
+ * @return a List of path elements from the tokenized path
+ */
+ private static List tokenizePath( String path, String separator )
+ {
+ List ret = new ArrayList();
+ StringTokenizer st = new StringTokenizer( path, separator );
+ while ( st.hasMoreTokens() )
+ {
+ ret.add( st.nextToken() );
+ }
+ return ret;
+ }
+
+
+ static boolean matchAntPathPatternStart( @Nonnull MatchPattern pattern,
+ @Nonnull String str,
+ @Nonnull String separator,
+ boolean isCaseSensitive )
+ {
+ return !separatorPatternStartSlashMismatch( pattern, str, separator )
+ && matchAntPathPatternStart( pattern.getTokenizedPathString(), str, separator, isCaseSensitive );
+ }
+
+ private static String[] tokenizePathToString( @Nonnull String path, @Nonnull String separator )
+ {
+ List ret = new ArrayList();
+ StringTokenizer st = new StringTokenizer( path, separator );
+ while ( st.hasMoreTokens() )
+ {
+ ret.add( st.nextToken() );
+ }
+ return ret.toArray( new String[ret.size()] );
+ }
+
+ private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
+ @Nonnull String str,
+ @Nonnull String separator,
+ boolean isCaseSensitive )
+ {
+ String[] strDirs = tokenizePathToString( str, separator );
+ return matchAntPathPatternStart( patDirs, strDirs, isCaseSensitive );
+ }
+
+ private static boolean matchAntPathPatternStart( @Nonnull String[] patDirs,
+ @Nonnull String[] tokenizedFileName,
+ boolean isCaseSensitive )
+ {
+
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = tokenizedFileName.length - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs[patIdxStart];
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, tokenizedFileName[strIdxStart], isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+
+ return strIdxStart > strIdxEnd || patIdxStart <= patIdxEnd;
+ }
+
+ private static boolean separatorPatternStartSlashMismatch( @Nonnull MatchPattern matchPattern, @Nonnull String str,
+ @Nonnull String separator )
+ {
+ return str.startsWith( separator ) != matchPattern.startsWith( separator );
+ }
+
+ private static boolean separatorPatternStartSlashMismatch( String pattern, String str, String separator )
+ {
+ return str.startsWith( separator ) != pattern.startsWith( separator );
+ }
+
+
+ static boolean matchAntPathPattern( String[] patDirs, String[] strDirs, boolean isCaseSensitive )
+ {
+ int patIdxStart = 0;
+ int patIdxEnd = patDirs.length - 1;
+ int strIdxStart = 0;
+ int strIdxEnd = strDirs.length - 1;
+
+ // up to first '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs[patIdxStart];
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, strDirs[strIdxStart], isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxStart++;
+ strIdxStart++;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs[i].equals( "**" ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ else
+ {
+ if ( patIdxStart > patIdxEnd )
+ {
+ // String not exhausted, but pattern is. Failure.
+ return false;
+ }
+ }
+
+ // up to last '**'
+ while ( patIdxStart <= patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ String patDir = patDirs[patIdxEnd];
+ if ( patDir.equals( "**" ) )
+ {
+ break;
+ }
+ if ( !match( patDir, strDirs[strIdxEnd], isCaseSensitive ) )
+ {
+ return false;
+ }
+ patIdxEnd--;
+ strIdxEnd--;
+ }
+ if ( strIdxStart > strIdxEnd )
+ {
+ // String is exhausted
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs[i].equals( "**" ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ while ( patIdxStart != patIdxEnd && strIdxStart <= strIdxEnd )
+ {
+ int patIdxTmp = -1;
+ for ( int i = patIdxStart + 1; i <= patIdxEnd; i++ )
+ {
+ if ( patDirs[i].equals( "**" ) )
+ {
+ patIdxTmp = i;
+ break;
+ }
+ }
+ if ( patIdxTmp == patIdxStart + 1 )
+ {
+ // '**/**' situation, so skip one
+ patIdxStart++;
+ continue;
+ }
+ // Find the pattern between padIdxStart & padIdxTmp in str between
+ // strIdxStart & strIdxEnd
+ int patLength = ( patIdxTmp - patIdxStart - 1 );
+ int strLength = ( strIdxEnd - strIdxStart + 1 );
+ int foundIdx = -1;
+ strLoop:
+ for ( int i = 0; i <= strLength - patLength; i++ )
+ {
+ for ( int j = 0; j < patLength; j++ )
+ {
+ String subPat = patDirs[patIdxStart + j + 1];
+ String subStr = strDirs[strIdxStart + i + j];
+ if ( !match( subPat, subStr, isCaseSensitive ) )
+ {
+ continue strLoop;
+ }
+ }
+
+ foundIdx = strIdxStart + i;
+ break;
+ }
+
+ if ( foundIdx == -1 )
+ {
+ return false;
+ }
+
+ patIdxStart = patIdxTmp;
+ strIdxStart = foundIdx + patLength;
+ }
+
+ for ( int i = patIdxStart; i <= patIdxEnd; i++ )
+ {
+ if ( !patDirs[i].equals( "**" ) )
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ static boolean isRegexPrefixedPattern( String pattern )
+ {
+ return pattern.length() > ( REGEX_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
+ && pattern.startsWith( REGEX_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
+ }
+
+ static boolean isAntPrefixedPattern( String pattern )
+ {
+ return pattern.length() > ( ANT_HANDLER_PREFIX.length() + PATTERN_HANDLER_SUFFIX.length() + 1 )
+ && pattern.startsWith( ANT_HANDLER_PREFIX ) && pattern.endsWith( PATTERN_HANDLER_SUFFIX );
+ }
+
+ static boolean matchAntPathPattern( @Nonnull MatchPattern matchPattern, @Nonnull String str,
+ @Nonnull String separator, boolean isCaseSensitive )
+ {
+ if ( separatorPatternStartSlashMismatch( matchPattern, str, separator ) )
+ {
+ return false;
+ }
+ String[] patDirs = matchPattern.getTokenizedPathString();
+ String[] strDirs = tokenizePathToString( str, separator );
+ return matchAntPathPattern( patDirs, strDirs, isCaseSensitive );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/WalkCollector.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/WalkCollector.java
new file mode 100644
index 000000000..3db835aea
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/io/WalkCollector.java
@@ -0,0 +1,83 @@
+package org.apache.maven.shared.utils.io;
+
+/*
+ * 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 java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @deprecated use {@code java.nio.file.FileVisitor} and related classes
+ */
+@Deprecated
+public class WalkCollector
+ implements DirectoryWalkListener
+{
+ final List steps;
+
+ File startingDir;
+
+ int startCount;
+
+ int finishCount;
+
+ int percentageLow;
+
+ int percentageHigh;
+
+ /**
+ * Create an instance.
+ */
+ public WalkCollector()
+ {
+ steps = new ArrayList();
+ startCount = 0;
+ finishCount = 0;
+ percentageLow = 0;
+ percentageHigh = 0;
+ }
+
+ /** {@inheritDoc} */
+ public void debug( String message )
+ {
+ // can be used to set some message
+ }
+
+ /** {@inheritDoc} */
+ public void directoryWalkStarting( File basedir )
+ {
+ startingDir = basedir;
+ startCount++;
+ }
+
+ /** {@inheritDoc} */
+ public void directoryWalkStep( int percentage, File file )
+ {
+ steps.add( file );
+ percentageLow = Math.min( percentageLow, percentage );
+ percentageHigh = Math.max( percentageHigh, percentage );
+ }
+
+ /** {@inheritDoc} */
+ public void directoryWalkFinished()
+ {
+ finishCount++;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/AnsiMessageBuilder.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/AnsiMessageBuilder.java
new file mode 100644
index 000000000..2d59bc9dc
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/AnsiMessageBuilder.java
@@ -0,0 +1,156 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.fusesource.jansi.Ansi;
+
+/**
+ * Message builder implementation that supports ANSI colors through
+ * Jansi with configurable styles through {@link Style}.
+ */
+class AnsiMessageBuilder
+ implements MessageBuilder, LoggerLevelRenderer
+{
+ private Ansi ansi;
+
+ AnsiMessageBuilder()
+ {
+ this( Ansi.ansi() );
+ }
+
+ AnsiMessageBuilder( StringBuilder builder )
+ {
+ this( Ansi.ansi( builder ) );
+ }
+
+ AnsiMessageBuilder( int size )
+ {
+ this( Ansi.ansi( size ) );
+ }
+
+ AnsiMessageBuilder( Ansi ansi )
+ {
+ this.ansi = ansi;
+ }
+
+ public String debug( String level )
+ {
+ return Style.DEBUG.apply( ansi ).a( level ).reset().toString();
+ }
+
+ public String info( String level )
+ {
+ return Style.INFO.apply( ansi ).a( level ).reset().toString();
+ }
+
+ public String warning( String level )
+ {
+ return Style.WARNING.apply( ansi ).a( level ).reset().toString();
+ }
+
+ public String error( String level )
+ {
+ return Style.ERROR.apply( ansi ).a( level ).reset().toString();
+ }
+
+ public AnsiMessageBuilder success( Object message )
+ {
+ Style.SUCCESS.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder warning( Object message )
+ {
+ Style.WARNING.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder failure( Object message )
+ {
+ Style.FAILURE.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder strong( Object message )
+ {
+ Style.STRONG.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder mojo( Object message )
+ {
+ Style.MOJO.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder project( Object message )
+ {
+ Style.PROJECT.apply( ansi ).a( message ).reset();
+ return this;
+ }
+
+ public AnsiMessageBuilder a( char[] value, int offset, int len )
+ {
+ ansi.a( value, offset, len );
+ return this;
+ }
+
+ public AnsiMessageBuilder a( char[] value )
+ {
+ ansi.a( value );
+ return this;
+ }
+
+ public AnsiMessageBuilder a( CharSequence value, int start, int end )
+ {
+ ansi.a( value, start, end );
+ return this;
+ }
+
+ public AnsiMessageBuilder a( CharSequence value )
+ {
+ ansi.a( value );
+ return this;
+ }
+
+ public AnsiMessageBuilder a( Object value )
+ {
+ ansi.a( value );
+ return this;
+ }
+
+ public AnsiMessageBuilder newline()
+ {
+ ansi.newline();
+ return this;
+ }
+
+ public AnsiMessageBuilder format( String pattern, Object... args )
+ {
+ ansi.format( pattern, args );
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ return ansi.toString();
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/LoggerLevelRenderer.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/LoggerLevelRenderer.java
new file mode 100644
index 000000000..9dac0eb17
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/LoggerLevelRenderer.java
@@ -0,0 +1,53 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.
+ */
+
+/**
+ * Logger level renderer, intended for Maven slf4j logging provider implementers to render
+ * logger level.
+ *
+ * @since 3.2.0
+ */
+public interface LoggerLevelRenderer
+{
+ /**
+ * Render DEBUG level.
+ * By default, bold cyan
+ */
+ String debug( String level );
+
+ /**
+ * Render INFO level.
+ * By default, bold blue
+ */
+ String info( String level );
+
+ /**
+ * Render WARNING level.
+ * By default, bold yellow
+ */
+ String warning( String level );
+
+ /**
+ * Render ERROR level.
+ * By default, bold red
+ */
+ String error( String level );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageBuilder.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageBuilder.java
new file mode 100644
index 000000000..060e824eb
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageBuilder.java
@@ -0,0 +1,103 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.
+ */
+
+/**
+ * Message builder that supports configurable styling.
+ * @see MessageUtils
+ * @since 3.1.0
+ */
+public interface MessageBuilder
+{
+ /**
+ * Append message content in success style.
+ * By default, bold green
+ */
+ MessageBuilder success( Object message );
+
+ /**
+ * Append message content in warning style.
+ * By default, bold yellow
+ */
+ MessageBuilder warning( Object message );
+
+ /**
+ * Append message content in failure style.
+ * By default, bold red
+ */
+ MessageBuilder failure( Object message );
+
+ /**
+ * Append message content in strong style.
+ * By default, bold
+ */
+ MessageBuilder strong( Object message );
+
+ /**
+ * Append message content in mojo style.
+ * By default, green
+ */
+ MessageBuilder mojo( Object message );
+
+ /**
+ * Append message content in project style.
+ * By default, cyan
+ */
+ MessageBuilder project( Object message );
+
+ //
+ // message building methods modelled after Ansi methods
+ //
+ /**
+ * Append content to the message buffer.
+ */
+ MessageBuilder a( char[] value, int offset, int len );
+
+ /**
+ * Append content to the message buffer.
+ */
+ MessageBuilder a( char[] value );
+
+ /**
+ * Append content to the message buffer.
+ */
+ MessageBuilder a( CharSequence value, int start, int end );
+
+ /**
+ * Append content to the message buffer.
+ */
+ MessageBuilder a( CharSequence value );
+
+ /**
+ * Append content to the message buffer.
+ */
+ MessageBuilder a( Object value );
+
+ /**
+ * Append newline to the message buffer.
+ */
+ MessageBuilder newline();
+
+ /**
+ * Append formatted content to the buffer.
+ * @see String#format(String, Object...)
+ */
+ MessageBuilder format( String pattern, Object... args );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageUtils.java
new file mode 100644
index 000000000..74b85a42c
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/MessageUtils.java
@@ -0,0 +1,204 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.fusesource.jansi.Ansi;
+import org.fusesource.jansi.AnsiConsole;
+
+/**
+ * Colored message utils, to manage colors consistently across plugins (only if Maven version is at least 3.5.0).
+ * For Maven version before 3.5.0, message built with this util will never add color.
+ *
+ * Internally, Jansi is used to render
+ * ANSI colors on any platform.
+ * @since 3.1.0
+ */
+public class MessageUtils
+{
+ private static final boolean JANSI;
+
+ /** Reference to the JVM shutdown hook, if registered */
+ private static Thread shutdownHook;
+
+ /** Synchronization monitor for the "uninstall" */
+ private static final Object STARTUP_SHUTDOWN_MONITOR = new Object();
+
+ static
+ {
+ boolean jansi = true;
+ try
+ {
+ // JAnsi is provided by Maven core since 3.5.0
+ Class.forName( "org.fusesource.jansi.Ansi" );
+ }
+ catch ( ClassNotFoundException cnfe )
+ {
+ jansi = false;
+ }
+ JANSI = jansi;
+ }
+
+ /**
+ * Install color support.
+ * This method is called by Maven core, and calling it is not necessary in plugins.
+ */
+ public static void systemInstall()
+ {
+ if ( JANSI )
+ {
+ AnsiConsole.systemInstall();
+ }
+ }
+
+ /**
+ * Undo a previous {@link #systemInstall()}. If {@link #systemInstall()} was called
+ * multiple times, {@link #systemUninstall()} must be called call the same number of times before
+ * it is actually uninstalled.
+ */
+ public static void systemUninstall()
+ {
+ synchronized ( STARTUP_SHUTDOWN_MONITOR )
+ {
+ doSystemUninstall();
+
+ // hook can only set when JANSI is true
+ if ( shutdownHook != null )
+ {
+ // if out and system_out are same instance again, ansi is assumed to be uninstalled
+ if ( AnsiConsole.out == AnsiConsole.system_out )
+ {
+ try
+ {
+ Runtime.getRuntime().removeShutdownHook( shutdownHook );
+ }
+ catch ( IllegalStateException ex )
+ {
+ // ignore - VM is already shutting down
+ }
+ }
+ }
+ }
+ }
+
+ private static void doSystemUninstall()
+ {
+ if ( JANSI )
+ {
+ AnsiConsole.systemUninstall();
+ }
+ }
+
+ /**
+ * Enables message color (if JAnsi is available).
+ * @param flag
+ */
+ public static void setColorEnabled( boolean flag )
+ {
+ if ( JANSI )
+ {
+ Ansi.setEnabled( flag );
+ }
+ }
+
+ /**
+ * Is message color enabled: requires JAnsi available (through Maven) and the color has not been disabled.
+ */
+ public static boolean isColorEnabled()
+ {
+ return JANSI ? Ansi.isEnabled() : false;
+ }
+
+ /**
+ * Create a default message buffer.
+ * @return a new buffer
+ */
+ public static MessageBuilder buffer()
+ {
+ return JANSI ? new AnsiMessageBuilder() : new PlainMessageBuilder();
+ }
+
+ /**
+ * Create a message buffer with defined String builder.
+ * @return a new buffer
+ */
+ public static MessageBuilder buffer( StringBuilder builder )
+ {
+ return JANSI ? new AnsiMessageBuilder( builder ) : new PlainMessageBuilder( builder );
+ }
+
+ /**
+ * Create a message buffer with an internal buffer of defined size.
+ * @return a new buffer
+ */
+ public static MessageBuilder buffer( int size )
+ {
+ return JANSI ? new AnsiMessageBuilder( size ) : new PlainMessageBuilder( size );
+ }
+
+ /**
+ * Create a logger level renderer.
+ * @return a logger level renderer
+ * @since 3.2.0
+ */
+ @SuppressWarnings( "checkstyle:magicnumber" )
+ public static LoggerLevelRenderer level()
+ {
+ return JANSI ? new AnsiMessageBuilder( 20 ) : new PlainMessageBuilder( 7 );
+ }
+
+ /**
+ * Remove any ANSI code from a message (colors or other escape sequences).
+ * @param msg message eventually containing ANSI codes
+ * @return the message with ANSI codes removed
+ */
+ public static String stripAnsiCodes( String msg )
+ {
+ return msg.replaceAll( "\u001B\\[[;\\d]*[ -/]*[@-~]", "" );
+ }
+
+ /**
+ * Register a shutdown hook with the JVM runtime, uninstalling Ansi support on
+ * JVM shutdown unless is has already been uninstalled at that time.
+ *
Delegates to {@link #doSystemUninstall()} for the actual uninstall procedure
+ *
+ * @see Runtime#addShutdownHook(Thread)
+ * @see MessageUtils#systemUninstall()
+ * @see #doSystemUninstall()
+ */
+ public static void registerShutdownHook()
+ {
+ if ( JANSI && shutdownHook == null )
+ {
+ // No shutdown hook registered yet.
+ shutdownHook = new Thread()
+ {
+ @Override
+ public void run()
+ {
+ synchronized ( STARTUP_SHUTDOWN_MONITOR )
+ {
+ doSystemUninstall();
+ }
+ }
+ };
+ Runtime.getRuntime().addShutdownHook( shutdownHook );
+ }
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/PlainMessageBuilder.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/PlainMessageBuilder.java
new file mode 100644
index 000000000..6a7b56e1b
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/PlainMessageBuilder.java
@@ -0,0 +1,142 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.
+ */
+
+/**
+ * Message builder implementation that just ignores styling, for Maven version earlier than 3.5.0.
+ */
+class PlainMessageBuilder
+ implements MessageBuilder, LoggerLevelRenderer
+{
+ private StringBuilder buffer;
+
+ PlainMessageBuilder()
+ {
+ buffer = new StringBuilder();
+ }
+
+ PlainMessageBuilder( StringBuilder builder )
+ {
+ buffer = builder;
+ }
+
+ PlainMessageBuilder( int size )
+ {
+ buffer = new StringBuilder( size );
+ }
+
+ public String debug( String level )
+ {
+ return a( level ).toString();
+ }
+
+ public String info( String level )
+ {
+ return a( level ).toString();
+ }
+
+ public String warning( String level )
+ {
+ return a( level ).toString();
+ }
+
+ public String error( String level )
+ {
+ return a( level ).toString();
+ }
+
+ public PlainMessageBuilder success( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder warning( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder failure( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder strong( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder mojo( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder project( Object message )
+ {
+ return a( message );
+ }
+
+ public PlainMessageBuilder a( char[] value, int offset, int len )
+ {
+ buffer.append( value, offset, len );
+ return this;
+ }
+
+ public PlainMessageBuilder a( char[] value )
+ {
+ buffer.append( value );
+ return this;
+ }
+
+ public PlainMessageBuilder a( CharSequence value, int start, int end )
+ {
+ buffer.append( value, start, end );
+ return this;
+ }
+
+ public PlainMessageBuilder a( CharSequence value )
+ {
+ buffer.append( value );
+ return this;
+ }
+
+ public PlainMessageBuilder a( Object value )
+ {
+ buffer.append( value );
+ return this;
+ }
+
+ public PlainMessageBuilder newline()
+ {
+ buffer.append( System.getProperty( "line.separator" ) );
+ return this;
+ }
+
+ public PlainMessageBuilder format( String pattern, Object... args )
+ {
+ buffer.append( String.format( pattern, args ) );
+ return this;
+ }
+
+ @Override
+ public String toString()
+ {
+ return buffer.toString();
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/Style.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/Style.java
new file mode 100644
index 000000000..756ff1411
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/Style.java
@@ -0,0 +1,182 @@
+package org.apache.maven.shared.utils.logging;
+
+/*
+ * 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.fusesource.jansi.Ansi;
+import org.fusesource.jansi.Ansi.Color;
+
+import java.util.Locale;
+
+/**
+ * Configurable message styles.
+ * @since 3.1.0
+ */
+enum Style
+{
+
+ DEBUG( "bold,cyan" ),
+ INFO( "bold,blue" ),
+ WARNING( "bold,yellow" ),
+ ERROR( "bold,red" ),
+ SUCCESS( "bold,green" ),
+ FAILURE( "bold,red" ),
+ STRONG( "bold" ),
+ MOJO( "green" ),
+ PROJECT( "cyan" );
+
+ private final boolean bold;
+
+ private final boolean bright;
+
+ private final Color color;
+
+ private final boolean bgBright;
+
+ private final Color bgColor;
+
+ Style( String defaultValue )
+ {
+ boolean currentBold = false;
+ boolean currentBright = false;
+ Color currentColor = null;
+ boolean currentBgBright = false;
+ Color currentBgColor = null;
+
+ String value = System.getProperty( "style." + name().toLowerCase( Locale.ENGLISH ),
+ defaultValue ).toLowerCase( Locale.ENGLISH );
+
+ for ( String token : value.split( "," ) )
+ {
+ if ( "bold".equals( token ) )
+ {
+ currentBold = true;
+ }
+ else if ( token.startsWith( "bg" ) )
+ {
+ token = token.substring( 2 );
+ if ( token.startsWith( "bright" ) )
+ {
+ currentBgBright = true;
+ token = token.substring( 6 );
+ }
+ currentBgColor = toColor( token );
+ }
+ else
+ {
+ if ( token.startsWith( "bright" ) )
+ {
+ currentBright = true;
+ token = token.substring( 6 );
+ }
+ currentColor = toColor( token );
+ }
+ }
+
+ this.bold = currentBold;
+ this.bright = currentBright;
+ this.color = currentColor;
+ this.bgBright = currentBgBright;
+ this.bgColor = currentBgColor;
+ }
+
+ private static Color toColor( String token )
+ {
+ for ( Color color : Color.values() )
+ {
+ if ( color.toString().equalsIgnoreCase( token ) )
+ {
+ return color;
+ }
+ }
+ return null;
+ }
+
+ Ansi apply( Ansi ansi )
+ {
+ if ( bold )
+ {
+ ansi.bold();
+ }
+ if ( color != null )
+ {
+ if ( bright )
+ {
+ ansi.fgBright( color );
+ }
+ else
+ {
+ ansi.fg( color );
+ }
+ }
+ if ( bgColor != null )
+ {
+ if ( bgBright )
+ {
+ ansi.bgBright( bgColor );
+ }
+ else
+ {
+ ansi.bg( bgColor );
+ }
+ }
+ return ansi;
+ }
+
+ @Override
+ public String toString()
+ {
+ if ( !bold && color == null && bgColor == null )
+ {
+ return name();
+ }
+ StringBuilder sb = new StringBuilder( name() + '=' );
+ if ( bold )
+ {
+ sb.append( "bold" );
+ }
+ if ( color != null )
+ {
+ if ( sb.length() > 0 )
+ {
+ sb.append( ',' );
+ }
+ if ( bright )
+ {
+ sb.append( "bright" );
+ }
+ sb.append( color.name() );
+ }
+ if ( bgColor != null )
+ {
+ if ( sb.length() > 0 )
+ {
+ sb.append( ',' );
+ }
+ sb.append( "bg" );
+ if ( bgBright )
+ {
+ sb.append( "bright" );
+ }
+ sb.append( bgColor.name() );
+ }
+ return sb.toString();
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/package-info.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/package-info.java
new file mode 100644
index 000000000..dfc5ddeed
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/logging/package-info.java
@@ -0,0 +1,51 @@
+// CHECKSTYLE_OFF: RegexpHeader
+/*
+ * 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.
+ */
+/**
+ * An API to write Maven messages to console with styled color content, consistently across whole
+ * Maven ecosystem (Maven itself or any plugin or extension).
+ *
+ * Messages are built with instances of {@link org.apache.maven.shared.utils.logging.MessageBuilder MessageBuilder}
+ * which provides a fluent API, while error level are colored by slf4j provider with
+ * {@link org.apache.maven.shared.utils.logging.LoggerLevelRenderer LoggerLevelRenderer}.
+ *
+ * {@link org.apache.maven.shared.utils.logging.MessageUtils MessageUtils} gives access to these builders.
+ *
+ * Plugins can use this API with any Maven version: color
+ * just won't be activated when run with Maven version older than 3.5.0.
+ *
+ * Styles are:
+ *
debug, info, warning and error for
+ * {@link org.apache.maven.shared.utils.logging.LoggerLevelRenderer logger level rendering},
+ *
success, warning, failure, strong, mojo
+ * and project for {@link org.apache.maven.shared.utils.logging.MessageBuilder message content}
+ *
+ * Default styles colors can be overridden through system properties, that can be set in MAVEN_OPTS
+ * environment variable (eventually in .mavenrc script):
+ *
system properties are named style.<style name>,
+ *
values are comma separated combination of bold, <color> and
+ * bg<color> (for background), where <color> is
+ * an ANSI color: black,
+ * red, green, yellow, blue, magenta,
+ * cyan or white, eventually with bright prefix
+ *
+ * @since 3.1.0
+ */
+package org.apache.maven.shared.utils.logging;
+
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/PrettyPrintXMLWriter.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/PrettyPrintXMLWriter.java
new file mode 100644
index 000000000..851cffbf2
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/PrettyPrintXMLWriter.java
@@ -0,0 +1,393 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import org.apache.maven.shared.utils.Os;
+
+/**
+ * XMLWriter with nice indentation.
+ *
+ * @author kama
+ */
+public class PrettyPrintXMLWriter
+ implements XMLWriter
+{
+ private static final char[] CLOSE_1 = "/>".toCharArray();
+
+ private static final char[] CLOSE_2 = "".toCharArray();
+
+ private static final char[] DEFAULT_LINE_INDENT = new char[]{ ' ', ' ' };
+
+ private PrintWriter writer;
+
+ private ArrayList elementStack = new ArrayList();
+
+ private boolean processingElement = false;
+
+ private boolean documentStarted = false;
+
+ private boolean endOnSameLine = false;
+
+ private int depth = 0;
+
+ private char[] lineIndent;
+
+ private char[] lineSeparator;
+
+ private String encoding;
+
+ private String docType;
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ */
+ public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent )
+ {
+ this( writer, lineIndent, null, null );
+ }
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ */
+ public PrettyPrintXMLWriter( Writer writer, String lineIndent )
+ {
+ this( new PrintWriter( writer ), lineIndent );
+ }
+
+ /**
+ * @param writer not null
+ */
+ public PrettyPrintXMLWriter( PrintWriter writer )
+ {
+ this( writer, null, null );
+ }
+
+ /**
+ * @param writer not null
+ */
+ public PrettyPrintXMLWriter( Writer writer )
+ {
+ this( new PrintWriter( writer ) );
+ }
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ * @param encoding could be null or invalid.
+ * @param doctype could be null.
+ */
+ public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String encoding, String doctype )
+ {
+ this( writer, lineIndent.toCharArray(), Os.LINE_SEP.toCharArray(), encoding, doctype );
+ }
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ * @param encoding could be null or invalid.
+ * @param doctype could be null.
+ */
+ public PrettyPrintXMLWriter( Writer writer, String lineIndent, String encoding, String doctype )
+ {
+ this( new PrintWriter( writer ), lineIndent, encoding, doctype );
+ }
+
+ /**
+ * @param writer not null
+ * @param encoding could be null or invalid.
+ * @param doctype could be null.
+ */
+ public PrettyPrintXMLWriter( PrintWriter writer, String encoding, String doctype )
+ {
+ this( writer, DEFAULT_LINE_INDENT, Os.LINE_SEP.toCharArray(), encoding, doctype );
+ }
+
+ /**
+ * @param writer not null
+ * @param encoding could be null or invalid.
+ * @param doctype could be null.
+ */
+ public PrettyPrintXMLWriter( Writer writer, String encoding, String doctype )
+ {
+ this( new PrintWriter( writer ), encoding, doctype );
+ }
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ * @param lineSeparator could be null, but the normal way is valid line separator
+ * @param encoding could be null or the encoding to use.
+ * @param doctype could be null.
+ */
+ public PrettyPrintXMLWriter( PrintWriter writer, String lineIndent, String lineSeparator, String encoding,
+ String doctype )
+ {
+ this( writer, lineIndent.toCharArray(), lineSeparator.toCharArray(), encoding, doctype );
+ }
+
+ /**
+ * @param writer not null
+ * @param lineIndent could be null, but the normal way is some spaces.
+ * @param lineSeparator could be null, but the normal way is valid line separator
+ * @param encoding could be null or the encoding to use.
+ * @param doctype could be null.
+ */
+ private PrettyPrintXMLWriter( PrintWriter writer, char[] lineIndent, char[] lineSeparator, String encoding,
+ String doctype )
+ {
+ super();
+ this.writer = writer;
+ this.lineIndent = lineIndent;
+ this.lineSeparator = lineSeparator;
+ this.encoding = encoding;
+ this.docType = doctype;
+
+ depth = 0;
+
+ // Fail early with assertions enabled. Issue is in the calling code not having checked for any errors.
+ assert !writer.checkError() : "Unexpected error state PrintWriter passed to PrettyPrintXMLWriter.";
+ }
+
+ /** {@inheritDoc} */
+ public void addAttribute( String key, String value ) throws IOException
+ {
+ if ( !processingElement )
+ {
+ throw new IllegalStateException( "currently processing no element" );
+ }
+
+ writer.write( ' ' );
+ writer.write( key );
+ writer.write( '=' );
+ XMLEncode.xmlEncodeTextAsPCDATA( value, true, '"', writer );
+ if ( writer.checkError() )
+ {
+ throw new IOException( "Failure adding attribute '" + key + "' with value '" + value + "'" );
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void setEncoding( String encoding )
+ {
+ if ( documentStarted )
+ {
+ throw new IllegalStateException( "Document headers already written!" );
+ }
+
+ this.encoding = encoding;
+ }
+
+ /** {@inheritDoc} */
+ public void setDocType( String docType )
+ {
+ if ( documentStarted )
+ {
+ throw new IllegalStateException( "Document headers already written!" );
+ }
+
+ this.docType = docType;
+ }
+
+ /**
+ * @param lineSeparator The line separator to be used.
+ */
+ public void setLineSeparator( String lineSeparator )
+ {
+ if ( documentStarted )
+ {
+ throw new IllegalStateException( "Document headers already written!" );
+ }
+
+ this.lineSeparator = lineSeparator.toCharArray();
+ }
+
+ /**
+ * @param lineIndentParameter The line indent parameter.
+ */
+ public void setLineIndenter( String lineIndentParameter )
+ {
+ if ( documentStarted )
+ {
+ throw new IllegalStateException( "Document headers already written!" );
+ }
+
+ this.lineIndent = lineIndentParameter.toCharArray();
+ }
+
+ /** {@inheritDoc} */
+ public void startElement( String elementName ) throws IOException
+ {
+ boolean firstLine = ensureDocumentStarted();
+
+ completePreviouslyOpenedElement();
+
+ if ( !firstLine )
+ {
+ newLine();
+ }
+
+ writer.write( '<' );
+ writer.write( elementName );
+ if ( writer.checkError() )
+ {
+ throw new IOException( "Failure starting element '" + elementName + "'." );
+ }
+
+ processingElement = true;
+
+ elementStack.add( depth++, elementName );
+ }
+
+ /** {@inheritDoc} */
+ public void writeText( String text ) throws IOException
+ {
+ ensureDocumentStarted();
+
+ completePreviouslyOpenedElement();
+
+ XMLEncode.xmlEncodeText( text, writer );
+
+ endOnSameLine = true;
+
+ if ( writer.checkError() )
+ {
+ throw new IOException( "Failure writing text." );
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void writeMarkup( String markup ) throws IOException
+ {
+ ensureDocumentStarted();
+
+ completePreviouslyOpenedElement();
+
+ writer.write( markup );
+
+ if ( writer.checkError() )
+ {
+ throw new IOException( "Failure writing markup." );
+ }
+ }
+
+ /** {@inheritDoc} */
+ public void endElement() throws IOException
+ {
+ String chars = elementStack.get( --depth );
+ if ( processingElement )
+ {
+ // this means we don't have any content yet so we just add a />
+ writer.write( CLOSE_1 );
+
+ processingElement = false;
+ }
+ else
+ {
+ if ( !endOnSameLine )
+ {
+ newLine();
+ }
+
+ // otherwise we need a full closing tag for that element
+ writer.write( CLOSE_2 );
+ writer.write( chars );
+ writer.write( '>' );
+ }
+
+ endOnSameLine = false;
+
+ if ( writer.checkError() )
+ {
+ throw new IOException( "Failure ending element." );
+ }
+ }
+
+ /**
+ * Write the documents if not already done.
+ *
+ * @return true if the document headers have freshly been written.
+ */
+ private boolean ensureDocumentStarted()
+ {
+ if ( !documentStarted )
+ {
+ if ( docType != null || encoding != null )
+ {
+ writeDocumentHeader();
+ }
+
+ documentStarted = true;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ private void writeDocumentHeader()
+ {
+ writer.write( "" );
+
+ newLine();
+
+ if ( docType != null )
+ {
+ writer.write( "' );
+ newLine();
+ }
+ }
+
+ private void newLine()
+ {
+ writer.write( lineSeparator );
+
+ for ( int i = 0; i < depth; i++ )
+ {
+ writer.write( lineIndent );
+ }
+ }
+
+ private void completePreviouslyOpenedElement()
+ {
+ if ( processingElement )
+ {
+ writer.write( '>' );
+ processingElement = false;
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLEncode.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLEncode.java
new file mode 100644
index 000000000..0d6ecd165
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLEncode.java
@@ -0,0 +1,378 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+
+/**
+ * Collection of XML encoding/decoding helpers.
+ * This is all about the special characters & and <, and for attributes
+ * " and '. These must be encoded/decoded from/to XML.
+ */
+final class XMLEncode
+{
+
+ private static final int CDATA_BLOCK_THRESHOLD_LENGTH = 12;
+
+ private static final char DEFAULT_QUOTE_CHAR = '"';
+
+ /**
+ * Checks if this text purely consists of the white space characters
+ * ' ', TAB, NEWLINE.
+ */
+ public static boolean isWhiteSpace( String text )
+ {
+ for ( int i = 0; i < text.length(); i++ )
+ {
+ char c = text.charAt( i );
+ if ( !Character.isWhitespace( c ) )
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Makes any text fit into XML attributes.
+ */
+ public static String xmlEncodeTextForAttribute( String text, char quoteChar )
+ {
+ if ( text == null )
+ {
+ return null;
+ }
+ return xmlEncodeTextAsPCDATA( text, true, quoteChar );
+ }
+
+ /**
+ * Encodes text as XML in the most suitable way, either CDATA block or PCDATA.
+ */
+ public static String xmlEncodeText( String text )
+ {
+ if ( text == null )
+ {
+ return null;
+ }
+ StringWriter writer = new StringWriter( text.length() * 2 );
+ xmlEncodeText( text, writer );
+ return writer.toString();
+ }
+
+ public static void xmlEncodeText( String text, Writer writer )
+ {
+ if ( text == null )
+ {
+ return;
+ }
+ try
+ {
+ if ( !needsEncoding( text ) )
+ {
+ writer.write( text );
+ return;
+ }
+ else
+ {
+ // only encode as cdata if is is longer than CDATA block overhead:
+ if ( text.length() > CDATA_BLOCK_THRESHOLD_LENGTH )
+ {
+ String cdata = xmlEncodeTextAsCDATABlock( text );
+ if ( cdata != null )
+ {
+ writer.write( cdata );
+ return;
+ }
+ }
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( e );
+ }
+ // if every thing else fails, do it the save way...
+ xmlEncodeTextAsPCDATA( text, false, DEFAULT_QUOTE_CHAR, writer );
+ }
+
+ /**
+ * Encodes any text as PCDATA.
+ */
+ public static String xmlEncodeTextAsPCDATA( String text )
+ {
+ if ( text == null )
+ {
+ return null;
+ }
+ return xmlEncodeTextAsPCDATA( text, false );
+ }
+
+ /**
+ * Encodes any text as PCDATA.
+ *
+ * @param forAttribute if you want
+ * quotes and apostrophes specially treated for attributes
+ */
+ public static String xmlEncodeTextAsPCDATA( String text, boolean forAttribute )
+ {
+ return xmlEncodeTextAsPCDATA( text, forAttribute, DEFAULT_QUOTE_CHAR );
+ }
+
+ /**
+ * Encodes any text as PCDATA.
+ *
+ * @param forAttribute if you want
+ * quotes and apostrophes specially treated for attributes
+ * @param quoteChar if this is for attributes this char is used to quote the attribute value
+ */
+ public static String xmlEncodeTextAsPCDATA( String text, boolean forAttribute, char quoteChar )
+ {
+ if ( text == null )
+ {
+ return null;
+ }
+ StringWriter writer = new StringWriter( text.length() * 2 );
+ xmlEncodeTextAsPCDATA( text, forAttribute, quoteChar, writer );
+ return writer.toString();
+ }
+
+ public static void xmlEncodeTextAsPCDATA( String text, boolean forAttribute, char quoteChar, Writer n )
+ {
+ if ( text == null )
+ {
+ return;
+ }
+ try
+ {
+ char c;
+ int length = text.length();
+ if ( forAttribute )
+ {
+ n.append( quoteChar );
+ }
+
+ for ( int i = 0; i < length; i++ )
+ {
+ c = text.charAt( i );
+ switch ( c )
+ {
+ case '&':
+ n.append( "&" );
+ break;
+ case '<':
+ n.append( "<" );
+ break;
+ case '>': // FIX for sourceforge bug #802520 ("]]>" needs encoding)
+ n.append( ">" );
+ break;
+ case '"':
+ if ( forAttribute )
+ {
+ n.append( """ );
+ }
+ else
+ {
+ n.append( c );
+ }
+ break;
+ case '\'':
+ if ( forAttribute )
+ {
+ n.append( "'" );
+ }
+ else
+ {
+ n.append( c );
+ }
+ break;
+ case '\r':
+ if ( forAttribute )
+ {
+ if ( i == ( length - 1 ) || text.charAt( i + 1 ) != '\n' )
+ {
+ n.append( "
" );
+ }
+ }
+ else
+ {
+ n.append( c );
+ }
+ // but skip the \r in \r\n
+
+ break;
+ case '\n':
+ if ( forAttribute )
+ {
+ n.append( "
" );
+ }
+ break;
+
+ default:
+ n.append( c );
+ break;
+ }
+ }
+
+ if ( forAttribute )
+ {
+ n.append( quoteChar );
+ }
+ }
+ catch ( IOException e )
+ {
+ throw new RuntimeException( e );
+ }
+
+ }
+
+ /**
+ * Returns string as CDATA block if possible, otherwise null.
+ */
+ public static String xmlEncodeTextAsCDATABlock( String text )
+ {
+ if ( text == null )
+ {
+ return null;
+ }
+ if ( isCompatibleWithCDATABlock( text ) )
+ {
+ return "";
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ /**
+ * Checks if this text needs encoding in order to be represented in XML.
+ */
+ public static boolean needsEncoding( String text )
+ {
+ return needsEncoding( text, false );
+ }
+
+ /**
+ * Checks if this text needs encoding in order to be represented in XML.
+ *
+ * Set checkForAttr if you want to check for storability in
+ * an attribute.
+ */
+ public static boolean needsEncoding( String data, boolean checkForAttr )
+ {
+ if ( data == null )
+ {
+ return false;
+ }
+ char c;
+ for ( int i = 0; i < data.length(); i++ )
+ {
+ c = data.charAt( i );
+ if ( c == '&' || c == '<' || ( checkForAttr && ( c == '"' || c == '\'' ) ) )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Can this text be stored into a CDATA block?
+ */
+ public static boolean isCompatibleWithCDATABlock( String text )
+ {
+ return text != null && ( !text.contains( "]]>" ) );
+ }
+
+ /**
+ * Make CDATA out of possibly encoded PCDATA.
+ * E.g. make '&' out of '&'
+ */
+ public static String xmlDecodeTextToCDATA( String pcdata )
+ {
+ if ( pcdata == null )
+ {
+ return null;
+ }
+ char c, c1, c2, c3, c4, c5;
+ StringBuilder n = new StringBuilder( pcdata.length() );
+ for ( int i = 0; i < pcdata.length(); i++ )
+ {
+ c = pcdata.charAt( i );
+ if ( c == '&' )
+ {
+ c1 = lookAhead( 1, i, pcdata );
+ c2 = lookAhead( 2, i, pcdata );
+ c3 = lookAhead( 3, i, pcdata );
+ c4 = lookAhead( 4, i, pcdata );
+ c5 = lookAhead( 5, i, pcdata );
+
+ if ( c1 == 'a' && c2 == 'm' && c3 == 'p' && c4 == ';' )
+ {
+ n.append( "&" );
+ i += 4;
+ }
+ else if ( c1 == 'l' && c2 == 't' && c3 == ';' )
+ {
+ n.append( "<" );
+ i += 3;
+ }
+ else if ( c1 == 'g' && c2 == 't' && c3 == ';' )
+ {
+ n.append( ">" );
+ i += 3;
+ }
+ else if ( c1 == 'q' && c2 == 'u' && c3 == 'o' && c4 == 't' && c5 == ';' )
+ {
+ n.append( "\"" );
+ i += 5;
+ }
+ else if ( c1 == 'a' && c2 == 'p' && c3 == 'o' && c4 == 's' && c5 == ';' )
+ {
+ n.append( "'" );
+ i += 5;
+ }
+ else
+ {
+ n.append( "&" );
+ }
+ }
+ else
+ {
+ n.append( c );
+ }
+ }
+ return n.toString();
+ }
+
+ private static char lookAhead( int la, int offset, String data )
+ {
+ try
+ {
+ return data.charAt( offset + la );
+ }
+ catch ( StringIndexOutOfBoundsException e )
+ {
+ return 0x0;
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLWriter.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLWriter.java
new file mode 100644
index 000000000..0daad8bc2
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XMLWriter.java
@@ -0,0 +1,90 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+
+/**
+ * Interface for tools writing XML files.
+ * XMLWriters are not thread safe and must not be accessed concurrently.
+ */
+public interface XMLWriter
+{
+
+ /**
+ * Sets the encoding of the document.
+ * If not set, UTF-8 is being used
+ *
+ * @param encoding the encoding
+ * @throws IllegalStateException if the generation of the document has already started
+ */
+ void setEncoding( String encoding );
+
+ /**
+ * Sets the docType of the document.
+ *
+ * @param docType the docType
+ * @throws IllegalStateException if the generation of the document has already started
+ */
+ void setDocType( String docType );
+
+
+ /**
+ * Start an XML Element tag.
+ * @param name The name of the tag.
+ * @throws IOException if starting the element fails.
+ */
+ void startElement( String name ) throws IOException;
+
+
+ /**
+ * Add a XML attribute to the current XML Element.
+ * This method must get called immediately after {@link #startElement(String)}
+ * @param key The key of the attribute.
+ * @param value The value of the attribute.
+ * @throws IllegalStateException if no element tag is currently in process
+ * @throws IOException if adding the attribute fails.
+ */
+ void addAttribute( String key, String value ) throws IOException;
+
+ /**
+ * Add a value text to the current element tag
+ * This will perform XML escaping to guarantee valid content
+ * @param text The text which should be written.
+ * @throws IllegalStateException if no element tag got started yet
+ * @throws IOException if writing the text fails.
+ */
+ void writeText( String text ) throws IOException;
+
+ /**
+ * Add a preformatted markup to the current element tag
+ * @param text The text which should be written.
+ * @throws IllegalStateException if no element tag got started yet
+ * @throws IOException if writing the markup fails.
+ */
+ void writeMarkup( String text ) throws IOException;
+
+ /**
+ * End the previously opened element.
+ * @see #startElement(String)
+ * @throws IOException if ending the element fails.
+ */
+ void endElement() throws IOException;
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlReaderException.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlReaderException.java
new file mode 100644
index 000000000..f1bc0c6f9
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlReaderException.java
@@ -0,0 +1,167 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * The XmlReaderException is thrown by the XmlReader constructors if the charset encoding can not be determined
+ * according to the XML 1.0 specification and RFC 3023.
+ *
+ * The exception returns the unconsumed InputStream to allow the application to do an alternate processing with the
+ * stream. Note that the original InputStream given to the XmlReader cannot be used as that one has been already read.
+ *
+ *
+ * @author Alejandro Abdelnur
+ * @version revision 1.1 taken on 26/06/2007 from Rome (see https://rome.dev.java.net/source/browse/rome/src/java/com/sun/syndication/io/XmlReaderException.java)
+ */
+class XmlReaderException
+ extends IOException
+{
+ /**
+ *
+ */
+ private static final long serialVersionUID = 5044326391409597950L;
+
+ private final String bomEncoding;
+
+ private final String xmlGuessEncoding;
+
+ private final String xmlEncoding;
+
+ private final String contentTypeMime;
+
+ private final String contentTypeEncoding;
+
+ private final InputStream is;
+
+ /**
+ * Creates an exception instance if the charset encoding could not be determined.
+ *
+ * Instances of this exception are thrown by the XmlReader.
+ *
+ *
+ * @param msg message describing the reason for the exception.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ * @param is the unconsumed InputStream.
+ */
+ XmlReaderException( String msg, String bomEnc, String xmlGuessEnc, String xmlEnc, InputStream is )
+ {
+ this( msg, null, null, bomEnc, xmlGuessEnc, xmlEnc, is );
+ }
+
+ /**
+ * Creates an exception instance if the charset encoding could not be determined.
+ *
+ * Instances of this exception are thrown by the XmlReader.
+ *
+ *
+ * @param msg message describing the reason for the exception.
+ * @param ctMime MIME type in the content-type.
+ * @param ctEnc encoding in the content-type.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ * @param is the unconsumed InputStream.
+ */
+ XmlReaderException( String msg, String ctMime, String ctEnc, String bomEnc, String xmlGuessEnc,
+ String xmlEnc, InputStream is )
+ {
+ super( msg );
+ contentTypeMime = ctMime;
+ contentTypeEncoding = ctEnc;
+ bomEncoding = bomEnc;
+ xmlGuessEncoding = xmlGuessEnc;
+ xmlEncoding = xmlEnc;
+ this.is = is;
+ }
+
+ /**
+ * Returns the BOM encoding found in the InputStream.
+ *
+ *
+ * @return the BOM encoding, null if none.
+ */
+ public String getBomEncoding()
+ {
+ return bomEncoding;
+ }
+
+ /**
+ * Returns the encoding guess based on the first bytes of the InputStream.
+ *
+ *
+ * @return the encoding guess, null if it couldn't be guessed.
+ */
+ public String getXmlGuessEncoding()
+ {
+ return xmlGuessEncoding;
+ }
+
+ /**
+ * Returns the encoding found in the XML prolog of the InputStream.
+ *
+ *
+ * @return the encoding of the XML prolog, null if none.
+ */
+ public String getXmlEncoding()
+ {
+ return xmlEncoding;
+ }
+
+ /**
+ * Returns the MIME type in the content-type used to attempt determining the encoding.
+ *
+ *
+ * @return the MIME type in the content-type, null if there was not content-type or the encoding detection did not
+ * involve HTTP.
+ */
+ public String getContentTypeMime()
+ {
+ return contentTypeMime;
+ }
+
+ /**
+ * Returns the encoding in the content-type used to attempt determining the encoding.
+ *
+ *
+ * @return the encoding in the content-type, null if there was not content-type, no encoding in it or the encoding
+ * detection did not involve HTTP.
+ */
+ public String getContentTypeEncoding()
+ {
+ return contentTypeEncoding;
+ }
+
+ /**
+ * Returns the unconsumed InputStream to allow the application to do an alternate encoding detection on the
+ * InputStream.
+ *
+ *
+ * @return the unconsumed InputStream.
+ */
+ public InputStream getInputStream()
+ {
+ return is;
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReader.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReader.java
new file mode 100644
index 000000000..11a577742
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReader.java
@@ -0,0 +1,174 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.regex.Pattern;
+
+/**
+ *
+ */
+public class XmlStreamReader
+ extends Reader
+{
+ private final org.apache.commons.io.input.XmlStreamReader reader;
+
+ private static String staticDefaultEncoding = null;
+
+ /**
+ * @param encoding define the default encoding.
+ */
+ public static void setDefaultEncoding( String encoding )
+ {
+ staticDefaultEncoding = encoding;
+ }
+
+ /**
+ * @return the default encoding.
+ */
+ public static String getDefaultEncoding()
+ {
+ return staticDefaultEncoding;
+ }
+
+ /**
+ * @param file The file to create it from.
+ * @throws IOException in case of an error.
+ */
+ public XmlStreamReader( File file )
+ throws IOException
+ {
+ this( new FileInputStream( file ) );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @throws IOException in case of an error.
+ */
+ public XmlStreamReader( InputStream is )
+ throws IOException
+ {
+ this( is, true );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param lenient yes/no
+ * @throws IOException in case of an error.
+ * @throws XmlStreamReaderException in case of an error.
+ */
+ public XmlStreamReader( InputStream is, boolean lenient )
+ throws IOException, XmlStreamReaderException
+ {
+ reader = new org.apache.commons.io.input.XmlStreamReader( is, lenient, staticDefaultEncoding );
+ }
+
+ /**
+ * @param url {@link URL}
+ * @throws IOException in case of error.
+ */
+ public XmlStreamReader( URL url )
+ throws IOException
+ {
+ this( url.openConnection() );
+ }
+
+ /**
+ * @param conn The URL connection {@link URLConnection}.
+ * @throws IOException in case of error.
+ */
+ public XmlStreamReader( URLConnection conn )
+ throws IOException
+ {
+ reader = new org.apache.commons.io.input.XmlStreamReader( conn, staticDefaultEncoding );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param httpContentType content type.
+ * @throws IOException in case of error.
+ */
+ public XmlStreamReader( InputStream is, String httpContentType )
+ throws IOException
+ {
+ this( is, httpContentType, true );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param httpContentType content type.
+ * @param lenient yes/no.
+ * @param defaultEncoding The default encoding.
+ * @throws IOException in case of error.
+ * @throws XmlStreamReaderException in case of error.
+ */
+ public XmlStreamReader( InputStream is, String httpContentType, boolean lenient, String defaultEncoding )
+ throws IOException, XmlStreamReaderException
+ {
+ reader = new org.apache.commons.io.input.XmlStreamReader( is, httpContentType, lenient,
+ ( defaultEncoding == null )
+ ? staticDefaultEncoding
+ : defaultEncoding );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param httpContentType content type.
+ * @param lenient yes/no.
+ * @throws IOException in case of error.
+ * @throws XmlStreamReaderException in case of error.
+ */
+ public XmlStreamReader( InputStream is, String httpContentType, boolean lenient )
+ throws IOException, XmlStreamReaderException
+ {
+ this( is, httpContentType, lenient, null );
+ }
+
+ /**
+ * @return The current encoding.
+ */
+ public String getEncoding()
+ {
+ return reader.getEncoding();
+ }
+
+ /** {@inheritDoc} */
+ public int read( char[] buf, int offset, int len )
+ throws IOException
+ {
+ return reader.read( buf, offset, len );
+ }
+
+ /** {@inheritDoc} */
+ public void close()
+ throws IOException
+ {
+ reader.close();
+ }
+
+ static final Pattern ENCODING_PATTERN =
+ Pattern.compile( "<\\?xml.*encoding[\\s]*=[\\s]*((?:\".[^\"]*\")|(?:'.[^']*'))", Pattern.MULTILINE );
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReaderException.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReaderException.java
new file mode 100644
index 000000000..eeabae533
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamReaderException.java
@@ -0,0 +1,81 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.InputStream;
+
+/**
+ * The XmlStreamReaderException is thrown by the XmlStreamReader constructors if the charset encoding can not be
+ * determined according to the XML 1.0 specification and RFC 3023.
+ *
+ * The exception returns the unconsumed InputStream to allow the application to do an alternate processing with the
+ * stream. Note that the original InputStream given to the XmlStreamReader cannot be used as that one has been already
+ * read.
+ *
+ *
+ * @author Alejandro Abdelnur
+ * @version revision 1.1 taken on 26/06/2007 from Rome (see
+ * https://rome.dev.java.net/source/browse/rome/src/java/com/sun/syndication/io/XmlReaderException.java)
+ */
+class XmlStreamReaderException
+ extends XmlReaderException
+{
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1007947701939672080L;
+
+ /**
+ * Creates an exception instance if the charset encoding could not be determined.
+ *
+ * Instances of this exception are thrown by the XmlReader.
+ *
+ *
+ * @param msg message describing the reason for the exception.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ * @param is the unconsumed InputStream.
+ */
+ XmlStreamReaderException( String msg, String bomEnc, String xmlGuessEnc, String xmlEnc, InputStream is )
+ {
+ super( msg, bomEnc, xmlGuessEnc, xmlEnc, is );
+ }
+
+ /**
+ * Creates an exception instance if the charset encoding could not be determined.
+ *
+ * Instances of this exception are thrown by the XmlReader.
+ *
+ *
+ * @param msg message describing the reason for the exception.
+ * @param ctMime MIME type in the content-type.
+ * @param ctEnc encoding in the content-type.
+ * @param bomEnc BOM encoding.
+ * @param xmlGuessEnc XML guess encoding.
+ * @param xmlEnc XML prolog encoding.
+ * @param is the unconsumed InputStream.
+ */
+ XmlStreamReaderException( String msg, String ctMime, String ctEnc, String bomEnc, String xmlGuessEnc,
+ String xmlEnc, InputStream is )
+ {
+ super( msg, ctMime, ctEnc, bomEnc, xmlGuessEnc, xmlEnc, is );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamWriter.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamWriter.java
new file mode 100644
index 000000000..2923faf59
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlStreamWriter.java
@@ -0,0 +1,50 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.File;
+import java.io.FileNotFoundException;
+import java.io.OutputStream;
+
+/**
+ * We just wrap the commons StreamWriter to not get into troubles
+ * by exposing shaded commons-io packages
+ */
+public class XmlStreamWriter
+ extends org.apache.commons.io.output.XmlStreamWriter
+{
+ /**
+ * @param out {@link OutputStream}
+ */
+ public XmlStreamWriter( OutputStream out )
+ {
+ super( out );
+ }
+
+ /**
+ * @param file The file to use.
+ * @throws FileNotFoundException in case of not found file.
+ */
+ public XmlStreamWriter( File file )
+ throws FileNotFoundException
+ {
+ super( file );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlWriterUtil.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlWriterUtil.java
new file mode 100644
index 000000000..d9dd49e34
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/XmlWriterUtil.java
@@ -0,0 +1,362 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import org.apache.maven.shared.utils.StringUtils;
+
+/**
+ * Utility class for the XmlWriter class.
+ *
+ * @author Vincent Siveton
+ *
+ */
+public class XmlWriterUtil
+{
+ /** The vm line separator */
+ public static final String LS = System.getProperty( "line.separator" );
+
+ /** The default line indenter size i.e. 2. */
+ public static final int DEFAULT_INDENTATION_SIZE = 2;
+
+ /** The default column before line wrapping i.e. 80. */
+ public static final int DEFAULT_COLUMN_LINE = 80;
+
+ /**
+ * Convenience method to write one CRLF.
+ *
+ * @param writer not null writer
+ * @throws IOException if writing fails.
+ */
+ public static void writeLineBreak( XMLWriter writer ) throws IOException
+ {
+ writeLineBreak( writer, 1 );
+ }
+
+ /**
+ * Convenience method to repeat CRLF.
+ *
+ * @param writer not null
+ * @param repeat positive number
+ * @throws IOException if writing fails.
+ */
+ public static void writeLineBreak( XMLWriter writer, int repeat ) throws IOException
+ {
+ for ( int i = 0; i < repeat; i++ )
+ {
+ writer.writeMarkup( LS );
+ }
+ }
+
+ /**
+ * Convenience method to repeat CRLF and to indent the writer by 2.
+ *
+ * @param writer not null
+ * @param repeat The number of repetitions of the indent
+ * @param indent positive number
+ * @see #DEFAULT_INDENTATION_SIZE
+ * @see #writeLineBreak(XMLWriter, int, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeLineBreak( XMLWriter writer, int repeat, int indent ) throws IOException
+ {
+ writeLineBreak( writer, repeat, indent, DEFAULT_INDENTATION_SIZE );
+ }
+
+ /**
+ * Convenience method to repeat CRLF and to indent the writer by indentSize.
+ *
+ * @param writer not null
+ * @param repeat The number of repetitions of the indent
+ * @param indent positive number
+ * @param indentSize positive number
+ * @throws IOException if writing fails.
+ */
+ public static void writeLineBreak( XMLWriter writer, int repeat, int indent, int indentSize ) throws IOException
+ {
+ writeLineBreak( writer, repeat );
+
+ if ( indent < 0 )
+ {
+ indent = 0;
+ }
+
+ if ( indentSize < 0 )
+ {
+ indentSize = 0;
+ }
+
+ writer.writeText( StringUtils.repeat( " ", indent * indentSize ) );
+ }
+
+ /**
+ * Convenience method to write XML comment line break. Its size is 80.
+ *
+ * @param writer not null
+ * @see #DEFAULT_COLUMN_LINE
+ * @see #writeCommentLineBreak(XMLWriter, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentLineBreak( XMLWriter writer ) throws IOException
+ {
+ writeCommentLineBreak( writer, DEFAULT_COLUMN_LINE );
+ }
+
+ /**
+ * Convenience method to write XML comment line break with columnSize as length.
+ *
+ * @param writer not null
+ * @param columnSize positive number
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentLineBreak( XMLWriter writer, int columnSize ) throws IOException
+ {
+ if ( columnSize < 10 )
+ {
+ columnSize = DEFAULT_COLUMN_LINE;
+ }
+
+ writer.writeMarkup( "" + LS );
+ }
+
+ /**
+ * Convenience method to write XML comment line. The comment is splitted to have a size of
+ * 80.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @see #DEFAULT_INDENTATION_SIZE
+ * @see #writeComment(XMLWriter, String, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeComment( XMLWriter writer, String comment ) throws IOException
+ {
+ writeComment( writer, comment, 0, DEFAULT_INDENTATION_SIZE );
+ }
+
+ /**
+ * Convenience method to write XML comment line. The comment is split to have a size of
+ * 80 and is indented by indent using 2 as indentation size.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @see #DEFAULT_INDENTATION_SIZE
+ * @see #writeComment(XMLWriter, String, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeComment( XMLWriter writer, String comment, int indent ) throws IOException
+ {
+ writeComment( writer, comment, indent, DEFAULT_INDENTATION_SIZE );
+ }
+
+ /**
+ * Convenience method to write XML comment line. The comment is split to have a size of 80
+ * and is indented by indent using indentSize.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @param indentSize positive number
+ * @see #DEFAULT_COLUMN_LINE
+ * @see #writeComment(XMLWriter, String, int, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeComment( XMLWriter writer, String comment, int indent, int indentSize ) throws IOException
+ {
+ writeComment( writer, comment, indent, indentSize, DEFAULT_COLUMN_LINE );
+ }
+
+ /**
+ * Convenience method to write XML comment line. The comment is split to have a size of
+ * columnSize and is indented by indent using indentSize.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @param indentSize positive number
+ * @param columnSize positive number
+ * @throws IOException if writing fails.
+ */
+ public static void writeComment( XMLWriter writer, String comment, int indent, int indentSize, int columnSize )
+ throws IOException
+ {
+ if ( comment == null )
+ {
+ comment = "null";
+ }
+
+ if ( indent < 0 )
+ {
+ indent = 0;
+ }
+
+ if ( indentSize < 0 )
+ {
+ indentSize = 0;
+ }
+
+ if ( columnSize < 0 )
+ {
+ columnSize = DEFAULT_COLUMN_LINE;
+ }
+
+ String indentation = StringUtils.repeat( " ", indent * indentSize );
+ int magicNumber = indentation.length() + columnSize - "-->".length() - 1;
+ String[] sentences = StringUtils.split( comment, LS );
+
+ StringBuffer line = new StringBuffer( indentation + "" ).append( LS );
+ writer.writeMarkup( line.toString() );
+ }
+ line = new StringBuffer( indentation + "" ).append( LS );
+
+ writer.writeMarkup( line.toString() );
+ }
+
+ /**
+ * Convenience method to write XML comments between two comments line break.
+ * The XML comment block is not indented.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @see #DEFAULT_INDENTATION_SIZE
+ * @see #writeCommentText(XMLWriter, String, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentText( XMLWriter writer, String comment ) throws IOException
+ {
+ writeCommentText( writer, comment, 0, DEFAULT_INDENTATION_SIZE );
+ }
+
+ /**
+ * Convenience method to write XML comments between two comments line break.
+ * The XML comment block is also indented by indent using
+ * 2 as indentation size.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @see #DEFAULT_INDENTATION_SIZE
+ * @see #writeCommentText(XMLWriter, String, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentText( XMLWriter writer, String comment, int indent ) throws IOException
+ {
+ writeCommentText( writer, comment, indent, DEFAULT_INDENTATION_SIZE );
+ }
+
+ /**
+ * Convenience method to write XML comment between two comment line break.
+ * The XML comment block is also indented by indent using indentSize.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @param indentSize positive number
+ * @see #DEFAULT_COLUMN_LINE
+ * @see #writeCommentText(XMLWriter, String, int, int, int)
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentText( XMLWriter writer, String comment, int indent, int indentSize )
+ throws IOException
+ {
+ writeCommentText( writer, comment, indent, indentSize, DEFAULT_COLUMN_LINE );
+ }
+
+ /**
+ * Convenience method to write XML comments between two comments line break.
+ * The XML comment block is also indented by indent using indentSize.
+ * The column size could be also be specified.
+ *
+ * @param writer not null
+ * @param comment The comment to write
+ * @param indent positive number
+ * @param indentSize positive number
+ * @param columnSize positive number
+ * @throws IOException if writing fails.
+ */
+ public static void writeCommentText( XMLWriter writer, String comment, int indent, int indentSize, int columnSize )
+ throws IOException
+ {
+ if ( indent < 0 )
+ {
+ indent = 0;
+ }
+
+ if ( indentSize < 0 )
+ {
+ indentSize = 0;
+ }
+
+ if ( columnSize < 0 )
+ {
+ columnSize = DEFAULT_COLUMN_LINE;
+ }
+
+ writeLineBreak( writer, 1 );
+
+ writer.writeMarkup( StringUtils.repeat( " ", indent * indentSize ) );
+ writeCommentLineBreak( writer, columnSize );
+
+ writeComment( writer, comment, indent, indentSize, columnSize );
+
+ writer.writeMarkup( StringUtils.repeat( " ", indent * indentSize ) );
+ writeCommentLineBreak( writer, columnSize );
+
+ writeLineBreak( writer, 1, indent, indentSize );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3Dom.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3Dom.java
new file mode 100644
index 000000000..1b2f1b66e
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3Dom.java
@@ -0,0 +1,429 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A reimplementation of Plexus Xpp3Dom based on the public interface of Plexus Xpp3Dom.
+ *
+ * @author Kristian Rosenvold
+ */
+public class Xpp3Dom
+ implements Iterable
+{
+ private static final long serialVersionUID = 2567894443061173996L;
+
+ private String name; // plexus: protected
+
+ private String value; // plexus: protected
+
+ private Map attributes; // plexus: protected
+
+ final List childList; // plexus: protected
+
+ final Map childMap; // plexus: protected
+
+ private Xpp3Dom parent; // plexus: protected
+
+ /**
+ * The attribute which identifies merge/append.
+ */
+ public static final String CHILDREN_COMBINATION_MODE_ATTRIBUTE = "combine.children";
+
+ private static final String CHILDREN_COMBINATION_MERGE = "merge";
+
+ /**
+ * The attribute append.
+ */
+ public static final String CHILDREN_COMBINATION_APPEND = "append";
+
+ @SuppressWarnings( "UnusedDeclaration" )
+ private static final String DEFAULT_CHILDREN_COMBINATION_MODE = CHILDREN_COMBINATION_MERGE; // plexus: public
+
+ /**
+ * The name of the attribute.
+ */
+ public static final String SELF_COMBINATION_MODE_ATTRIBUTE = "combine.self";
+
+ /**
+ * The attributes which identifies override.
+ */
+ public static final String SELF_COMBINATION_OVERRIDE = "override"; // plexus: public
+
+ /**
+ * The attribute which identifies merge
+ */
+ public static final String SELF_COMBINATION_MERGE = "merge";
+
+ @SuppressWarnings( "UnusedDeclaration" )
+ private static final String DEFAULT_SELF_COMBINATION_MODE = SELF_COMBINATION_MERGE; // plexus: public
+
+ private static final String[] EMPTY_STRING_ARRAY = new String[0];
+ private static final Xpp3Dom[] EMPTY_DOM_ARRAY = new Xpp3Dom[0];
+
+ /**
+ * @param name The name of the instance.
+ */
+ public Xpp3Dom( String name )
+ {
+ this.name = name;
+ childList = new ArrayList();
+ childMap = new HashMap();
+ }
+
+ /**
+ * Create instance.
+ * @param source The source.
+ */
+ public Xpp3Dom( Xpp3Dom source )
+ {
+ this( source, source.getName() );
+ }
+
+ /**
+ * Create instance.
+ * @param src The source Dom.
+ * @param name The name of the Dom.
+ */
+ public Xpp3Dom( @Nonnull Xpp3Dom src, String name )
+ {
+ this.name = name;
+
+ int size = src.getChildCount();
+ childList = new ArrayList( size );
+ childMap = new HashMap();
+
+ setValue( src.getValue() );
+
+ for ( String attributeName : src.getAttributeNames() )
+ {
+ setAttribute( attributeName, src.getAttribute( attributeName ) );
+ }
+
+ for ( Xpp3Dom xpp3Dom : src.getChildren() )
+ {
+ addChild( new Xpp3Dom( xpp3Dom ) );
+ }
+ }
+
+ /**
+ * @return The current name.
+ */
+ public String getName()
+ {
+ return name;
+ }
+
+ /**
+ * @return The current value.
+ */
+ @Nonnull public String getValue()
+ {
+ return value;
+ }
+
+ /**
+ * @param value The value to be set.
+ */
+ public void setValue( @Nonnull String value )
+ {
+ this.value = value;
+ }
+
+
+ /**
+ * @return The array of attribute names.
+ */
+ public String[] getAttributeNames()
+ {
+ boolean isNothing = attributes == null || attributes.isEmpty();
+ return isNothing ? EMPTY_STRING_ARRAY : attributes.keySet().toArray( new String[attributes.size()] );
+ }
+
+
+ /**
+ * @param nameParameter The name of the attribute.
+ * @return The attribute value.
+ */
+ public String getAttribute( String nameParameter )
+ {
+ return this.attributes != null ? this.attributes.get( nameParameter ) : null;
+ }
+
+ /**
+ * @param nameParameter The name of the attribute.
+ * @param valueParameter The value of the attribute.
+ */
+ public void setAttribute( @Nonnull String nameParameter, @Nonnull String valueParameter )
+ {
+ if ( valueParameter == null )
+ {
+ throw new NullPointerException( "value can not be null" );
+ }
+ if ( nameParameter == null )
+ {
+ throw new NullPointerException( "name can not be null" );
+ }
+ if ( attributes == null )
+ {
+ attributes = new HashMap();
+ }
+
+ attributes.put( nameParameter, valueParameter );
+ }
+
+ /**
+ * @param i The index to be selected.
+ * @return The child selected by index.
+ */
+ public Xpp3Dom getChild( int i )
+ {
+ return childList.get( i );
+ }
+
+ /**
+ * @param nameParameter The name of the child.
+ * @return The child selected by name.
+ */
+ public Xpp3Dom getChild( String nameParameter )
+ {
+ return childMap.get( nameParameter );
+ }
+
+ /**
+ * @param child The child to be added.
+ */
+ public void addChild( Xpp3Dom child )
+ {
+ child.setParent( this );
+ childList.add( child );
+ childMap.put( child.getName(), child );
+ }
+
+ /**
+ * @return The array of childs.
+ */
+ public Xpp3Dom[] getChildren()
+ {
+ boolean isNothing = childList == null || childList.isEmpty();
+ return isNothing ? EMPTY_DOM_ARRAY : childList.toArray( new Xpp3Dom[childList.size()] );
+ }
+
+ private List getChildrenList()
+ {
+ boolean isNothing = childList == null || childList.isEmpty();
+ return isNothing ? Collections.emptyList() : childList;
+ }
+
+ /**
+ * @param nameParameter The name of the child.
+ * @return The array of the Dom.
+ */
+ public Xpp3Dom[] getChildren( String nameParameter )
+ {
+ List children = getChildrenList( nameParameter );
+ return children.toArray( new Xpp3Dom[children.size()] );
+ }
+
+ List getChildrenList( String nameParameter )
+ {
+ if ( childList == null )
+ {
+ return Collections.emptyList();
+ }
+ else
+ {
+ ArrayList children = new ArrayList();
+ for ( Xpp3Dom aChildList : childList )
+ {
+ if ( nameParameter.equals( aChildList.getName() ) )
+ {
+ children.add( aChildList );
+ }
+ }
+ return children;
+ }
+ }
+
+ /**
+ * @return The number of childs.
+ */
+ public int getChildCount()
+ {
+ if ( childList == null )
+ {
+ return 0;
+ }
+
+ return childList.size();
+ }
+
+ /**
+ * @param i The child to be removed.
+ */
+ public void removeChild( int i )
+ {
+ Xpp3Dom child = childList.remove( i );
+ childMap.values().remove( child );
+ child.setParent( null );
+ }
+
+ /**
+ * @return The current parent.
+ */
+ public Xpp3Dom getParent()
+ {
+ return parent;
+ }
+
+ /**
+ * @param parent Set the parent.
+ */
+ public void setParent( Xpp3Dom parent )
+ {
+ this.parent = parent;
+ }
+
+ /**
+ * @param dominant The dominant part.
+ * @param recessive The recessive part.
+ * @param childMergeOverride true if child merge will take precedence false otherwise.
+ * @return The merged Xpp3Dom.
+ */
+ public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
+ {
+ return Xpp3DomUtils.mergeXpp3Dom( dominant, recessive, childMergeOverride );
+ }
+
+ /**
+ * @param dominant The dominant part.
+ * @param recessive The recessive part.
+ * @return The merged Xpp3Dom.
+ */
+ public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
+ {
+ return Xpp3DomUtils.mergeXpp3Dom( dominant, recessive );
+ }
+
+ /** {@inheritDoc} */
+ public boolean equals( Object obj )
+ {
+ if ( obj == this )
+ {
+ return true;
+ }
+
+ if ( !( obj instanceof Xpp3Dom ) )
+ {
+ return false;
+ }
+
+ Xpp3Dom dom = (Xpp3Dom) obj;
+
+ return !( name == null ? dom.name != null : !name.equals( dom.name ) )
+ && !( value == null ? dom.value != null : !value.equals( dom.value ) )
+ && !( attributes == null ? dom.attributes != null : !attributes.equals( dom.attributes ) )
+ && !( childList == null ? dom.childList != null : !childList.equals( dom.childList ) );
+ }
+
+ /** {@inheritDoc} */
+ public int hashCode()
+ {
+ int result = 17;
+ result = 37 * result + ( name != null ? name.hashCode() : 0 );
+ result = 37 * result + ( value != null ? value.hashCode() : 0 );
+ result = 37 * result + ( attributes != null ? attributes.hashCode() : 0 );
+ result = 37 * result + ( childList != null ? childList.hashCode() : 0 );
+ return result;
+ }
+
+ /** {@inheritDoc} */
+ public String toString()
+ {
+ try
+ {
+ StringWriter writer = new StringWriter();
+ Xpp3DomWriter.write( getPrettyPrintXMLWriter( writer ), this );
+ return writer.toString();
+ }
+ catch ( final IOException e )
+ {
+ // JDK error in StringWriter.
+ throw (AssertionError) new AssertionError( "Unexpected IOException from StringWriter." ).initCause( e );
+ }
+ }
+
+ /**
+ * @return Unescaped string.
+ */
+ public String toUnescapedString()
+ {
+ try
+ {
+ StringWriter writer = new StringWriter();
+ Xpp3DomWriter.write( getPrettyPrintXMLWriter( writer ), this, false );
+ return writer.toString();
+ }
+ catch ( final IOException e )
+ {
+ // JDK error in StringWriter.
+ throw (AssertionError) new AssertionError( "Unexpected IOException from StringWriter." ).initCause( e );
+ }
+ }
+
+ private PrettyPrintXMLWriter getPrettyPrintXMLWriter( StringWriter writer )
+ {
+ return new PrettyPrintXMLWriter( writer, "UTF-8", null );
+ }
+
+ /**
+ * @param str The string to be checked.
+ * @return true if the string is not empty (length > 0) and not null.
+ */
+ public static boolean isNotEmpty( String str )
+ {
+ return str != null && str.length() > 0;
+ }
+
+ /**
+ * @param str The string to be checked.
+ * @return true if the string is empty or null.
+ */
+ public static boolean isEmpty( String str )
+ {
+ return str == null || str.trim().length() == 0;
+ }
+
+ /** {@inheritDoc} */
+ public Iterator iterator()
+ {
+ return getChildrenList().iterator();
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomBuilder.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomBuilder.java
new file mode 100644
index 000000000..bfa77f370
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomBuilder.java
@@ -0,0 +1,295 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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.apache.maven.shared.utils.io.IOUtil;
+import org.apache.maven.shared.utils.xml.pull.XmlPullParserException;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+import javax.annotation.Nonnull;
+import javax.annotation.WillClose;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author Kristian Rosenvold
+ */
+public class Xpp3DomBuilder
+{
+ private static final boolean DEFAULT_TRIM = true;
+
+ /**
+ * @param reader {@link Reader}
+ * @return the built dom.
+ * @throws XmlPullParserException in case of an error.
+ */
+ public static Xpp3Dom build( @WillClose @Nonnull Reader reader )
+ throws XmlPullParserException
+ {
+ return build( reader, DEFAULT_TRIM );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param encoding The encoding.
+ * @return the built dom.
+ * @throws XmlPullParserException in case of an error.
+ */
+ public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding )
+ throws XmlPullParserException
+ {
+ return build( is, encoding, DEFAULT_TRIM );
+ }
+
+ /**
+ * @param is {@link InputStream}
+ * @param encoding The encoding.
+ * @param trim true/false.
+ * @return the built dom.
+ * @throws XmlPullParserException in case of an error.
+ */
+ public static Xpp3Dom build( @WillClose InputStream is, @Nonnull String encoding, boolean trim )
+ throws XmlPullParserException
+ {
+ try
+ {
+ Reader reader = new InputStreamReader( is, encoding );
+ return build( reader, trim );
+ }
+ catch ( UnsupportedEncodingException e )
+ {
+ throw new RuntimeException( e );
+ }
+ }
+
+ /**
+ * @param reader {@link Reader}
+ * @param trim true/false
+ * @return the built dom
+ * @throws XmlPullParserException in case of an error
+ */
+ public static Xpp3Dom build( @WillClose Reader reader, boolean trim )
+ throws XmlPullParserException
+ {
+ try
+ {
+ DocHandler docHandler = parseSax( new InputSource( reader ), trim );
+ reader.close();
+ return docHandler.result;
+ }
+ catch ( final IOException e )
+ {
+ throw new XmlPullParserException( e );
+ }
+ finally
+ {
+ IOUtil.close( reader );
+ }
+ }
+
+ private static DocHandler parseSax( @Nonnull InputSource inputSource, boolean trim )
+ throws XmlPullParserException
+ {
+ try
+ {
+ DocHandler ch = new DocHandler( trim );
+ XMLReader parser = createXmlReader();
+ parser.setContentHandler( ch );
+ parser.parse( inputSource );
+ return ch;
+ }
+ catch ( IOException e )
+ {
+ throw new XmlPullParserException( e );
+ }
+ catch ( SAXException e )
+ {
+ throw new XmlPullParserException( e );
+ }
+ }
+
+
+ private static XMLReader createXmlReader()
+ throws SAXException
+ {
+ XMLReader comSunXmlReader = instantiate( "com.sun.org.apache.xerces.internal.parsers.SAXParser" );
+ if ( comSunXmlReader != null )
+ {
+ return comSunXmlReader;
+ }
+
+ String key = "org.xml.sax.driver";
+ String oldParser = System.getProperty( key );
+ System.clearProperty( key ); // There's a "slight" problem with this an parallel maven: It does not work ;)
+
+ try
+ {
+ return org.xml.sax.helpers.XMLReaderFactory.createXMLReader();
+ }
+ finally
+ {
+ if ( oldParser != null )
+ {
+ System.setProperty( key, oldParser );
+ }
+ }
+
+ }
+
+ private static XMLReader instantiate( String s )
+ {
+ try
+ {
+ Class> aClass = Thread.currentThread().getContextClassLoader().loadClass( s );
+ return (XMLReader) aClass.newInstance();
+ }
+ catch ( ClassNotFoundException e )
+ {
+ return null;
+ }
+ catch ( InstantiationException e )
+ {
+ return null;
+ }
+ catch ( IllegalAccessException e )
+ {
+ return null;
+ }
+ }
+
+
+ private static class DocHandler
+ extends DefaultHandler
+ {
+ private final List elemStack = new ArrayList();
+
+ private final List values = new ArrayList();
+
+ Xpp3Dom result = null;
+
+ private final boolean trim;
+
+ private boolean spacePreserve = false;
+
+ DocHandler( boolean trim )
+ {
+ this.trim = trim;
+ }
+
+ @Override
+ public void startElement( String uri, String localName, String qName, Attributes attributes )
+ throws SAXException
+ {
+ spacePreserve = false;
+ Xpp3Dom child = new Xpp3Dom( localName );
+
+ attachToParent( child );
+ pushOnStack( child );
+
+ // Todo: Detecting tags that close immediately seem to be impossible in sax ?
+ // http://stackoverflow.com/questions/12968390/detecting-self-closing-tags-in-sax
+ values.add( new StringBuilder() );
+
+ int size = attributes.getLength();
+ for ( int i = 0; i < size; i++ )
+ {
+ String name = attributes.getQName( i );
+ String value = attributes.getValue( i );
+ child.setAttribute( name, value );
+ spacePreserve = spacePreserve || ( "xml:space".equals( name ) && "preserve".equals( value ) );
+ }
+ }
+
+ private boolean pushOnStack( Xpp3Dom child )
+ {
+ return elemStack.add( child );
+ }
+
+ private void attachToParent( Xpp3Dom child )
+ {
+ int depth = elemStack.size();
+ if ( depth > 0 )
+ {
+ elemStack.get( depth - 1 ).addChild( child );
+ }
+ }
+
+ private Xpp3Dom pop()
+ {
+ int depth = elemStack.size() - 1;
+ return elemStack.remove( depth );
+ }
+
+ @Override
+ public void endElement( String uri, String localName, String qName )
+ throws SAXException
+ {
+ int depth = elemStack.size() - 1;
+
+ Xpp3Dom element = pop();
+
+ /* this Object could be null if it is a singleton tag */
+ Object accumulatedValue = values.remove( depth );
+
+ if ( element.getChildCount() == 0 )
+ {
+ if ( accumulatedValue == null )
+ {
+ element.setValue( "" ); // null in xpp3dom, but we don't do that around here
+ }
+ else
+ {
+ element.setValue( accumulatedValue.toString() );
+ }
+ }
+
+ if ( depth == 0 )
+ {
+ result = element;
+ }
+ }
+
+ @Override
+ public void characters( char[] ch, int start, int length )
+ throws SAXException
+ {
+ String text = new String( ch, start, length );
+ appendToTopValue( ( trim && !spacePreserve ) ? text.trim() : text );
+ }
+
+ private void appendToTopValue( String toAppend )
+ {
+ // noinspection MismatchedQueryAndUpdateOfStringBuilder
+ StringBuilder stringBuilder = values.get( values.size() - 1 );
+ stringBuilder.append( toAppend );
+ }
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomUtils.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomUtils.java
new file mode 100644
index 000000000..1b10dc8c6
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomUtils.java
@@ -0,0 +1,162 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ *
+ */
+public class Xpp3DomUtils
+{
+ /**
+ * @param dominant {@link Xpp3Dom}
+ * @param recessive {@link Xpp3Dom}
+ * @param childMergeOverride true/false.
+ * @return Merged dom.
+ */
+ public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
+ {
+ return dominant != null ? merge( dominant, recessive, childMergeOverride ) : recessive;
+ }
+
+ /**
+ * @param dominant {@link Xpp3Dom}
+ * @param recessive {@link Xpp3Dom}
+ * @return Merged dom.
+ */
+ public static Xpp3Dom mergeXpp3Dom( Xpp3Dom dominant, Xpp3Dom recessive )
+ {
+ return dominant != null ? merge( dominant, recessive, null ) : recessive;
+ }
+
+ /**
+ * @param dominant {@link Xpp3Dom}
+ * @param recessive {@link Xpp3Dom}
+ * @param childMergeOverride true/false.
+ * @return Merged dom.
+ */
+ public static Xpp3Dom merge( Xpp3Dom dominant, Xpp3Dom recessive, Boolean childMergeOverride )
+ {
+ if ( recessive == null || isCombineSelfOverride( dominant ) )
+ {
+ return dominant;
+ }
+
+ if ( isEmpty( dominant.getValue() ) )
+ {
+ dominant.setValue( recessive.getValue() );
+ }
+
+ for ( String attr : recessive.getAttributeNames() )
+ {
+ if ( isEmpty( dominant.getAttribute( attr ) ) )
+ {
+ dominant.setAttribute( attr, recessive.getAttribute( attr ) );
+ }
+ }
+
+ if ( recessive.getChildCount() > 0 )
+ {
+ boolean mergeChildren = isMergeChildren( dominant, childMergeOverride );
+
+ if ( mergeChildren )
+ {
+ Map> commonChildren = getCommonChildren( dominant, recessive );
+ for ( Xpp3Dom recessiveChild : recessive )
+ {
+ Iterator it = commonChildren.get( recessiveChild.getName() );
+ if ( it == null )
+ {
+ dominant.addChild( new Xpp3Dom( recessiveChild ) );
+ }
+ else if ( it.hasNext() )
+ {
+ Xpp3Dom dominantChild = it.next();
+ merge( dominantChild, recessiveChild, childMergeOverride );
+ }
+ }
+ }
+ else
+ {
+ Xpp3Dom[] dominantChildren = dominant.getChildren();
+ dominant.childList.clear();
+ for ( Xpp3Dom child : recessive )
+ {
+ dominant.addChild( new Xpp3Dom( child ) );
+ }
+
+ for ( Xpp3Dom aDominantChildren : dominantChildren )
+ {
+ dominant.addChild( aDominantChildren );
+ }
+ }
+ }
+ return dominant;
+ }
+
+ private static Map> getCommonChildren( Xpp3Dom dominant, Xpp3Dom recessive )
+ {
+ Map> commonChildren = new HashMap>();
+
+ for ( String childName : recessive.childMap.keySet() )
+ {
+ List dominantChildren = dominant.getChildrenList( childName );
+ if ( dominantChildren.size() > 0 )
+ {
+ commonChildren.put( childName, dominantChildren.iterator() );
+ }
+ }
+ return commonChildren;
+ }
+
+ private static boolean isCombineSelfOverride( Xpp3Dom xpp3Dom )
+ {
+ String selfMergeMode = xpp3Dom.getAttribute( Xpp3Dom.SELF_COMBINATION_MODE_ATTRIBUTE );
+ return Xpp3Dom.SELF_COMBINATION_OVERRIDE.equals( selfMergeMode );
+ }
+
+ private static boolean isMergeChildren( Xpp3Dom dominant, Boolean override )
+ {
+ return override != null ? override : !isMergeChildren( dominant );
+ }
+
+ private static boolean isMergeChildren( Xpp3Dom dominant )
+ {
+ return Xpp3Dom.CHILDREN_COMBINATION_APPEND.equals(
+ dominant.getAttribute( Xpp3Dom.CHILDREN_COMBINATION_MODE_ATTRIBUTE ) );
+ }
+
+ /**
+ * @param str The string to be checked.
+ * @return true in case string is empty false otherwise.
+ */
+ public static boolean isEmpty( String str )
+ {
+ return str == null || str.trim().length() == 0;
+ }
+
+
+
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomWriter.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomWriter.java
new file mode 100644
index 000000000..b390c5454
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/Xpp3DomWriter.java
@@ -0,0 +1,96 @@
+package org.apache.maven.shared.utils.xml;
+
+/*
+ * 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 java.io.IOException;
+import java.io.PrintWriter;
+import java.io.Writer;
+
+/**
+ * @author Brett Porter
+ */
+public class Xpp3DomWriter
+{
+ /**
+ * @param writer {@link Writer}
+ * @param dom {@link Xpp3Dom}
+ * @throws IOException if writing fails.
+ */
+ public static void write( Writer writer, Xpp3Dom dom ) throws IOException
+ {
+ write( new PrettyPrintXMLWriter( writer ), dom );
+ }
+
+ /**
+ * @param writer {@link PrintWriter}
+ * @param dom {@link Xpp3Dom}
+ * @throws IOException if writing fails.
+ */
+ public static void write( PrintWriter writer, Xpp3Dom dom ) throws IOException
+ {
+ write( new PrettyPrintXMLWriter( writer ), dom );
+ }
+
+ /**
+ * @param xmlWriter {@link XMLWriter}
+ * @param dom {@link Xpp3Dom}
+ * @throws IOException if writing fails.
+ */
+ public static void write( XMLWriter xmlWriter, Xpp3Dom dom ) throws IOException
+ {
+ write( xmlWriter, dom, true );
+ }
+
+ /**
+ * @param xmlWriter {@link XMLWriter}
+ * @param dom {@link Xpp3Dom}
+ * @param escape true/false.
+ * @throws IOException if writing fails.
+ */
+ public static void write( XMLWriter xmlWriter, Xpp3Dom dom, boolean escape ) throws IOException
+ {
+ xmlWriter.startElement( dom.getName() );
+ String[] attributeNames = dom.getAttributeNames();
+ for ( String attributeName : attributeNames )
+ {
+ xmlWriter.addAttribute( attributeName, dom.getAttribute( attributeName ) );
+ }
+ Xpp3Dom[] children = dom.getChildren();
+ for ( Xpp3Dom aChildren : children )
+ {
+ write( xmlWriter, aChildren, escape );
+ }
+
+ String value = dom.getValue();
+ if ( value != null )
+ {
+ if ( escape )
+ {
+ xmlWriter.writeText( value );
+ }
+ else
+ {
+ xmlWriter.writeMarkup( value );
+ }
+ }
+ xmlWriter.endElement();
+ }
+
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/pull/XmlPullParserException.java b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/pull/XmlPullParserException.java
new file mode 100644
index 000000000..3858a9024
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/java/org/apache/maven/shared/utils/xml/pull/XmlPullParserException.java
@@ -0,0 +1,61 @@
+package org.apache.maven.shared.utils.xml.pull;
+
+/*
+ * 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.xml.sax.SAXException;
+
+import java.io.IOException;
+
+/**
+ *
+ */
+public class XmlPullParserException
+ extends RuntimeException
+{
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 117075811816936575L;
+
+ /**
+ * @param e IOException.
+ */
+ public XmlPullParserException( IOException e )
+ {
+ super( e );
+ }
+
+ /**
+ * @param e The exception.
+ */
+ public XmlPullParserException( SAXException e )
+ {
+ super( e );
+ }
+
+ /**
+ * @param message The message.
+ */
+ public XmlPullParserException( String message )
+ {
+ super( message );
+ }
+}
diff --git a/Java-base/maven-shared-utils/src/src/main/resources/META-INF/NOTICE b/Java-base/maven-shared-utils/src/src/main/resources/META-INF/NOTICE
new file mode 100644
index 000000000..3f59805ce
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/resources/META-INF/NOTICE
@@ -0,0 +1,2 @@
+This product includes software developed by
+The Apache Software Foundation (http://www.apache.org/).
diff --git a/Java-base/maven-shared-utils/src/src/main/resources/org/apache/maven/shared/utils/annotations.xml b/Java-base/maven-shared-utils/src/src/main/resources/org/apache/maven/shared/utils/annotations.xml
new file mode 100644
index 000000000..8ca14929a
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/main/resources/org/apache/maven/shared/utils/annotations.xml
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Java-base/maven-shared-utils/src/src/site/apt/index.apt.vm b/Java-base/maven-shared-utils/src/src/site/apt/index.apt.vm
new file mode 100644
index 000000000..419146d8b
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/site/apt/index.apt.vm
@@ -0,0 +1,52 @@
+ ------
+ Introduction
+ ------
+ Kristian Rosenvold
+ ------
+ 2013-07-24
+ ------
+
+ ~~ 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.
+
+ ~~ NOTE: For help with the syntax of this file, see:
+ ~~ http://maven.apache.org/doxia/references/apt-format.html
+
+
+${project.name}
+
+ This project aims to be a functional replacement for
+ {{{http://codehaus-plexus.github.io/plexus-utils/}plexus-utils}} in Maven.
+
+ It is not a 100% API compatible replacement though but a replacement :
+ lots of methods got cleaned up, generics got added and we dropped a lot of unused code.
+
+ Then there are additions, like
+ {{{./apidocs/org/apache/maven/shared/utils/logging/package-summary.html}styled message API}}.
+
+Why?
+
+ plexus-utils consisted mostly of code that was forked from various Apache projects.
+ maven-shared-utils is based on the original from the Apache sources.
+
+Why not commons?
+
+ We would prefer code to use commons-* where appropriate, but the plexus-utils became
+ slightly incompatible (different) from the commons over the years, so migrating is not
+ always a 1:1 operation. Migrating to maven-shared-utils is a 1:1 operation in most cases.
+
+ []
diff --git a/Java-base/maven-shared-utils/src/src/site/resources/download.cgi b/Java-base/maven-shared-utils/src/src/site/resources/download.cgi
new file mode 100644
index 000000000..1b178d2e6
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/site/resources/download.cgi
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# 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.
+#
+# Just call the standard mirrors.cgi script. It will use download.html
+# as the input template.
+exec /www/www.apache.org/dyn/mirrors/mirrors.cgi $*
\ No newline at end of file
diff --git a/Java-base/maven-shared-utils/src/src/site/site.xml b/Java-base/maven-shared-utils/src/src/site/site.xml
new file mode 100644
index 000000000..e607a00bc
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/site/site.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Java-base/maven-shared-utils/src/src/site/xdoc/download.xml.vm b/Java-base/maven-shared-utils/src/src/site/xdoc/download.xml.vm
new file mode 100644
index 000000000..666d997c4
--- /dev/null
+++ b/Java-base/maven-shared-utils/src/src/site/xdoc/download.xml.vm
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+ Download ${project.name} Source
+
+
+
+
+
${project.name} ${project.version} is distributed in source format. Use a source archive if you intend to build
+ ${project.name} yourself. Otherwise, simply use the ready-made binary artifacts from central repository.
+
+
You will be prompted for a mirror - if the file is not found on yours, please be patient, as it may take 24
+ hours to reach all mirrors.
+
+
In order to guard against corrupted downloads/installations, it is highly recommended to
+ verify the signature
+ of the release bundles against the public KEYS used by the Apache Maven
+ developers.
+
+ We strongly encourage our users to configure a Maven repository mirror closer to their location, please read How to Use Mirrors for Repositories.
+
+
+
+
+
+ [if-any logo]
+
+
+
+ [end]
+ The currently selected mirror is
+ [preferred].
+ If you encounter a problem with this mirror,
+ please select another mirror.
+ If all mirrors are failing, there are
+ backup
+ mirrors
+ (at the end of the mirrors list) that should be available.
+