1/**
2 * Copyright 2007 Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.tonicsystems.jarjar.util;
18
19import java.io.*;
20import java.lang.reflect.Array;
21import java.util.*;
22
23public class ClassHeaderReader
24{
25    private int access;
26    private String thisClass;
27    private String superClass;
28    private String[] interfaces;
29
30    private InputStream in;
31    private byte[] b = new byte[0x2000];
32    private int[] items = new int[1000];
33    private int bsize = 0;
34    private MyByteArrayInputStream bin = new MyByteArrayInputStream();
35    private DataInputStream data = new DataInputStream(bin);
36
37    public int getAccess() {
38        return access;
39    }
40
41    public String getClassName() {
42        return thisClass;
43    }
44
45    public String getSuperName() {
46        return superClass;
47    }
48
49    public String[] getInterfaces() {
50        return interfaces;
51    }
52
53    public void read(InputStream in) throws IOException {
54        try {
55            this.in = in;
56            bsize = 0;
57            access = 0;
58            thisClass = superClass = null;
59            interfaces = null;
60
61            try {
62                buffer(4);
63            } catch (IOException e) {
64                // ignore
65            }
66            if (b[0] != (byte)0xCA || b[1] != (byte)0xFE || b[2] != (byte)0xBA || b[3] != (byte)0xBE)
67                throw new ClassFormatError("Bad magic number");
68
69            buffer(6);
70            readUnsignedShort(4); // minorVersion
71            readUnsignedShort(6); // majorVersion
72            // TODO: check version
73            int constant_pool_count = readUnsignedShort(8);
74            items = (int[])resizeArray(items, constant_pool_count);
75
76            int index = 10;
77            for (int i = 1; i < constant_pool_count; i++) {
78                int size;
79                buffer(index + 3); // TODO: reduce calls to buffer
80                int tag = b[index];
81                items[i] = index + 1;
82                switch (tag) {
83                case 9:  // Fieldref
84                case 10: // Methodref
85                case 11: // InterfaceMethodref
86                case 3:  // Integer
87                case 4:  // Float
88                case 12: // NameAndType
89                    size = 4;
90                    break;
91                case 5:  // Long
92                case 6:  // Double
93                    size = 8;
94                    i++;
95                    break;
96                case 1:  // Utf8
97                    size = 2 + readUnsignedShort(index + 1);
98                    break;
99                case 7:  // Class
100                case 8:  // String
101                    size = 2;
102                    break;
103                default:
104                    throw new IllegalStateException("Unknown constant pool tag " + tag);
105                }
106                index += size + 1;
107            }
108            buffer(index + 8);
109            access = readUnsignedShort(index);
110            thisClass = readClass(index + 2);
111            superClass = readClass(index + 4);
112            int interfaces_count = readUnsignedShort(index + 6);
113
114            index += 8;
115            buffer(index + interfaces_count * 2);
116            interfaces = new String[interfaces_count];
117            for (int i = 0; i < interfaces_count; i++) {
118                interfaces[i] = readClass(index);
119                index += 2;
120            }
121        } finally {
122            in.close();
123        }
124    }
125
126    private String readClass(int index) throws IOException {
127        index = readUnsignedShort(index);
128        if (index == 0)
129            return null;
130        index = readUnsignedShort(items[index]);
131        bin.readFrom(b, items[index]);
132        return data.readUTF();
133    }
134
135    private int readUnsignedShort(int index) {
136        byte[] b = this.b;
137        return ((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF);
138    }
139
140    private static final int CHUNK = 2048;
141    private void buffer(int amount) throws IOException {
142        if (amount > b.length)
143            b = (byte[])resizeArray(b, b.length * 2);
144        if (amount > bsize) {
145            int rounded = (int)(CHUNK * Math.ceil((float)amount / CHUNK));
146            bsize += read(in, b, bsize, rounded - bsize);
147            if (amount > bsize)
148                throw new EOFException();
149        }
150    }
151
152    private static int read(InputStream in, byte[] b, int off, int len) throws IOException {
153        int total = 0;
154        while (total < len) {
155            int result = in.read(b, off + total, len - total);
156            if (result == -1)
157                break;
158            total += result;
159        }
160        return total;
161    }
162
163    private static Object resizeArray(Object array, int length)
164    {
165        if (Array.getLength(array) < length) {
166            Object newArray = Array.newInstance(array.getClass().getComponentType(), length);
167            System.arraycopy(array, 0, newArray, 0, Array.getLength(array));
168            return newArray;
169        } else {
170            return array;
171        }
172    }
173
174    private static class MyByteArrayInputStream extends ByteArrayInputStream
175    {
176        public MyByteArrayInputStream() {
177            super(new byte[0]);
178        }
179
180        public void readFrom(byte[] buf, int pos) {
181            this.buf = buf;
182            this.pos = pos;
183            count = buf.length;
184        }
185    }
186}
187