1/*
2 * Copyright (c) 2015, 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 */
25package jdk.tools.jlink.internal;
26
27import java.io.File;
28import java.io.IOException;
29import java.io.InputStream;
30import java.nio.file.Files;
31import java.nio.file.Path;
32import java.util.ArrayList;
33import java.util.List;
34import java.util.Objects;
35import java.util.function.Consumer;
36import java.util.stream.Stream;
37
38/**
39 * An Archive backed by a directory.
40 */
41public class DirArchive implements Archive {
42
43    /**
44     * A File located in a Directory.
45     */
46    private class FileEntry extends Archive.Entry {
47
48        private final long size;
49        private final Path path;
50
51        FileEntry(Path path, String name) {
52            super(DirArchive.this, getPathName(path), name,
53                  Archive.Entry.EntryType.CLASS_OR_RESOURCE);
54            this.path = path;
55            try {
56                size = Files.size(path);
57            } catch (IOException ex) {
58                throw new RuntimeException(ex);
59            }
60        }
61
62        /**
63         * Returns the number of bytes of this file.
64         */
65        @Override
66        public long size() {
67            return size;
68        }
69
70        @Override
71        public InputStream stream() throws IOException {
72            InputStream stream = Files.newInputStream(path);
73            open.add(stream);
74            return stream;
75        }
76    }
77
78    private static final String MODULE_INFO = "module-info.class";
79
80    private final Path dirPath;
81    private final String moduleName;
82    private final List<InputStream> open = new ArrayList<>();
83    private final int chop;
84    private final Consumer<String> log;
85    private static final Consumer<String> noopConsumer = (String t) -> {
86    };
87
88    public DirArchive(Path dirPath) {
89        this(dirPath, noopConsumer);
90    }
91
92    public DirArchive(Path dirPath, Consumer<String> log) {
93        Objects.requireNonNull(dirPath);
94        if (!Files.isDirectory(dirPath)) {
95            throw new IllegalArgumentException(dirPath + " is not a directory");
96        }
97        chop = dirPath.toString().length() + 1;
98        this.moduleName = Objects.requireNonNull(dirPath.getFileName()).toString();
99        this.dirPath = dirPath;
100        this.log = log;
101    }
102
103    @Override
104    public String moduleName() {
105        return moduleName;
106    }
107
108    @Override
109    public Path getPath() {
110        return dirPath;
111    }
112
113    @Override
114    public Stream<Entry> entries() {
115        try {
116            return Files.walk(dirPath).map(this::toEntry).filter(n -> n != null);
117        } catch (IOException ex) {
118            throw new RuntimeException(ex);
119        }
120    }
121
122    private Archive.Entry toEntry(Path p) {
123        if (Files.isDirectory(p)) {
124            return null;
125        }
126        String name = getPathName(p).substring(chop);
127        log.accept(moduleName + "/" + name);
128        return new FileEntry(p, name);
129    }
130
131    @Override
132    public void close() throws IOException {
133        IOException e = null;
134        for (InputStream stream : open) {
135            try {
136                stream.close();
137            } catch (IOException ex) {
138                if (e == null) {
139                    e = ex;
140                } else {
141                    e.addSuppressed(ex);
142                }
143            }
144        }
145        if (e != null) {
146            throw e;
147        }
148    }
149
150    @Override
151    public void open() throws IOException {
152        // NOOP
153    }
154
155    private static String getPathName(Path path) {
156        return path.toString().replace(File.separatorChar, '/');
157    }
158}
159