    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.harmony.unpack200;
    020
    021import java.util.List;
    022
    023import org.apache.commons.compress.harmony.pack200.Pack200Exception;
    024import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
    025import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantPoolEntry;
    026
    027/**
    028 * Manages the constant pool used for re-creating class files.
    029 */
    030public class SegmentConstantPool {
    031
    032    /**
    033     * Value {@value}.
    034     */
    035    public static final int ALL = 0;
    036
    037    /**
    038     * Value {@value}.
    039     */
    040    public static final int UTF_8 = 1;
    041
    042    /**
    043     * Value {@value}.
    044     */
    045    public static final int CP_INT = 2;
    046
    047    // define in archive order
    048
    049    /**
    050     * Value {@value}.
    051     */
    052    public static final int CP_FLOAT = 3;
    053
    054    /**
    055     * Value {@value}.
    056     */
    057    public static final int CP_LONG = 4;
    058
    059    /**
    060     * Value {@value}.
    061     */
    062    public static final int CP_DOUBLE = 5;
    063
    064    /**
    065     * Value {@value}.
    066     */
    067    public static final int CP_STRING = 6;
    068
    069    /**
    070     * Value {@value}.
    071     */
    072    public static final int CP_CLASS = 7;
    073
    074    /**
    075     * Value {@value}.
    076     */
    077    public static final int SIGNATURE = 8; // TODO and more to come --
    078
    079    /**
    080     * Value {@value}.
    081     */
    082    public static final int CP_DESCR = 9;
    083
    084    /**
    085     * Value {@value}.
    086     */
    087    public static final int CP_FIELD = 10;
    088
    089    /**
    090     * Value {@value}.
    091     */
    092    public static final int CP_METHOD = 11;
    093
    094    /**
    095     * Value {@value}.
    096     */
    097    public static final int CP_IMETHOD = 12;
    098
    099    /**
    100     * Value {@value}.
    101     */
    102    protected static final String REGEX_MATCH_ALL = ".*";
    103
    104    /**
    105     * Value {@value}.
    106     */
    107    protected static final String INITSTRING = "<init>";
    108
    109    /**
    110     * Value {@value}.
    111     */
    112    protected static final String REGEX_MATCH_INIT = "^" + INITSTRING + ".*";
    113
    114    /**
    115     * We don't want a dependency on regex in Pack200. The only place one exists is in matchSpecificPoolEntryIndex(). To eliminate this dependency, we've
    116     * implemented the world's stupidest regexMatch. It knows about the two forms we care about: .* (aka REGEX_MATCH_ALL) {@code ^<init>;.*} (aka
    117     * REGEX_MATCH_INIT) and will answer correctly if those are passed as the regexString.
    118     *
    119     * @param regexString   String against which the compareString will be matched
    120     * @param compareString String to match against the regexString
    121     * @return boolean true if the compareString matches the regexString; otherwise false.
    122     */
    123    protected static boolean regexMatches(final String regexString, final String compareString) {
    124        if (REGEX_MATCH_ALL.equals(regexString)) {
    125            return true;
    126        }
    127        if (REGEX_MATCH_INIT.equals(regexString)) {
    128            if (compareString.length() < INITSTRING.length()) {
    129                return false;
    130            }
    131            return INITSTRING.equals(compareString.substring(0, INITSTRING.length()));
    132        }
    133        throw new Error("regex trying to match a pattern I don't know: " + regexString);
    134    }
    135
    136    static int toIndex(final long index) throws Pack200Exception {
    137        if (index < 0) {
    138            throw new Pack200Exception("Cannot have a negative index.");
    139        }
    140        return toIntExact(index);
    141    }
    142
    143    static int toIntExact(final long index) throws Pack200Exception {
    144        try {
    145            return Math.toIntExact(index);
    146        } catch (final ArithmeticException e) {
    147            throw new Pack200Exception("index", e);
    148        }
    149    }
    150
    151    private final CpBands bands;
    152
    153    private final SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache();
    154
    155    /**
    156     * Constructs a new instance.
    157     *
    158     * @param bands Constant pool bands.
    159     */
    160    public SegmentConstantPool(final CpBands bands) {
    161        this.bands = bands;
    162    }
    163
    164    /**
    165     * Gets the CPClass associated with a class name. Returns null if the class doesn't exist.
    166     *
    167     * @param name Class name to look for (form: java/lang/Object)
    168     * @return CPClass for that class name, or null if not found.
    169     * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
    170     */
    171    public ConstantPoolEntry getClassPoolEntry(final String name) throws Pack200Exception {
    172        final int index = matchSpecificPoolEntryIndex(bands.getCpClass(), name, 0);
    173        return index == -1 ? null : getConstantPoolEntry(CP_CLASS, index);
    174    }
    175
    176    /**
    177     * Gets the subset constant pool of the specified type to be just that which has the specified class name. Answer the ConstantPoolEntry at the desiredIndex
    178     * of the subset pool.
    179     *
    180     * @param cp               type of constant pool array to search.
    181     * @param desiredIndex     index of the constant pool.
    182     * @param desiredClassName class to use to generate a subset of the pool.
    183     * @return ConstantPoolEntry
    184     * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
    185     */
    186    public ConstantPoolEntry getClassSpecificPoolEntry(final int cp, final long desiredIndex, final String desiredClassName) throws Pack200Exception {
    187        final String[] array;
    188        switch (cp) {
    189        case CP_FIELD:
    190            array = bands.getCpFieldClass();
    191            break;
    192        case CP_METHOD:
    193            array = bands.getCpMethodClass();
    194            break;
    195        case CP_IMETHOD:
    196            array = bands.getCpIMethodClass();
    197            break;
    198        default:
    199            throw new Pack200Exception("Type is not supported yet: " + cp);
    200        }
    201        return getConstantPoolEntry(cp, matchSpecificPoolEntryIndex(array, desiredClassName, toIndex(desiredIndex)));
    202    }
    203
    204    /**
    205     * Gets the constant pool entry of the given type and index.
    206     *
    207     * @param type Constant pool type.
    208     * @param index Index into a specific constant pool.
    209     * @return a constant pool entry.
    210     * @throws Pack200Exception if a type is not supported or the index not in the range [0, {@link Integer#MAX_VALUE}].
    211     */
    212    public ConstantPoolEntry getConstantPoolEntry(final int type, final long index) throws Pack200Exception {
    213        if (index == -1) {
    214            return null;
    215        }
    216        final int actualIndex = toIndex(index);
    217        switch (type) {
    218        case UTF_8:
    219            return bands.cpUTF8Value(actualIndex);
    220        case CP_INT:
    221            return bands.cpIntegerValue(actualIndex);
    222        case CP_FLOAT:
    223            return bands.cpFloatValue(actualIndex);
    224        case CP_LONG:
    225            return bands.cpLongValue(actualIndex);
    226        case CP_DOUBLE:
    227            return bands.cpDoubleValue(actualIndex);
    228        case CP_STRING:
    229            return bands.cpStringValue(actualIndex);
    230        case CP_CLASS:
    231            return bands.cpClassValue(actualIndex);
    232        case SIGNATURE:
    233            throw new Pack200Exception("Type SIGNATURE is not supported yet: " + SIGNATURE);
    234        // return null /* new CPSignature(bands.getCpSignature()[index]) */;
    235        case CP_DESCR:
    236            throw new Pack200Exception("Type CP_DESCR is not supported yet: " + CP_DESCR);
    237        // return null /* new CPDescriptor(bands.getCpDescriptor()[index])
    238        // */;
    239        case CP_FIELD:
    240            return bands.cpFieldValue(actualIndex);
    241        case CP_METHOD:
    242            return bands.cpMethodValue(actualIndex);
    243        case CP_IMETHOD:
    244            return bands.cpIMethodValue(actualIndex);
    245        default:
    246            break;
    247        }
    248        // etc
    249        throw new Pack200Exception("Type is not supported yet: " + type);
    250    }
    251
    252    /**
    253     * Gets the {@code init} method for the specified class.
    254     *
    255     * @param cp               constant pool to search, must be {@link #CP_METHOD}.
    256     * @param value            index of {@code init} method.
    257     * @param desiredClassName String class name of the {@code init} method.
    258     * @return CPMethod {@code init} method.
    259     * @throws Pack200Exception if a type is not supported or an index not in the range [0, {@link Integer#MAX_VALUE}].
    260     */
    261    public ConstantPoolEntry getInitMethodPoolEntry(final int cp, final long value, final String desiredClassName) throws Pack200Exception {
    262        if (cp != CP_METHOD) {
    263            throw new Pack200Exception("Nothing but CP_METHOD can be an <init>");
    264        }
    265        final int realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), bands.getCpMethodDescriptor(), desiredClassName, REGEX_MATCH_INIT,
    266                toIndex(value));
    267        return getConstantPoolEntry(cp, realIndex);
    268    }
    269
    270    public ClassFileEntry getValue(final int cp, final long longIndex) throws Pack200Exception {
    271        final int index = (int) longIndex;
    272        if (index == -1) {
    273            return null;
    274        }
    275        if (index < 0) {
    276            throw new Pack200Exception("Cannot have a negative range");
    277        }
    278        switch (cp) {
    279        case UTF_8:
    280            return bands.cpUTF8Value(index);
    281        case CP_INT:
    282            return bands.cpIntegerValue(index);
    283        case CP_FLOAT:
    284            return bands.cpFloatValue(index);
    285        case CP_LONG:
    286            return bands.cpLongValue(index);
    287        case CP_DOUBLE:
    288            return bands.cpDoubleValue(index);
    289        case CP_STRING:
    290            return bands.cpStringValue(index);
    291        case CP_CLASS:
    292            return bands.cpClassValue(index);
    293        case SIGNATURE:
    294            return bands.cpSignatureValue(index);
    295        case CP_DESCR:
    296            return bands.cpNameAndTypeValue(index);
    297        default:
    298            break;
    299        }
    300        throw new Error("Tried to get a value I don't know about: " + cp);
    301    }
    302
    303    /**
    304     * A number of things make use of subsets of structures. In one particular example, _super bytecodes will use a subset of method or field classes which have
    305     * just those methods / fields defined in the superclass. Similarly, _this bytecodes use just those methods/fields defined in this class, and _init
    306     * bytecodes use just those methods that start with {@code <init>}.
    307     * <p>
    308     * This method takes an array of names, a String to match for, an index and a boolean as parameters, and answers the array position in the array of the
    309     * indexth element which matches (or equals) the String (depending on the state of the boolean)
    310     * </p>
    311     * <p>
    312     * In other words, if the class array consists of: Object [position 0, 0th instance of Object] String [position 1, 0th instance of String] String [position
    313     * 2, 1st instance of String] Object [position 3, 1st instance of Object] Object [position 4, 2nd instance of Object] then matchSpecificPoolEntryIndex(...,
    314     * "Object", 2, false) will answer 4. matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1.
    315     * </p>
    316     *
    317     * @param nameArray     Array of Strings against which the compareString is tested
    318     * @param compareString String for which to search
    319     * @param desiredIndex  nth element with that match (counting from 0)
    320     * @return int index into nameArray, or -1 if not found.
    321     */
    322    protected int matchSpecificPoolEntryIndex(final String[] nameArray, final String compareString, final int desiredIndex) {
    323        return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, REGEX_MATCH_ALL, desiredIndex);
    324    }
    325
    326    /**
    327     * This method's function is to look through pairs of arrays. It keeps track of the number of hits it finds using the following basis of comparison for a
    328     * hit: - the primaryArray[index] must be .equals() to the primaryCompareString - the secondaryArray[index] .matches() the secondaryCompareString. When the
    329     * desiredIndex number of hits has been reached, the index into the original two arrays of the element hit is returned.
    330     *
    331     * @param primaryArray          The first array to search
    332     * @param secondaryArray        The second array (must be same .length as primaryArray)
    333     * @param primaryCompareString  The String to compare against primaryArray using .equals()
    334     * @param secondaryCompareRegex The String to compare against secondaryArray using .matches()
    335     * @param desiredIndex          The nth hit whose position we're seeking
    336     * @return int index that represents the position of the nth hit in primaryArray and secondaryArray
    337     */
    338    protected int matchSpecificPoolEntryIndex(final String[] primaryArray, final String[] secondaryArray, final String primaryCompareString,
    339            final String secondaryCompareRegex, final int desiredIndex) {
    340        int instanceCount = -1;
    341        final List<Integer> indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString);
    342        if (indexList.isEmpty()) {
    343            // Primary key not found, no chance of finding secondary
    344            return -1;
    345        }
    346
    347        for (final Integer element : indexList) {
    348            final int arrayIndex = element.intValue();
    349            if (regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) {
    350                instanceCount++;
    351                if (instanceCount == desiredIndex) {
    352                    return arrayIndex;
    353                }
    354            }
    355        }
    356        // We didn't return in the for loop, so the desiredMatch
    357        // with desiredIndex must not exist in the arrays.
    358        return -1;
    359    }
    360}