001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      https://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.commons.lang3;
018
019import java.util.Objects;
020
021/**
022 * Operations regarding the classpath.
023 *
024 * <p>
025 * The methods of this class do not allow {@code null} inputs.
026 * </p>
027 *
028 * @since 3.3
029 */
030//@Immutable
031public class ClassPathUtils {
032
033    /**
034     * Gets the package name of the context class.
035     * <p>
036     * Returns an empty string for an unnamed package, including when {@link Class#getPackage()} returns {@code null} on Java 8.
037     * </p>
038     *
039     * @param context The context class, must not be {@code null}.
040     * @return The package name, or an empty string for an unnamed package.
041     * @throws NullPointerException Thrown if {@code context} is null, represents an array, a primitive type or {@code void}, or represents a named class whose
042     *                              package is unavailable.
043     */
044    private static String getPackageName(final Class<?> context) {
045        final Package contextPackage = context.getPackage();
046        if (contextPackage == null && !context.isArray() && !context.isPrimitive() && context.getName().indexOf('.') < 0) {
047            return StringUtils.EMPTY;
048        }
049        return Objects.requireNonNull(contextPackage, "context").getName();
050    }
051
052    /**
053     * Converts a package name to a Java path ('/').
054     *
055     * @param path The source path.
056     * @return A package name.
057     * @throws NullPointerException Thrown if {@code path} is null.
058     * @since 3.13.0
059     */
060    public static String packageToPath(final String path) {
061        return Objects.requireNonNull(path, "path").replace('.', '/');
062    }
063
064    /**
065     * Converts a Java path ('/') to a package name.
066     *
067     * @param path The source path.
068     * @return A package name.
069     * @throws NullPointerException Thrown if {@code path} is null.
070     * @since 3.13.0
071     */
072    public static String pathToPackage(final String path) {
073        return Objects.requireNonNull(path, "path").replace('/', '.');
074    }
075
076    /**
077     * Qualifies a resource name with a prefix and separator.
078     * <p>
079     * Returns {@code resourceName} unchanged when the prefix is empty.
080     * </p>
081     *
082     * @param prefix       The package name or path, must not be {@code null}.
083     * @param resourceName The resource name, validated as non-null by the caller.
084     * @param separator    The separator between the prefix and resource name.
085     * @return The qualified resource name, or the unchanged resource name when the prefix is empty.
086     * @throws NullPointerException Thrown if {@code prefix} is null.
087     */
088    private static String qualify(final String prefix, final String resourceName, final char separator) {
089        return prefix.isEmpty() ? resourceName : prefix + separator + resourceName;
090    }
091
092    /**
093     * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context.
094     *
095     * <p>
096     * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed.
097     * </p>
098     *
099     * <p>
100     * If the context class is in an unnamed package, returns {@code resourceName} unchanged.
101     * </p>
102     *
103     * <pre>
104     * ClassPathUtils.toFullyQualifiedName(StringUtils.class, "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
105     * </pre>
106     *
107     * @param context      The context for constructing the name.
108     * @param resourceName The resource name to construct the fully qualified name for.
109     * @return The fully qualified name of the resource with name {@code resourceName}.
110     * @throws NullPointerException Thrown if either {@code context} or {@code resourceName} is null, if {@code context} represents an array, a primitive
111     *                              type or {@code void}, or if it represents a named class whose package is unavailable.
112     */
113    public static String toFullyQualifiedName(final Class<?> context, final String resourceName) {
114        Objects.requireNonNull(context, "context");
115        Objects.requireNonNull(resourceName, "resourceName");
116        return qualify(getPackageName(context), resourceName, '.');
117    }
118
119    /**
120     * Returns the fully qualified name for the resource with name {@code resourceName} relative to the given context.
121     *
122     * <p>
123     * Note that this method does not check whether the resource actually exists. It only constructs the name. Null inputs are not allowed.
124     * </p>
125     *
126     * <p>
127     * If the context is an unnamed package, returns {@code resourceName} unchanged.
128     * </p>
129     *
130     * <pre>
131     * ClassPathUtils.toFullyQualifiedName(StringUtils.class.getPackage(), "StringUtils.properties") = "org.apache.commons.lang3.StringUtils.properties"
132     * </pre>
133     *
134     * @param context      The context for constructing the name.
135     * @param resourceName The resource name to construct the fully qualified name for.
136     * @return The fully qualified name of the resource with name {@code resourceName}.
137     * @throws NullPointerException Thrown if either {@code context} or {@code resourceName} is null.
138     */
139    public static String toFullyQualifiedName(final Package context, final String resourceName) {
140        Objects.requireNonNull(context, "context");
141        Objects.requireNonNull(resourceName, "resourceName");
142        return qualify(context.getName(), resourceName, '.');
143    }
144
145    /**
146     * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context.
147     *
148     * <p>
149     * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed.
150     * </p>
151     *
152     * <p>
153     * If the context class is in an unnamed package, returns {@code resourceName} unchanged.
154     * </p>
155     *
156     * <pre>
157     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class, "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
158     * </pre>
159     *
160     * @param context      The context for constructing the path.
161     * @param resourceName The resource name to construct the fully qualified path for.
162     * @return The fully qualified path of the resource with name {@code resourceName}.
163     * @throws NullPointerException Thrown if either {@code context} or {@code resourceName} is null, if {@code context} represents an array, a primitive
164     *                              type or {@code void}, or if it represents a named class whose package is unavailable.
165     */
166    public static String toFullyQualifiedPath(final Class<?> context, final String resourceName) {
167        Objects.requireNonNull(context, "context");
168        Objects.requireNonNull(resourceName, "resourceName");
169        return qualify(packageToPath(getPackageName(context)), resourceName, '/');
170    }
171
172    /**
173     * Returns the fully qualified path for the resource with name {@code resourceName} relative to the given context.
174     *
175     * <p>
176     * Note that this method does not check whether the resource actually exists. It only constructs the path. Null inputs are not allowed.
177     * </p>
178     *
179     * <p>
180     * If the context is an unnamed package, returns {@code resourceName} unchanged.
181     * </p>
182     *
183     * <pre>
184     * ClassPathUtils.toFullyQualifiedPath(StringUtils.class.getPackage(), "StringUtils.properties") = "org/apache/commons/lang3/StringUtils.properties"
185     * </pre>
186     *
187     * @param context      The context for constructing the path.
188     * @param resourceName The resource name to construct the fully qualified path for.
189     * @return The fully qualified path of the resource with name {@code resourceName}.
190     * @throws NullPointerException Thrown if either {@code context} or {@code resourceName} is null.
191     */
192    public static String toFullyQualifiedPath(final Package context, final String resourceName) {
193        Objects.requireNonNull(context, "context");
194        Objects.requireNonNull(resourceName, "resourceName");
195        return qualify(packageToPath(context.getName()), resourceName, '/');
196    }
197
198    /**
199     * {@link ClassPathUtils} instances should NOT be constructed in standard programming. Instead, the class should be used as
200     * {@code ClassPathUtils.toFullyQualifiedName(MyClass.class, "MyClass.properties");}.
201     *
202     * <p>
203     * This constructor is public to permit tools that require a JavaBean instance to operate.
204     * </p>
205     *
206     * @deprecated TODO Make private in 4.0.
207     */
208    @Deprecated
209    public ClassPathUtils() {
210        // empty
211    }
212
213}