001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 *
017 */
018package org.apache.bcel.classfile;
019
020import java.io.BufferedInputStream;
021import java.io.DataInputStream;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.util.zip.ZipEntry;
026import java.util.zip.ZipFile;
027
028import org.apache.bcel.Const;
029
030/**
031 * Wrapper class that parses a given Java .class file. The method <A
032 * href ="#parse">parse</A> returns a <A href ="JavaClass.html">
033 * JavaClass</A> object on success. When an I/O error or an
034 * inconsistency occurs an appropiate exception is propagated back to
035 * the caller.
036 *
037 * The structure and the names comply, except for a few conveniences,
038 * exactly with the <A href="http://docs.oracle.com/javase/specs/">
039 * JVM specification 1.0</a>. See this paper for
040 * further details about the structure of a bytecode file.
041 *
042 */
043public final class ClassParser {
044
045    private DataInputStream dataInputStream;
046    private final boolean fileOwned;
047    private final String file_name;
048    private String zip_file;
049    private int class_name_index;
050    private int superclass_name_index;
051    private int major; // Compiler version
052    private int minor; // Compiler version
053    private int access_flags; // Access rights of parsed class
054    private int[] interfaces; // Names of implemented interfaces
055    private ConstantPool constant_pool; // collection of constants
056    private Field[] fields; // class fields, i.e., its variables
057    private Method[] methods; // methods defined in the class
058    private Attribute[] attributes; // attributes defined in the class
059    private final boolean is_zip; // Loaded from zip file
060    private static final int BUFSIZE = 8192;
061
062
063    /**
064     * Parses class from the given stream.
065     *
066     * @param inputStream Input stream
067     * @param file_name File name
068     */
069    public ClassParser(final InputStream inputStream, final String file_name) {
070        this.file_name = file_name;
071        fileOwned = false;
072        final String clazz = inputStream.getClass().getName(); // Not a very clean solution ...
073        is_zip = clazz.startsWith("java.util.zip.") || clazz.startsWith("java.util.jar.");
074        if (inputStream instanceof DataInputStream) {
075            this.dataInputStream = (DataInputStream) inputStream;
076        } else {
077            this.dataInputStream = new DataInputStream(new BufferedInputStream(inputStream, BUFSIZE));
078        }
079    }
080
081
082    /** Parses class from given .class file.
083     *
084     * @param file_name file name
085     */
086    public ClassParser(final String file_name) {
087        is_zip = false;
088        this.file_name = file_name;
089        fileOwned = true;
090    }
091
092
093    /** Parses class from given .class file in a ZIP-archive
094     *
095     * @param zip_file zip file name
096     * @param file_name file name
097     */
098    public ClassParser(final String zip_file, final String file_name) {
099        is_zip = true;
100        fileOwned = true;
101        this.zip_file = zip_file;
102        this.file_name = file_name;
103    }
104
105
106    /**
107     * Parses the given Java class file and return an object that represents
108     * the contained data, i.e., constants, methods, fields and commands.
109     * A <em>ClassFormatException</em> is raised, if the file is not a valid
110     * .class file. (This does not include verification of the byte code as it
111     * is performed by the java interpreter).
112     *
113     * @return Class object representing the parsed class file
114     * @throws  IOException
115     * @throws  ClassFormatException
116     */
117    public JavaClass parse() throws IOException, ClassFormatException {
118        ZipFile zip = null;
119        try {
120            if (fileOwned) {
121                if (is_zip) {
122                    zip = new ZipFile(zip_file);
123                    final ZipEntry entry = zip.getEntry(file_name);
124
125                    if (entry == null) {
126                        throw new IOException("File " + file_name + " not found");
127                    }
128
129                    dataInputStream = new DataInputStream(new BufferedInputStream(zip.getInputStream(entry),
130                            BUFSIZE));
131                } else {
132                    dataInputStream = new DataInputStream(new BufferedInputStream(new FileInputStream(
133                            file_name), BUFSIZE));
134                }
135            }
136            /****************** Read headers ********************************/
137            // Check magic tag of class file
138            readID();
139            // Get compiler version
140            readVersion();
141            /****************** Read constant pool and related **************/
142            // Read constant pool entries
143            readConstantPool();
144            // Get class information
145            readClassInfo();
146            // Get interface information, i.e., implemented interfaces
147            readInterfaces();
148            /****************** Read class fields and methods ***************/
149            // Read class fields, i.e., the variables of the class
150            readFields();
151            // Read class methods, i.e., the functions in the class
152            readMethods();
153            // Read class attributes
154            readAttributes();
155            // Check for unknown variables
156            //Unknown[] u = Unknown.getUnknownAttributes();
157            //for (int i=0; i < u.length; i++)
158            //  System.err.println("WARNING: " + u[i]);
159            // Everything should have been read now
160            //      if(file.available() > 0) {
161            //        int bytes = file.available();
162            //        byte[] buf = new byte[bytes];
163            //        file.read(buf);
164            //        if(!(is_zip && (buf.length == 1))) {
165            //      System.err.println("WARNING: Trailing garbage at end of " + file_name);
166            //      System.err.println(bytes + " extra bytes: " + Utility.toHexString(buf));
167            //        }
168            //      }
169        } finally {
170            // Read everything of interest, so close the file
171            if (fileOwned) {
172                try {
173                    if (dataInputStream != null) {
174                        dataInputStream.close();
175                    }
176                } catch (final IOException ioe) {
177                    //ignore close exceptions
178                }
179            }
180            try {
181                if (zip != null) {
182                    zip.close();
183                }
184            } catch (final IOException ioe) {
185                //ignore close exceptions
186            }
187        }
188        // Return the information we have gathered in a new object
189        return new JavaClass(class_name_index, superclass_name_index, file_name, major, minor,
190                access_flags, constant_pool, interfaces, fields, methods, attributes, is_zip
191                        ? JavaClass.ZIP
192                        : JavaClass.FILE);
193    }
194
195
196    /**
197     * Reads information about the attributes of the class.
198     * @throws  IOException
199     * @throws  ClassFormatException
200     */
201    private void readAttributes() throws IOException, ClassFormatException {
202        final int attributes_count = dataInputStream.readUnsignedShort();
203        attributes = new Attribute[attributes_count];
204        for (int i = 0; i < attributes_count; i++) {
205            attributes[i] = Attribute.readAttribute(dataInputStream, constant_pool);
206        }
207    }
208
209
210    /**
211     * Reads information about the class and its super class.
212     * @throws  IOException
213     * @throws  ClassFormatException
214     */
215    private void readClassInfo() throws IOException, ClassFormatException {
216        access_flags = dataInputStream.readUnsignedShort();
217        /* Interfaces are implicitely abstract, the flag should be set
218         * according to the JVM specification.
219         */
220        if ((access_flags & Const.ACC_INTERFACE) != 0) {
221            access_flags |= Const.ACC_ABSTRACT;
222        }
223        if (((access_flags & Const.ACC_ABSTRACT) != 0)
224                && ((access_flags & Const.ACC_FINAL) != 0)) {
225            throw new ClassFormatException("Class " + file_name + " can't be both final and abstract");
226        }
227        class_name_index = dataInputStream.readUnsignedShort();
228        superclass_name_index = dataInputStream.readUnsignedShort();
229    }
230
231
232    /**
233     * Reads constant pool entries.
234     * @throws  IOException
235     * @throws  ClassFormatException
236     */
237    private void readConstantPool() throws IOException, ClassFormatException {
238        constant_pool = new ConstantPool(dataInputStream);
239    }
240
241
242    /**
243     * Reads information about the fields of the class, i.e., its variables.
244     * @throws  IOException
245     * @throws  ClassFormatException
246     */
247    private void readFields() throws IOException, ClassFormatException {
248        final int fields_count = dataInputStream.readUnsignedShort();
249        fields = new Field[fields_count];
250        for (int i = 0; i < fields_count; i++) {
251            fields[i] = new Field(dataInputStream, constant_pool);
252        }
253    }
254
255
256    /******************** Private utility methods **********************/
257    /**
258     * Checks whether the header of the file is ok.
259     * Of course, this has to be the first action on successive file reads.
260     * @throws  IOException
261     * @throws  ClassFormatException
262     */
263    private void readID() throws IOException, ClassFormatException {
264        if (dataInputStream.readInt() != Const.JVM_CLASSFILE_MAGIC) {
265            throw new ClassFormatException(file_name + " is not a Java .class file");
266        }
267    }
268
269
270    /**
271     * Reads information about the interfaces implemented by this class.
272     * @throws  IOException
273     * @throws  ClassFormatException
274     */
275    private void readInterfaces() throws IOException, ClassFormatException {
276        final int interfaces_count = dataInputStream.readUnsignedShort();
277        interfaces = new int[interfaces_count];
278        for (int i = 0; i < interfaces_count; i++) {
279            interfaces[i] = dataInputStream.readUnsignedShort();
280        }
281    }
282
283
284    /**
285     * Reads information about the methods of the class.
286     * @throws  IOException
287     * @throws  ClassFormatException
288     */
289    private void readMethods() throws IOException, ClassFormatException {
290        final int methods_count = dataInputStream.readUnsignedShort();
291        methods = new Method[methods_count];
292        for (int i = 0; i < methods_count; i++) {
293            methods[i] = new Method(dataInputStream, constant_pool);
294        }
295    }
296
297
298    /**
299     * Reads major and minor version of compiler which created the file.
300     * @throws  IOException
301     * @throws  ClassFormatException
302     */
303    private void readVersion() throws IOException, ClassFormatException {
304        minor = dataInputStream.readUnsignedShort();
305        major = dataInputStream.readUnsignedShort();
306    }
307}