ImageFileCreator.java revision 14919:cc2b9f4a15cb
1/*
2 * Copyright (c) 2014, 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.BufferedOutputStream;
28import java.io.DataOutputStream;
29import java.io.IOException;
30import java.io.OutputStream;
31import java.nio.ByteOrder;
32import java.nio.file.Files;
33import java.nio.file.Path;
34import java.util.ArrayList;
35import java.util.HashMap;
36import java.util.HashSet;
37import java.util.List;
38import java.util.Map;
39import java.util.Objects;
40import java.util.Set;
41import java.util.stream.Collectors;
42import java.util.stream.Stream;
43import jdk.tools.jlink.internal.Archive.Entry;
44import jdk.tools.jlink.internal.Archive.Entry.EntryType;
45import jdk.tools.jlink.internal.ModulePoolImpl.CompressedModuleData;
46import jdk.tools.jlink.plugin.PluginException;
47import jdk.tools.jlink.plugin.ModuleEntry;
48
49/**
50 * An image (native endian.)
51 * <pre>{@code
52 * {
53 *   u4 magic;
54 *   u2 major_version;
55 *   u2 minor_version;
56 *   u4 resource_count;
57 *   u4 table_length;
58 *   u4 location_attributes_size;
59 *   u4 strings_size;
60 *   u4 redirect[table_length];
61 *   u4 offsets[table_length];
62 *   u1 location_attributes[location_attributes_size];
63 *   u1 strings[strings_size];
64 *   u1 content[if !EOF];
65 * }
66 * }</pre>
67 */
68public final class ImageFileCreator {
69    private final Map<String, List<Entry>> entriesForModule = new HashMap<>();
70    private final ImagePluginStack plugins;
71    private ImageFileCreator(ImagePluginStack plugins) {
72        this.plugins = Objects.requireNonNull(plugins);
73    }
74
75    public static ExecutableImage create(Set<Archive> archives,
76            ImagePluginStack plugins)
77            throws IOException {
78        return ImageFileCreator.create(archives, ByteOrder.nativeOrder(),
79                plugins);
80    }
81
82    public static ExecutableImage create(Set<Archive> archives,
83            ByteOrder byteOrder)
84            throws IOException {
85        return ImageFileCreator.create(archives, byteOrder,
86                new ImagePluginStack());
87    }
88
89    public static ExecutableImage create(Set<Archive> archives,
90            ByteOrder byteOrder,
91            ImagePluginStack plugins)
92            throws IOException
93    {
94        ImageFileCreator image = new ImageFileCreator(plugins);
95        try {
96            image.readAllEntries(archives);
97            // write to modular image
98            image.writeImage(archives, byteOrder);
99        } finally {
100            //Close all archives
101            for (Archive a : archives) {
102                a.close();
103            }
104        }
105
106        return plugins.getExecutableImage();
107    }
108
109    private void readAllEntries(Set<Archive> archives) {
110        archives.stream().forEach((archive) -> {
111            Map<Boolean, List<Entry>> es;
112            try (Stream<Entry> entries = archive.entries()) {
113                es = entries.collect(Collectors.partitioningBy(n -> n.type()
114                        == EntryType.CLASS_OR_RESOURCE));
115            }
116            String mn = archive.moduleName();
117            List<Entry> all = new ArrayList<>();
118            all.addAll(es.get(false));
119            all.addAll(es.get(true));
120            entriesForModule.put(mn, all);
121        });
122    }
123
124    public static boolean isClassPackage(String path) {
125        return path.endsWith(".class") && !path.endsWith("module-info.class");
126    }
127
128    public static void recreateJimage(Path jimageFile,
129            Set<Archive> archives,
130            ImagePluginStack pluginSupport)
131            throws IOException {
132        try {
133            Map<String, List<Entry>> entriesForModule
134                    = archives.stream().collect(Collectors.toMap(
135                                    Archive::moduleName,
136                                    a -> {
137                                        try (Stream<Entry> entries = a.entries()) {
138                                            return entries.collect(Collectors.toList());
139                                        }
140                                    }));
141            ByteOrder order = ByteOrder.nativeOrder();
142            BasicImageWriter writer = new BasicImageWriter(order);
143            ModulePoolImpl pool = createPools(archives, entriesForModule, order, writer);
144            try (OutputStream fos = Files.newOutputStream(jimageFile);
145                    BufferedOutputStream bos = new BufferedOutputStream(fos);
146                    DataOutputStream out = new DataOutputStream(bos)) {
147                generateJImage(pool, writer, pluginSupport, out);
148            }
149        } finally {
150            //Close all archives
151            for (Archive a : archives) {
152                a.close();
153            }
154        }
155    }
156
157    private void writeImage(Set<Archive> archives,
158            ByteOrder byteOrder)
159            throws IOException {
160        BasicImageWriter writer = new BasicImageWriter(byteOrder);
161        ModulePoolImpl allContent = createPools(archives,
162                entriesForModule, byteOrder, writer);
163        ModulePoolImpl result = generateJImage(allContent,
164             writer, plugins, plugins.getJImageFileOutputStream());
165
166        //Handle files.
167        try {
168            plugins.storeFiles(allContent, result, writer);
169        } catch (Exception ex) {
170            throw new IOException(ex);
171        }
172    }
173
174    private static ModulePoolImpl generateJImage(ModulePoolImpl allContent,
175            BasicImageWriter writer,
176            ImagePluginStack pluginSupport,
177            DataOutputStream out
178    ) throws IOException {
179        ModulePoolImpl resultResources;
180        try {
181            resultResources = pluginSupport.visitResources(allContent);
182        } catch (PluginException pe) {
183            throw pe;
184        } catch (Exception ex) {
185            throw new IOException(ex);
186        }
187        Set<String> duplicates = new HashSet<>();
188        long[] offset = new long[1];
189
190        List<ModuleEntry> content = new ArrayList<>();
191        List<String> paths = new ArrayList<>();
192                 // the order of traversing the resources and the order of
193        // the module content being written must be the same
194        resultResources.entries().forEach(res -> {
195            if (res.getType().equals(ModuleEntry.Type.CLASS_OR_RESOURCE)) {
196                String path = res.getPath();
197                content.add(res);
198                long uncompressedSize = res.getLength();
199                long compressedSize = 0;
200                if (res instanceof CompressedModuleData) {
201                    CompressedModuleData comp
202                            = (CompressedModuleData) res;
203                    compressedSize = res.getLength();
204                    uncompressedSize = comp.getUncompressedSize();
205                }
206                long onFileSize = res.getLength();
207
208                if (duplicates.contains(path)) {
209                    System.err.format("duplicate resource \"%s\", skipping%n",
210                            path);
211                    // TODO Need to hang bytes on resource and write
212                    // from resource not zip.
213                    // Skipping resource throws off writing from zip.
214                    offset[0] += onFileSize;
215                    return;
216                }
217                duplicates.add(path);
218                writer.addLocation(path, offset[0], compressedSize, uncompressedSize);
219                paths.add(path);
220                offset[0] += onFileSize;
221            }
222        });
223
224        ImageResourcesTree tree = new ImageResourcesTree(offset[0], writer, paths);
225
226        // write header and indices
227        byte[] bytes = writer.getBytes();
228        out.write(bytes, 0, bytes.length);
229
230        // write module content
231        content.stream().forEach((res) -> {
232            res.write(out);
233        });
234
235        tree.addContent(out);
236
237        out.close();
238
239        return resultResources;
240    }
241
242    private static ModulePoolImpl createPools(Set<Archive> archives,
243            Map<String, List<Entry>> entriesForModule,
244            ByteOrder byteOrder,
245            BasicImageWriter writer) throws IOException {
246        ModulePoolImpl resources = new ModulePoolImpl(byteOrder, new StringTable() {
247
248            @Override
249            public int addString(String str) {
250                return writer.addString(str);
251            }
252
253            @Override
254            public String getString(int id) {
255                return writer.getString(id);
256            }
257        });
258        for (Archive archive : archives) {
259            String mn = archive.moduleName();
260            for (Entry entry : entriesForModule.get(mn)) {
261                String path;
262                if (entry.type() == EntryType.CLASS_OR_RESOURCE) {
263                    // Removal of "classes/" radical.
264                    path = entry.name();
265                    if (path.endsWith("module-info.class")) {
266                        path = "/" + path;
267                    } else {
268                        path = "/" + mn + "/" + path;
269                    }
270                } else {
271                    // Entry.path() contains the kind of file native, conf, bin, ...
272                    // Keep it to avoid naming conflict (eg: native/jvm.cfg and config/jvm.cfg
273                    path = "/" + mn + "/" + entry.path();
274                }
275
276                resources.add(new ArchiveEntryModuleEntry(mn, path, entry));
277            }
278        }
279        return resources;
280    }
281
282    /**
283     * Helper method that splits a Resource path onto 3 items: module, parent
284     * and resource name.
285     *
286     * @param path
287     * @return An array containing module, parent and name.
288     */
289    public static String[] splitPath(String path) {
290        Objects.requireNonNull(path);
291        String noRoot = path.substring(1);
292        int pkgStart = noRoot.indexOf("/");
293        String module = noRoot.substring(0, pkgStart);
294        List<String> result = new ArrayList<>();
295        result.add(module);
296        String pkg = noRoot.substring(pkgStart + 1);
297        String resName;
298        int pkgEnd = pkg.lastIndexOf("/");
299        if (pkgEnd == -1) { // No package.
300            resName = pkg;
301        } else {
302            resName = pkg.substring(pkgEnd + 1);
303        }
304
305        pkg = toPackage(pkg, false);
306        result.add(pkg);
307        result.add(resName);
308
309        String[] array = new String[result.size()];
310        return result.toArray(array);
311    }
312
313    private static String toPackage(String name, boolean log) {
314        int index = name.lastIndexOf('/');
315        if (index > 0) {
316            return name.substring(0, index).replace('/', '.');
317        } else {
318            // ## unnamed package
319            if (log) {
320                System.err.format("Warning: %s in unnamed package%n", name);
321            }
322            return "";
323        }
324    }
325}
326