001/*
002 * Copyright 2023 the original author or authors.
003 * <p>
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 * <p>
008 * https://www.apache.org/licenses/LICENSE-2.0
009 * <p>
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package de.cuioss.tools.io;
017
018import static de.cuioss.tools.base.Preconditions.checkArgument;
019import static de.cuioss.tools.string.MoreStrings.isEmpty;
020import static java.util.Objects.requireNonNull;
021
022import java.io.BufferedInputStream;
023import java.io.IOException;
024import java.io.StringWriter;
025import java.nio.charset.Charset;
026import java.nio.charset.StandardCharsets;
027import java.nio.file.Files;
028import java.nio.file.Path;
029import java.nio.file.StandardCopyOption;
030
031import lombok.AccessLevel;
032import lombok.NoArgsConstructor;
033
034/**
035 * Utility class for dealing with generic / classpath related file access.
036 *
037 * @author Oliver Wolff
038 */
039@NoArgsConstructor(access = AccessLevel.PRIVATE)
040public final class FileLoaderUtility {
041
042    /**
043     * Returns an implementation of {@link FileLoader} matching to the given path.
044     *
045     * @param pathName must not be null or empty.
046     * @return a configured implementation of {@link FileLoader}. In case the
047     *         pathName is prefixed with {@link FileTypePrefix#CLASSPATH} it returns
048     *         a {@link ClassPathLoader}. If prefixed with
049     *         {@link FileTypePrefix#URL} it returns a {@link UrlLoader}. Otherwise,
050     *         it returns a {@link FileSystemLoader}.
051     */
052    public static FileLoader getLoaderForPath(final String pathName) {
053        if (isEmpty(pathName)) {
054            throw new IllegalArgumentException("pathName must not be null nor empty");
055        }
056        if (pathName.startsWith(FileTypePrefix.CLASSPATH.getPrefix())) {
057            return new ClassPathLoader(pathName);
058        }
059        if (pathName.startsWith(FileTypePrefix.URL.getPrefix())) {
060            return new UrlLoader(pathName);
061        }
062        return new FileSystemLoader(pathName);
063    }
064
065    /**
066     * Helper class that copies the content of a {@link FileLoader} to the
067     * temp-folder and references it
068     * <h2>Caution: Security-Impact</h2> Creating a temp-file might introduce a
069     * security issue. Never ever use this location for sensitive information that
070     * might be of interest for an attacker
071     *
072     * @param source           must not be null and represent an accessible file,
073     *                         saying {@link FileLoader#isReadable()}
074     * @param markDeleteOnExit if <code>true</code> the file will be marked to
075     *                         delete on Exit.
076     * @return a reference on a file copied in the temp folder
077     * @throws IOException
078     */
079    @SuppressWarnings("java:S5443") // owolff: See hint Caution: Security-Impact
080    public static Path copyFileToTemp(final FileLoader source, final boolean markDeleteOnExit) throws IOException {
081        checkArgument(null != source, "Attribute with name source must not be null");
082        checkArgument(source.isReadable(), "Source must be readable");
083
084        final var target = Files.createTempFile(source.getFileName().getNamePart(), source.getFileName().getSuffix());
085
086        try (final var inputStream = source.inputStream()) {
087            Files.copy(new BufferedInputStream(inputStream), target, StandardCopyOption.REPLACE_EXISTING);
088        }
089        if (markDeleteOnExit) {
090            target.toFile().deleteOnExit();
091        }
092        return target;
093    }
094
095    /**
096     * Convenience method for reading the content from a given {@link FileLoader}
097     * into a String
098     *
099     * @param fileLoader must not be null
100     * @param charset    must not be null
101     * @return The String content of the File represented by the given
102     *         {@link FileLoader}
103     * @throws IOException
104     */
105    public static String toString(final FileLoader fileLoader, final Charset charset) throws IOException {
106        requireNonNull(fileLoader);
107        final var writer = new StringWriter();
108        try (final var inputStream = fileLoader.inputStream()) {
109            IOStreams.copy(inputStream, writer, charset);
110        }
111        return writer.toString();
112    }
113
114    /**
115     * Convenience method for reading the content from a given {@link FileLoader}
116     * into a String read as UTF-8 String
117     *
118     * @param fileLoader must not be null
119     * @return The String content of the File represented by the given
120     *         {@link FileLoader}
121     * @throws IOException
122     */
123    public static String toString(final FileLoader fileLoader) throws IOException {
124        return toString(fileLoader, StandardCharsets.UTF_8);
125    }
126
127    /**
128     * Convenience method for reading the content from a given {@link FileLoader}
129     * into a String read as UTF-8 String
130     *
131     * @param fileLoader must not be null
132     * @return The String content of the File represented by the given
133     *         {@link FileLoader}
134     * @throws IllegalArgumentException masking the actual {@link IOException}
135     * @throws NullPointerException     if <code>fileLoader</code> is null
136     */
137    public static String toStringUnchecked(final FileLoader fileLoader) {
138        requireNonNull(fileLoader);
139        try {
140            return toString(fileLoader);
141        } catch (final IOException | IllegalStateException e) {
142            throw new IllegalArgumentException("Unable to read from Path " + fileLoader.getFileName(), e);
143        }
144    }
145}