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.string.MoreStrings.isEmpty;
019import static java.util.Objects.requireNonNull;
020
021import java.io.IOException;
022import java.io.InputStream;
023import java.io.OutputStream;
024import java.net.MalformedURLException;
025import java.net.URL;
026import java.nio.file.Files;
027import java.nio.file.Path;
028import java.nio.file.Paths;
029
030import de.cuioss.tools.base.Preconditions;
031import de.cuioss.tools.logging.CuiLogger;
032import de.cuioss.tools.string.MoreStrings;
033import lombok.EqualsAndHashCode;
034import lombok.Getter;
035import lombok.ToString;
036
037/**
038 * File-system based variant. Responsible for all non
039 * {@link FileTypePrefix#CLASSPATH} files.
040 *
041 * @author Oliver Wolff
042 */
043@EqualsAndHashCode(of = { "normalizedPathName" })
044@ToString
045public class FileSystemLoader implements FileReaderWriter {
046
047    private static final long serialVersionUID = -1278929108857440808L;
048
049    private static final CuiLogger LOG = new CuiLogger(FileSystemLoader.class);
050
051    private final String normalizedPathName;
052
053    @Getter
054    private final boolean readable;
055
056    @Getter
057    private final boolean writable;
058
059    @Getter
060    private final StructuredFilename fileName;
061
062    /**
063     * @param pathName must not be null nor empty, must not start with the prefix
064     *                 "classpath:" but may start with the prefix "file:" and
065     *                 contain at least one character despite the prefix. On all
066     *                 other cases a {@link IllegalArgumentException} will be
067     *                 thrown.
068     */
069    public FileSystemLoader(final String pathName) {
070        requireNonNull(pathName);
071        normalizedPathName = checkPathName(pathName);
072        Preconditions.checkArgument(!isEmpty(normalizedPathName), "'%s' can not be normalized", pathName);
073        final var path = getPath();
074        readable = MorePaths.checkReadablePath(path, false, true);
075        writable = MorePaths.checkAccessiblePath(path, false, false);
076        fileName = new StructuredFilename(path.toAbsolutePath().getFileName().toString());
077    }
078
079    /**
080     * Variant that uses a path as the constructor argument
081     *
082     * @param path must not be null
083     */
084    public FileSystemLoader(final Path path) {
085        this(requireNonNull(path).toFile().getAbsolutePath());
086    }
087
088    /**
089     * @return the path
090     */
091    public Path getPath() {
092        return Paths.get(normalizedPathName);
093    }
094
095    @Override
096    public InputStream inputStream() throws IOException {
097        if (!isReadable()) {
098            throw new IllegalStateException("'" + normalizedPathName + "' is not readable");
099        }
100        return Files.newInputStream(getPath());
101    }
102
103    /**
104     * Checks and modifies a given pathName
105     *
106     * @param pathName must not be null nor empty, must not start with the prefix
107     *                 "classpath:" but may start with the prefix "file:" and
108     *                 contain at least one character despite the prefix. On all
109     *                 other cases a {@link IllegalArgumentException} will be
110     *                 thrown.
111     *
112     * @return the normalized pathname without prefix
113     */
114    public static String checkPathName(final String pathName) {
115        MoreStrings.requireNotEmpty(pathName, "pathName");
116        if (pathName.startsWith(FileTypePrefix.CLASSPATH.getPrefix())) {
117            throw new IllegalArgumentException("Invalid path name, must start not start with "
118                    + FileTypePrefix.CLASSPATH + " but was: " + pathName);
119        }
120        var newPathName = pathName;
121        if (pathName.startsWith(FileTypePrefix.FILE.getPrefix())) {
122            newPathName = FileTypePrefix.FILE.removePrefix(pathName);
123        } else if (pathName.startsWith(FileTypePrefix.EXTERNAL.getPrefix())) {
124            try {
125                newPathName = new java.io.File(".").getCanonicalPath() + FileTypePrefix.EXTERNAL.removePrefix(pathName);
126                LOG.debug("Loading config file from external path: {}", newPathName);
127            } catch (final IOException e) {
128                LOG.error("Retrieving the current dir failed: ", e);
129            }
130        }
131
132        if (isEmpty(newPathName)) {
133            throw new IllegalArgumentException("Filename " + pathName + " is invalid");
134        }
135        return MorePaths.getRealPathSafely(newPathName).toString();
136    }
137
138    @Override
139    public boolean isFilesystemLoader() {
140        return true;
141    }
142
143    @Override
144    public URL getURL() {
145        try {
146            return getPath().toUri().toURL();
147        } catch (final MalformedURLException e) {
148            throw new IllegalStateException(e);
149        }
150    }
151
152    /**
153     * Truncate and overwrite an existing file, or create the file if it doesn't
154     * initially exist.
155     */
156    @Override
157    public OutputStream outputStream() throws IOException {
158        if (!isWritable()) {
159            throw new IllegalStateException(normalizedPathName + " is not writable");
160        }
161        return Files.newOutputStream(getPath());
162    }
163}