1 /* 2 * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.jpackage.internal; 26 27 import java.io.File; 28 import java.io.IOException; 29 import java.nio.file.Files; 30 import java.nio.file.Path; 31 import java.util.ArrayList; 32 import java.util.Collection; 33 import java.util.Collections; 34 import java.util.HashMap; 35 import java.util.List; 36 import java.util.Map; 37 import java.util.function.BiFunction; 38 import java.util.stream.Collectors; 39 40 41 /** 42 * Group of paths. 43 * Each path in the group is assigned a unique id. 44 */ 45 final class PathGroup { 46 PathGroup(Map<Object, Path> paths) { 47 entries = Collections.unmodifiableMap(paths); 48 } 49 50 Path getPath(Object id) { 51 return entries.get(id); 52 } 53 54 /** 55 * All configured entries. 56 */ 57 List<Path> paths() { 58 return entries.values().stream().collect(Collectors.toList()); 59 } 60 61 /** 62 * Root entries. 63 */ 64 List<Path> roots() { 65 // Sort by the number of path components in ascending order. 66 List<Map.Entry<Path, Path>> sorted = normalizedPaths().stream().sorted( 67 (a, b) -> a.getKey().getNameCount() - b.getKey().getNameCount()).collect( 68 Collectors.toList()); 69 70 // Returns `true` if `a` is a parent of `b` 71 BiFunction<Map.Entry<Path, Path>, Map.Entry<Path, Path>, Boolean> isParentOrSelf = (a, b) -> { 72 return a == b || b.getKey().startsWith(a.getKey()); 73 }; 74 75 return sorted.stream().filter( 76 v -> v == sorted.stream().sequential().filter( 77 v2 -> isParentOrSelf.apply(v2, v)).findFirst().get()).map( 78 v -> v.getValue()).collect(Collectors.toList()); 79 } 80 81 long sizeInBytes() throws IOException { 82 long reply = 0; 83 for (Path dir : roots().stream().filter(f -> Files.isDirectory(f)).collect( 84 Collectors.toList())) { 85 reply += Files.walk(dir).filter(p -> Files.isRegularFile(p)).mapToLong( 86 f -> f.toFile().length()).sum(); 87 } 88 return reply; 89 } 90 91 PathGroup resolveAt(Path root) { 92 return new PathGroup(entries.entrySet().stream().collect( 93 Collectors.toMap(e -> e.getKey(), 94 e -> root.resolve(e.getValue())))); 95 } 96 97 void copy(PathGroup dst) throws IOException { 98 copy(this, dst, false); 99 } 100 101 void move(PathGroup dst) throws IOException { 102 copy(this, dst, true); 103 } 104 105 static interface Facade<T> { 106 PathGroup pathGroup(); 107 108 default Collection<Path> paths() { 109 return pathGroup().paths(); 110 } 111 112 default List<Path> roots() { 113 return pathGroup().roots(); 114 } 115 116 default long sizeInBytes() throws IOException { 117 return pathGroup().sizeInBytes(); 118 } 119 120 T resolveAt(Path root); 121 122 default void copy(Facade<T> dst) throws IOException { 123 pathGroup().copy(dst.pathGroup()); 124 } 125 126 default void move(Facade<T> dst) throws IOException { 127 pathGroup().move(dst.pathGroup()); 128 } 129 } 130 131 private static void copy(PathGroup src, PathGroup dst, boolean move) throws 132 IOException { 133 copy(move, src.entries.keySet().stream().filter( 134 id -> dst.entries.containsKey(id)).map(id -> Map.entry( 135 src.entries.get(id), dst.entries.get(id))).collect( 136 Collectors.toCollection(ArrayList::new))); 137 } 138 139 private static void copy(boolean move, List<Map.Entry<Path, Path>> entries) 140 throws IOException { 141 142 // destination -> source file mapping 143 Map<Path, Path> actions = new HashMap<>(); 144 for (var action: entries) { 145 Path src = action.getKey(); 146 Path dst = action.getValue(); 147 if (src.toFile().isDirectory()) { 148 Files.walk(src).forEach(path -> actions.put(dst.resolve( 149 src.relativize(path)).toAbsolutePath().normalize(), path)); 150 } else { 151 actions.put(dst.toAbsolutePath().normalize(), src); 152 } 153 } 154 155 for (var action : actions.entrySet()) { 156 Path dst = action.getKey(); 157 Path src = action.getValue(); 158 159 if (src.equals(dst) || !src.toFile().exists()) { 160 continue; 161 } 162 163 if (src.toFile().isDirectory()) { 164 Files.createDirectories(dst); 165 } else { 166 Files.createDirectories(dst.getParent()); 167 if (move) { 168 Files.move(src, dst); 169 } else { 170 Files.copy(src, dst); 171 } 172 } 173 } 174 175 if (move) { 176 // Delete source dirs. 177 for (var entry: entries) { 178 File srcFile = entry.getKey().toFile(); 179 if (srcFile.isDirectory()) { 180 IOUtils.deleteRecursive(srcFile); 181 } 182 } 183 } 184 } 185 186 private static Map.Entry<Path, Path> normalizedPath(Path v) { 187 final Path normalized; 188 if (!v.isAbsolute()) { 189 normalized = Path.of("./").resolve(v.normalize()); 190 } else { 191 normalized = v.normalize(); 192 } 193 194 return Map.entry(normalized, v); 195 } 196 197 private List<Map.Entry<Path, Path>> normalizedPaths() { 198 return entries.values().stream().map(PathGroup::normalizedPath).collect( 199 Collectors.toList()); 200 } 201 202 private final Map<Object, Path> entries; 203 }