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}