aboutsummaryrefslogtreecommitdiffstats
path: root/pf4j/src/main/java/org/pf4j/util/FileUtils.java
blob: a05367c89d9c919f582accc68aef1a70faf2a9ab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
/*
 * Copyright (C) 2012-present the original author or authors.
 *
 * Licensed 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.
 */
package org.pf4j.util;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileFilter;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

/**
 * @author Decebal Suiu
 */
public class FileUtils {

    private static final Logger log = LoggerFactory.getLogger(FileUtils.class);

    public static List<String> readLines(Path path, boolean ignoreComments) throws IOException {
        File file = path.toFile();
        if (!file.exists() || !file.isFile()) {
            return new ArrayList<>();
        }

        List<String> lines = new ArrayList<>();

        try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
            String line;
            while ((line = reader.readLine()) != null) {
                if (ignoreComments && !line.startsWith("#") && !lines.contains(line)) {
                    lines.add(line);
                }
            }
        }

        return lines;
    }

    public static void writeLines(Collection<String> lines, File file) throws IOException {
        Files.write(file.toPath(), lines, StandardCharsets.UTF_8);
    }

    /**
     * Delete a file or recursively delete a folder, do not follow symlinks.
     *
     * @param path the file or folder to delete
     * @throws IOException if something goes wrong
     */
    public static void delete(Path path) throws IOException {
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {

           @Override
           public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException {
               if (!attrs.isSymbolicLink()) {
                   Files.delete(path);
               }

               return FileVisitResult.CONTINUE;
           }

           @Override
           public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
               Files.delete(dir);

               return FileVisitResult.CONTINUE;
           }

        });
    }

    public static List<File> getJars(Path folder) {
        List<File> bucket = new ArrayList<>();
        getJars(bucket, folder);

        return bucket;
    }

    private static void getJars(final List<File> bucket, Path folder) {
        FileFilter jarFilter = new JarFileFilter();
        FileFilter directoryFilter = new DirectoryFileFilter();

        if (Files.exists(folder) && Files.isDirectory(folder)) {
            File[] jars = folder.toFile().listFiles(jarFilter);
            for (int i = 0; (jars != null) && (i < jars.length); ++i) {
                bucket.add(jars[i]);
            }

            File[] directories = folder.toFile().listFiles(directoryFilter);
            for (int i = 0; (directories != null) && (i < directories.length); ++i) {
                File directory = directories[i];
                getJars(bucket, directory.toPath());
            }
        }
    }

    /**
     * Finds a path with various endings or null if not found.
     *
     * @param basePath the base name
     * @param endings a list of endings to search for
     * @return new path or null if not found
     */
    public static Path findWithEnding(Path basePath, String... endings) {
        for (String ending : endings) {
            Path newPath = basePath.resolveSibling(basePath.getFileName() + ending);
            if (Files.exists(newPath)) {
                return newPath;
            }
        }

        return null;
    }

    /**
     * Delete a file (not recursively) and ignore any errors.
     *
     * @param path the path to delete
     */
    public static void optimisticDelete(Path path) {
        if (path == null) {
            return;
        }

        try {
            Files.delete(path);
        } catch (IOException ignored) { }
    }

    /**
     * Unzip a zip file in a directory that has the same name as the zip file.
     * For example if the zip file is {@code my-plugin.zip} then the resulted directory
     * is {@code my-plugin}.
     *
     * @param filePath the file to evaluate
     * @return Path of unzipped folder or original path if this was not a zip file
     * @throws IOException on error
     */
    public static Path expandIfZip(Path filePath) throws IOException {
        if (!isZipFile(filePath)) {
            return filePath;
        }

        FileTime pluginZipDate = Files.getLastModifiedTime(filePath);
        String fileName = filePath.getFileName().toString();
        Path pluginDirectory = filePath.resolveSibling(fileName.substring(0, fileName.lastIndexOf(".")));

        if (!Files.exists(pluginDirectory) || pluginZipDate.compareTo(Files.getLastModifiedTime(pluginDirectory)) > 0) {
            // do not overwrite an old version, remove it
            if (Files.exists(pluginDirectory)) {
                FileUtils.delete(pluginDirectory);
            }

            // create root for plugin
            Files.createDirectories(pluginDirectory);

            // expand '.zip' file
            Unzip unzip = new Unzip();
            unzip.setSource(filePath.toFile());
            unzip.setDestination(pluginDirectory.toFile());
            unzip.extract();
            log.info("Expanded plugin zip '{}' in '{}'", filePath.getFileName(), pluginDirectory.getFileName());
        }

        return pluginDirectory;
    }

    /**
     * Return true only if path is a zip file.
     *
     * @param path to a file/dir
     * @return true if file with {@code .zip} ending
     */
    public static boolean isZipFile(Path path) {
        return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".zip");
    }

    /**
     * Return true only if path is a jar file.
     *
     * @param path to a file/dir
     * @return true if file with {@code .jar} ending
     */
    public static boolean isJarFile(Path path) {
        return Files.isRegularFile(path) && path.toString().toLowerCase().endsWith(".jar");
    }

    public static Path getPath(Path path, String first, String... more) throws IOException {
        URI uri = path.toUri();
        if (isJarFile(path)) {
            String pathString = path.toString();
            // transformation for Windows OS
            pathString = StringUtils.addStart(pathString.replace("\\", "/"), "/");
            // space is replaced with %20
            pathString = pathString.replaceAll(" ","%20");
            uri = URI.create("jar:file:" + pathString);
        }

        return getPath(uri, first, more);
    }

    public static Path getPath(URI uri, String first, String... more) throws IOException {
        try (FileSystem fileSystem = getFileSystem(uri)) {
            return fileSystem.getPath(first, more);
        }
    }

    public static Path findFile(Path directoryPath, String fileName) {
        File[] files = directoryPath.toFile().listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isFile()) {
                    if (file.getName().equals(fileName)) {
                        return file.toPath();
                    }
                } else if (file.isDirectory()) {
                    Path foundFile = findFile(file.toPath(), fileName);
                    if (foundFile != null) {
                        return foundFile;
                    }
                }
            }
        }

        return null;
    }

    private static FileSystem getFileSystem(URI uri) throws IOException {
        FileSystem fileSystem = FileSystems.getFileSystem(uri);
        if (fileSystem != null) {
            return fileSystem;
        }

        return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
    }

}