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 }