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