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}