1 /*
  2  * Copyright (c) 2014, 2020, 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 
 26 package jdk.tools.jlink.internal;
 27 
 28 import java.nio.ByteOrder;
 29 import java.util.ArrayList;
 30 import java.util.List;
 31 import java.util.Objects;
 32 import jdk.internal.jimage.ImageHeader;
 33 import jdk.internal.jimage.ImageStream;
 34 import jdk.internal.jimage.ImageStringsReader;
 35 
 36 public final class BasicImageWriter {
 37     public static final String MODULES_IMAGE_NAME = "modules";
 38 
 39     private ByteOrder byteOrder;
 40     private ImageStringsWriter strings;
 41     private int length;
 42     private int[] redirect;
 43     private ImageLocationWriter[] locations;
 44     private List<ImageLocationWriter> input;
 45     private ImageStream headerStream;
 46     private ImageStream redirectStream;
 47     private ImageStream locationOffsetStream;
 48     private ImageStream locationStream;
 49     private ImageStream allIndexStream;
 50 
 51     public BasicImageWriter() {
 52         this(ByteOrder.nativeOrder());
 53     }
 54 
 55     public BasicImageWriter(ByteOrder byteOrder) {
 56         this.byteOrder = Objects.requireNonNull(byteOrder);
 57         this.input = new ArrayList<>();
 58         this.strings = new ImageStringsWriter();
 59         this.headerStream = new ImageStream(byteOrder);
 60         this.redirectStream = new ImageStream(byteOrder);
 61         this.locationOffsetStream = new ImageStream(byteOrder);
 62         this.locationStream = new ImageStream(byteOrder);
 63         this.allIndexStream = new ImageStream(byteOrder);
 64     }
 65 
 66     public ByteOrder getByteOrder() {
 67         return byteOrder;
 68     }
 69 
 70     public int addString(String string) {
 71         return strings.add(string);
 72     }
 73 
 74     public String getString(int offset) {
 75         return strings.get(offset);
 76     }
 77 
 78     public void addLocation(String fullname, long contentOffset,
 79             long compressedSize, long uncompressedSize) {
 80         ImageLocationWriter location =
 81                 ImageLocationWriter.newLocation(fullname, strings,
 82                         contentOffset, compressedSize, uncompressedSize);
 83         input.add(location);
 84         length++;
 85     }
 86 
 87     ImageLocationWriter[] getLocations() {
 88         return locations;
 89     }
 90 
 91     int getLocationsCount() {
 92         return input.size();
 93     }
 94 
 95     private void generatePerfectHash() {
 96         PerfectHashBuilder<ImageLocationWriter> builder =
 97             new PerfectHashBuilder<>(
 98                         PerfectHashBuilder.Entry.class,
 99                         PerfectHashBuilder.Bucket.class);
100 
101         input.forEach((location) -> {
102             builder.put(location.getFullName(), location);
103         });
104 
105         builder.generate();
106 
107         length = builder.getCount();
108         redirect = builder.getRedirect();
109         PerfectHashBuilder.Entry<ImageLocationWriter>[] order = builder.getOrder();
110         locations = new ImageLocationWriter[length];
111 
112         for (int i = 0; i < length; i++) {
113             locations[i] = order[i].getValue();
114         }
115     }
116 
117     private void prepareStringBytes() {
118         strings.getStream().align(2);
119     }
120 
121     private void prepareRedirectBytes() {
122         for (int i = 0; i < length; i++) {
123             redirectStream.putInt(redirect[i]);
124         }
125     }
126 
127     private void prepareLocationBytes() {
128         // Reserve location offset zero for empty locations
129         locationStream.put(ImageLocationWriter.ATTRIBUTE_END << 3);
130 
131         for (int i = 0; i < length; i++) {
132             ImageLocationWriter location = locations[i];
133 
134             if (location != null) {
135                 location.writeTo(locationStream);
136             }
137         }
138 
139         locationStream.align(2);
140     }
141 
142     private void prepareOffsetBytes() {
143         for (int i = 0; i < length; i++) {
144             ImageLocationWriter location = locations[i];
145             int offset = location != null ? location.getLocationOffset() : 0;
146             locationOffsetStream.putInt(offset);
147         }
148     }
149 
150     private void prepareHeaderBytes() {
151         ImageHeader header = new ImageHeader(input.size(), length,
152                 locationStream.getSize(), strings.getSize());
153         header.writeTo(headerStream);
154     }
155 
156     private void prepareTableBytes() {
157         allIndexStream.put(headerStream);
158         allIndexStream.put(redirectStream);
159         allIndexStream.put(locationOffsetStream);
160         allIndexStream.put(locationStream);
161         allIndexStream.put(strings.getStream());
162     }
163 
164     public byte[] getBytes() {
165         if (allIndexStream.getSize() == 0) {
166             generatePerfectHash();
167             prepareStringBytes();
168             prepareRedirectBytes();
169             prepareLocationBytes();
170             prepareOffsetBytes();
171             prepareHeaderBytes();
172             prepareTableBytes();
173         }
174 
175         return allIndexStream.toArray();
176     }
177 
178     ImageLocationWriter find(String key) {
179         int index = redirect[ImageStringsReader.hashCode(key) % length];
180 
181         if (index < 0) {
182             index = -index - 1;
183         } else {
184             index = ImageStringsReader.hashCode(key, index) % length;
185         }
186 
187         return locations[index];
188     }
189 }