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