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.IOException;
  28 import java.nio.file.Files;
  29 import java.nio.file.Path;
  30 import java.util.ArrayList;
  31 import java.util.Collection;
  32 import java.util.Collections;
  33 import java.util.List;
  34 import java.util.Map;
  35 import java.util.stream.Collectors;
  36 import java.util.stream.Stream;
  37 
  38 
  39 /**
  40  * Group of paths.
  41  * Each path in the group is assigned a unique id.
  42  */
  43 final class PathGroup {
  44     PathGroup(Map<Object, Path> paths) {
  45         entries = Collections.unmodifiableMap(paths);
  46     }
  47 
  48     Path getPath(Object id) {
  49         return entries.get(id);
  50     }
  51 
  52     /**
  53      * All configured entries.
  54      */
  55     Collection<Path> paths() {
  56         return entries.values();
  57     }
  58 
  59     /**
  60      * Root entries.
  61      */
  62     List<Path> roots() {
  63         // Sort by the number of path components in ascending order.
  64         List<Path> sorted = paths().stream().sorted(
  65                 (a, b) -> a.getNameCount() - b.getNameCount()).collect(
  66                         Collectors.toList());
  67 
  68         return paths().stream().filter(
  69                 v -> v == sorted.stream().sequential().filter(
  70                         v2 -> v == v2 || v2.endsWith(v)).findFirst().get()).collect(
  71                         Collectors.toList());
  72     }
  73 
  74     long sizeInBytes() throws IOException {
  75         long reply = 0;
  76         for (Path dir : roots().stream().filter(f -> Files.isDirectory(f)).collect(
  77                 Collectors.toList())) {
  78             reply += Files.walk(dir).filter(p -> Files.isRegularFile(p)).mapToLong(
  79                     f -> f.toFile().length()).sum();
  80         }
  81         return reply;
  82     }
  83 
  84     PathGroup resolveAt(Path root) {
  85         return new PathGroup(entries.entrySet().stream().collect(
  86                 Collectors.toMap(e -> e.getKey(),
  87                         e -> root.resolve(e.getValue()))));
  88     }
  89 
  90     void copy(PathGroup dst) throws IOException {
  91         copy(this, dst, false);
  92     }
  93 
  94     void move(PathGroup dst) throws IOException {
  95         copy(this, dst, true);
  96     }
  97 
  98     static interface Facade<T> {
  99         PathGroup pathGroup();
 100 
 101         default Collection<Path> paths() {
 102             return pathGroup().paths();
 103         }
 104 
 105         default List<Path> roots() {
 106             return pathGroup().roots();
 107         }
 108 
 109         default long sizeInBytes() throws IOException {
 110             return pathGroup().sizeInBytes();
 111         }
 112 
 113         T resolveAt(Path root);
 114 
 115         default void copy(Facade<T> dst) throws IOException {
 116             pathGroup().copy(dst.pathGroup());
 117         }
 118 
 119         default void move(Facade<T> dst) throws IOException {
 120             pathGroup().move(dst.pathGroup());
 121         }
 122     }
 123 
 124     private static void copy(PathGroup src, PathGroup dst, boolean move) throws
 125             IOException {
 126         copy(move, src.entries.keySet().stream().filter(
 127                 id -> dst.entries.containsKey(id)).map(id -> Map.entry(
 128                 src.entries.get(id), dst.entries.get(id))).collect(
 129                 Collectors.toCollection(ArrayList::new)));
 130     }
 131 
 132     private static void copy(boolean move, List<Map.Entry<Path, Path>> entries)
 133             throws IOException {
 134 
 135         // Reorder entries. Entries with source entries with the least amount of
 136         // descending entries found between source entries should go first.
 137         entries.sort((e1, e2) -> e1.getKey().getNameCount() - e2.getKey().getNameCount());
 138 
 139         for (var entry : entries.stream().sequential().filter(e -> {
 140             return e == entries.stream().sequential().filter(e2 -> isDuplicate(e2, e)).findFirst().get();
 141                 }).collect(Collectors.toList())) {
 142             Path src = entry.getKey();
 143             Path dst = entry.getValue();
 144 
 145             if (src.equals(dst)) {
 146                 continue;
 147             }
 148 
 149             Files.createDirectories(dst.getParent());
 150             if (move) {
 151                 Files.move(src, dst);
 152             } else if (src.toFile().isDirectory()) {
 153                 IOUtils.copyRecursive(src, dst);
 154             } else {
 155                 IOUtils.copyFile(src.toFile(), dst.toFile());
 156             }
 157         }
 158     }
 159 
 160     private static boolean isDuplicate(Map.Entry<Path, Path> a,
 161             Map.Entry<Path, Path> b) {
 162         if (a == b || a.equals(b)) {
 163             return true;
 164         }
 165 
 166         if (b.getKey().getNameCount() < a.getKey().getNameCount()) {
 167             return isDuplicate(b, a);
 168         }
 169 
 170         if (!a.getKey().endsWith(b.getKey())) {
 171             return false;
 172         }
 173 
 174         Path relativeSrcPath = a.getKey().relativize(b.getKey());
 175         Path relativeDstPath = a.getValue().relativize(b.getValue());
 176 
 177         return relativeSrcPath.equals(relativeDstPath);
 178     }
 179 
 180     private final Map<Object, Path> entries;
 181 }