    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 */
    017package org.apache.commons.compress.harmony.unpack200;
    018
    019import java.util.List;
    020
    021import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    022import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
    023import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantPoolEntry;
    024
    025/**
    026 * SegmentConstantPool manages the constant pool used for re-creating class files.
    027 */
    028public class SegmentConstantPool {
    029
    030    private final CpBands bands;
    031    private final SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache();
    032
    033    /**
    034     * @param bands TODO
    035     */
    036    public SegmentConstantPool(final CpBands bands) {
    037        this.bands = bands;
    038    }
    039
    040    // define in archive order
    041
    042    public static final int ALL = 0;
    043    public static final int UTF_8 = 1;
    044    public static final int CP_INT = 2;
    045    public static final int CP_FLOAT = 3;
    046    public static final int CP_LONG = 4;
    047    public static final int CP_DOUBLE = 5;
    048    public static final int CP_STRING = 6;
    049    public static final int CP_CLASS = 7;
    050    public static final int SIGNATURE = 8; // TODO and more to come --
    051    public static final int CP_DESCR = 9;
    052    public static final int CP_FIELD = 10;
    053    public static final int CP_METHOD = 11;
    054    public static final int CP_IMETHOD = 12;
    055
    056    protected static final String REGEX_MATCH_ALL = ".*";
    057    protected static final String INITSTRING = "<init>";
    058    protected static final String REGEX_MATCH_INIT = "^" + INITSTRING + ".*";
    059
    060    public ClassFileEntry getValue(final int cp, final long value) throws Pack200Exception {
    061        final int index = (int) value;
    062        if (index == -1) {
    063            return null;
    064        }
    065        if (index < 0) {
    066            throw new Pack200Exception("Cannot have a negative range");
    067        }
    068        if (cp == UTF_8) {
    069            return bands.cpUTF8Value(index);
    070        }
    071        if (cp == CP_INT) {
    072            return bands.cpIntegerValue(index);
    073        }
    074        if (cp == CP_FLOAT) {
    075            return bands.cpFloatValue(index);
    076        }
    077        if (cp == CP_LONG) {
    078            return bands.cpLongValue(index);
    079        }
    080        if (cp == CP_DOUBLE) {
    081            return bands.cpDoubleValue(index);
    082        }
    083        if (cp == CP_STRING) {
    084            return bands.cpStringValue(index);
    085        }
    086        if (cp == CP_CLASS) {
    087            return bands.cpClassValue(index);
    088        }
    089        if (cp == SIGNATURE) {
    090            return bands.cpSignatureValue(index);
    091        }
    092        if (cp == CP_DESCR) {
    093            return bands.cpNameAndTypeValue(index);
    094        }
    095        throw new Error("Tried to get a value I don't know about: " + cp);
    096    }
    097
    098    /**
    099     * Subset the constant pool of the specified type to be just that which has the specified class name. Answer the
    100     * ConstantPoolEntry at the desiredIndex of the subsetted pool.
    101     *
    102     * @param cp type of constant pool array to search
    103     * @param desiredIndex index of the constant pool
    104     * @param desiredClassName class to use to generate a subset of the pool
    105     * @return ConstantPoolEntry
    106     * @throws Pack200Exception TODO
    107     */
    108    public ConstantPoolEntry getClassSpecificPoolEntry(final int cp, final long desiredIndex,
    109        final String desiredClassName) throws Pack200Exception {
    110        final int index = (int) desiredIndex;
    111        int realIndex = -1;
    112        String array[] = null;
    113        if (cp == CP_FIELD) {
    114            array = bands.getCpFieldClass();
    115        } else if (cp == CP_METHOD) {
    116            array = bands.getCpMethodClass();
    117        } else if (cp == CP_IMETHOD) {
    118            array = bands.getCpIMethodClass();
    119        } else {
    120            throw new Error("Don't know how to handle " + cp);
    121        }
    122        realIndex = matchSpecificPoolEntryIndex(array, desiredClassName, index);
    123        return getConstantPoolEntry(cp, realIndex);
    124    }
    125
    126    /**
    127     * Given the name of a class, answer the CPClass associated with that class. Answer null if the class doesn't exist.
    128     *
    129     * @param name Class name to look for (form: java/lang/Object)
    130     * @return CPClass for that class name, or null if not found.
    131     */
    132    public ConstantPoolEntry getClassPoolEntry(final String name) {
    133        final String classes[] = bands.getCpClass();
    134        final int index = matchSpecificPoolEntryIndex(classes, name, 0);
    135        if (index == -1) {
    136            return null;
    137        }
    138        try {
    139            return getConstantPoolEntry(CP_CLASS, index);
    140        } catch (final Pack200Exception ex) {
    141            throw new Error("Error getting class pool entry");
    142        }
    143    }
    144
    145    /**
    146     * Answer the init method for the specified class.
    147     *
    148     * @param cp constant pool to search (must be CP_METHOD)
    149     * @param value index of init method
    150     * @param desiredClassName String class name of the init method
    151     * @return CPMethod init method
    152     * @throws Pack200Exception TODO
    153     */
    154    public ConstantPoolEntry getInitMethodPoolEntry(final int cp, final long value, final String desiredClassName)
    155        throws Pack200Exception {
    156        int realIndex = -1;
    157        final String desiredRegex = REGEX_MATCH_INIT;
    158        if (cp != CP_METHOD) {
    159            // TODO really an error?
    160            throw new Error("Nothing but CP_METHOD can be an <init>");
    161        }
    162        realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), bands.getCpMethodDescriptor(),
    163            desiredClassName, desiredRegex, (int) value);
    164        return getConstantPoolEntry(cp, realIndex);
    165    }
    166
    167    /**
    168     * A number of things make use of subsets of structures. In one particular example, _super bytecodes will use a
    169     * subset of method or field classes which have just those methods / fields defined in the superclass. Similarly,
    170     * _this bytecodes use just those methods/fields defined in this class, and _init bytecodes use just those methods
    171     * that start with {@code <init>}.
    172     *
    173     * This method takes an array of names, a String to match for, an index and a boolean as parameters, and answers the
    174     * array position in the array of the indexth element which matches (or equals) the String (depending on the state
    175     * of the boolean)
    176     *
    177     * In other words, if the class array consists of: Object [position 0, 0th instance of Object] String [position 1,
    178     * 0th instance of String] String [position 2, 1st instance of String] Object [position 3, 1st instance of Object]
    179     * Object [position 4, 2nd instance of Object] then matchSpecificPoolEntryIndex(..., "Object", 2, false) will answer
    180     * 4. matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1.
    181     *
    182     * @param nameArray Array of Strings against which the compareString is tested
    183     * @param compareString String for which to search
    184     * @param desiredIndex nth element with that match (counting from 0)
    185     * @return int index into nameArray, or -1 if not found.
    186     */
    187    protected int matchSpecificPoolEntryIndex(final String[] nameArray, final String compareString,
    188        final int desiredIndex) {
    189        return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, REGEX_MATCH_ALL, desiredIndex);
    190    }
    191
    192    /**
    193     * This method's function is to look through pairs of arrays. It keeps track of the number of hits it finds using
    194     * the following basis of comparison for a hit: - the primaryArray[index] must be .equals() to the
    195     * primaryCompareString - the secondaryArray[index] .matches() the secondaryCompareString. When the desiredIndex
    196     * number of hits has been reached, the index into the original two arrays of the element hit is returned.
    197     *
    198     * @param primaryArray The first array to search
    199     * @param secondaryArray The second array (must be same .length as primaryArray)
    200     * @param primaryCompareString The String to compare against primaryArray using .equals()
    201     * @param secondaryCompareRegex The String to compare against secondaryArray using .matches()
    202     * @param desiredIndex The nth hit whose position we're seeking
    203     * @return int index that represents the position of the nth hit in primaryArray and secondaryArray
    204     */
    205    protected int matchSpecificPoolEntryIndex(final String[] primaryArray, final String[] secondaryArray,
    206        final String primaryCompareString, final String secondaryCompareRegex, final int desiredIndex) {
    207        int instanceCount = -1;
    208        final List indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString);
    209        if (indexList.isEmpty()) {
    210            // Primary key not found, no chance of finding secondary
    211            return -1;
    212        }
    213
    214        for (int index = 0; index < indexList.size(); index++) {
    215            final int arrayIndex = ((Integer) indexList.get(index)).intValue();
    216            if (regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) {
    217                instanceCount++;
    218                if (instanceCount == desiredIndex) {
    219                    return arrayIndex;
    220                }
    221            }
    222        }
    223        // We didn't return in the for loop, so the desiredMatch
    224        // with desiredIndex must not exist in the arrays.
    225        return -1;
    226    }
    227
    228    /**
    229     * We don't want a dependency on regex in Pack200. The only place one exists is in matchSpecificPoolEntryIndex(). To
    230     * eliminate this dependency, we've implemented the world's stupidest regexMatch. It knows about the two forms we
    231     * care about: .* (aka REGEX_MATCH_ALL) {@code ^<init>;.*} (aka REGEX_MATCH_INIT) and will answer correctly if those
    232     * are passed as the regexString.
    233     *
    234     * @param regexString String against which the compareString will be matched
    235     * @param compareString String to match against the regexString
    236     * @return boolean true if the compareString matches the regexString; otherwise false.
    237     */
    238    protected static boolean regexMatches(final String regexString, final String compareString) {
    239        if (REGEX_MATCH_ALL.equals(regexString)) {
    240            return true;
    241        }
    242        if (REGEX_MATCH_INIT.equals(regexString)) {
    243            if (compareString.length() < (INITSTRING.length())) {
    244                return false;
    245            }
    246            return (INITSTRING.equals(compareString.substring(0, INITSTRING.length())));
    247        }
    248        throw new Error("regex trying to match a pattern I don't know: " + regexString);
    249    }
    250
    251    public ConstantPoolEntry getConstantPoolEntry(final int cp, final long value) throws Pack200Exception {
    252        final int index = (int) value;
    253        if (index == -1) {
    254            return null;
    255        }
    256        if (index < 0) {
    257            throw new Pack200Exception("Cannot have a negative range");
    258        }
    259        if (cp == UTF_8) {
    260            return bands.cpUTF8Value(index);
    261        }
    262        if (cp == CP_INT) {
    263            return bands.cpIntegerValue(index);
    264        }
    265        if (cp == CP_FLOAT) {
    266            return bands.cpFloatValue(index);
    267        }
    268        if (cp == CP_LONG) {
    269            return bands.cpLongValue(index);
    270        }
    271        if (cp == CP_DOUBLE) {
    272            return bands.cpDoubleValue(index);
    273        }
    274        if (cp == CP_STRING) {
    275            return bands.cpStringValue(index);
    276        }
    277        if (cp == CP_CLASS) {
    278            return bands.cpClassValue(index);
    279        }
    280        if (cp == SIGNATURE) {
    281            throw new Error("I don't know what to do with signatures yet");
    282            // return null /* new CPSignature(bands.getCpSignature()[index]) */;
    283        }
    284        if (cp == CP_DESCR) {
    285            throw new Error("I don't know what to do with descriptors yet");
    286            // return null /* new CPDescriptor(bands.getCpDescriptor()[index])
    287            // */;
    288        }
    289        if (cp == CP_FIELD) {
    290            return bands.cpFieldValue(index);
    291        }
    292        if (cp == CP_METHOD) {
    293            return bands.cpMethodValue(index);
    294        }
    295        if (cp == CP_IMETHOD) {
    296            return bands.cpIMethodValue(index);
    297        }
    298        // etc
    299        throw new Error("Get value incomplete");
    300    }
    301}