1/*
2 * Copyright (c) 2016, 2017, 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.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24package jdk.tools.jaotc.binformat;
25
26import jdk.tools.jaotc.binformat.Symbol.Binding;
27import jdk.tools.jaotc.binformat.Symbol.Kind;
28import jdk.tools.jaotc.binformat.Container;
29
30import java.io.ByteArrayOutputStream;
31import java.nio.ByteBuffer;
32import java.nio.ByteOrder;
33import java.util.Arrays;
34
35/**
36 * Base class that represents content of all sections with byte-level granularity. The ByteContainer
37 * class is backed by a ByteArrayOutputStream. This class supports writing all desired byte content
38 * to the container using the method {@code appendBytes} and accessing the byte array using the
39 * method {@code getByteArray}.
40 *
41 * The method {@code putIntAt} updates the content of {@code contentBytes}. Changes are not
42 * reflected in {@code contentStream}.
43 */
44public class ByteContainer implements Container {
45    /**
46     * {@code ByteBuffer} representation of {@code BinaryContainer}.
47     */
48    private ByteBuffer contentBytes;
49
50    /**
51     * {@code ByteArrayoutputStream} to which all appends are done.
52     */
53    private ByteArrayOutputStream contentStream;
54
55    /**
56     * Boolean to indicate if contentBytes was modified.
57     */
58    private boolean bufferModified;
59
60    /**
61     * Boolean to indicate if this section contains any relocations.
62     */
63    private boolean hasRelocations;
64
65    /**
66     * Name of this container, used as section name.
67     */
68    private String containerName;
69    private final SymbolTable symbolTable;
70
71    /**
72     * Contains a unique id.
73     */
74    private int sectionId = -1;
75
76    /**
77     * Construct a {@code ByteContainer} object.
78     */
79    public ByteContainer(String containerName, SymbolTable symbolTable) {
80        this.containerName = containerName;
81        this.symbolTable = symbolTable;
82        this.contentBytes = null;
83        this.bufferModified = false;
84        this.hasRelocations = false;
85        this.contentStream = new ByteArrayOutputStream();
86    }
87
88    /**
89     * Update byte buffer to reflect the current contents of byte stream.
90     *
91     * @throws InternalError throws {@code InternalError} if buffer byte array was modified
92     */
93    private void updateByteBuffer() {
94        if (!bufferModified) {
95            contentBytes = ByteBuffer.wrap(contentStream.toByteArray());
96            // Default byte order of ByteBuffer is BIG_ENDIAN.
97            // Set it appropriately
98            this.contentBytes.order(ByteOrder.nativeOrder());
99        } else {
100            throw new InternalError("Backing byte buffer no longer in sync with byte stream");
101        }
102    }
103
104    /**
105     * Get the byte array of {@code ByteContainer}.
106     *
107     * @return byte array
108     * @throws InternalError throws {@code InternalError} if buffer byte array was modified
109     */
110    public byte[] getByteArray() {
111        if (!bufferModified) {
112            updateByteBuffer();
113        }
114        return contentBytes.array();
115    }
116
117    /**
118     * Append to byte stream. It is an error to append to stream if the byte buffer version is
119     * changed.
120     *
121     * @param newBytes new content
122     * @param off offset start offset in {@code newBytes}
123     * @param len length of data to write
124     * @throws InternalError throws {@code InternalError} if buffer byte array was modified
125     */
126    public ByteContainer appendBytes(byte[] newBytes, int off, int len) {
127        if (bufferModified) {
128            throw new InternalError("Backing byte buffer no longer in sync with byte stream");
129        }
130        contentStream.write(newBytes, off, len);
131        return this;
132    }
133
134    public ByteContainer appendBytes(byte[] newBytes) {
135        appendBytes(newBytes, 0, newBytes.length);
136        return this;
137    }
138
139    public ByteContainer appendInt(int i) {
140        if (bufferModified) {
141            throw new InternalError("Backing byte buffer no longer in sync with byte stream");
142        }
143        ByteBuffer b = ByteBuffer.allocate(Integer.BYTES);
144        b.order(ByteOrder.nativeOrder());
145        b.putInt(i);
146        byte[] result = b.array();
147        contentStream.write(result, 0, result.length);
148        return this;
149    }
150
151    public ByteContainer appendInts(int[] newInts) {
152        if (bufferModified) {
153            throw new InternalError("Backing byte buffer no longer in sync with byte stream");
154        }
155        ByteBuffer b = ByteBuffer.allocate(Integer.BYTES * newInts.length).order(ByteOrder.nativeOrder());
156        Arrays.stream(newInts).forEach(i -> b.putInt(i));
157        byte[] result = b.array();
158        contentStream.write(result, 0, result.length);
159        return this;
160    }
161
162    public void appendLong(long l) {
163        if (bufferModified) {
164            throw new InternalError("Backing byte buffer no longer in sync with byte stream");
165        }
166        ByteBuffer b = ByteBuffer.allocate(8);
167        b.order(ByteOrder.nativeOrder());
168        b.putLong(l);
169        byte[] result = b.array();
170        contentStream.write(result, 0, result.length);
171    }
172
173    /**
174     * Return the current size of byte stream backing the BinaryContainer.
175     *
176     * @return size of buffer stream
177     */
178    public int getByteStreamSize() {
179        return contentStream.size();
180    }
181
182    /**
183     * Return the name of this container.
184     *
185     * @return string containing name
186     */
187    public String getContainerName() {
188        return containerName;
189    }
190
191    /**
192     * Modify the byte buffer version of the byte output stream. Note that after calling this method
193     * all further updates to BinaryContainer will be out of sync with byte buffer content.
194     *
195     * @param index index of byte to be changed
196     * @param value new value
197     */
198    public void putIntAt(int index, int value) {
199        if (!bufferModified) {
200            updateByteBuffer();
201        }
202        contentBytes.putInt(index, value);
203        bufferModified = true;
204    }
205
206    public void putLongAt(int index, long value) {
207        if (!bufferModified) {
208            updateByteBuffer();
209        }
210        contentBytes.putLong(index, value);
211        bufferModified = true;
212    }
213
214    public void setSectionId(int id) {
215        if (sectionId != -1) {
216            throw new InternalError("Assigning new sectionId (old: " + sectionId + ", new: " + id + ")");
217        }
218        sectionId = id;
219    }
220
221    public int getSectionId() {
222        if (sectionId == -1) {
223            throw new InternalError("Using sectionId before assigned");
224        }
225        return sectionId;
226    }
227
228    public Symbol createSymbol(int offset, Kind kind, Binding binding, int size, String name) {
229        Symbol symbol = new Symbol(offset, kind, binding, this, size, name);
230        symbolTable.addSymbol(symbol);
231        return symbol;
232    }
233
234    public GotSymbol createGotSymbol(String name) {
235        GotSymbol symbol = new GotSymbol(Kind.OBJECT, Binding.LOCAL, this, name);
236        symbolTable.addSymbol(symbol);
237        return symbol;
238    }
239
240    public GotSymbol createGotSymbol(int offset, String name) {
241        GotSymbol symbol = new GotSymbol(offset, Kind.OBJECT, Binding.LOCAL, this, name);
242        symbolTable.addSymbol(symbol);
243        return symbol;
244    }
245
246    public void clear() {
247        this.contentBytes = null;
248        this.contentStream = null;
249    }
250
251    public void setHasRelocations() {
252        this.hasRelocations = true;
253    }
254
255    public boolean hasRelocations() {
256        return this.hasRelocations;
257    }
258}
259