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        if (cp != CP_METHOD) {
158            // TODO really an error?
159            throw new Error("Nothing but CP_METHOD can be an <init>");
160        }
161        realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), bands.getCpMethodDescriptor(),
162            desiredClassName, REGEX_MATCH_INIT, (int) value);
163        return getConstantPoolEntry(cp, realIndex);
164    }
165
166    /**
167     * A number of things make use of subsets of structures. In one particular example, _super bytecodes will use a
168     * subset of method or field classes which have just those methods / fields defined in the superclass. Similarly,
169     * _this bytecodes use just those methods/fields defined in this class, and _init bytecodes use just those methods
170     * that start with {@code <init>}.
171     *
172     * This method takes an array of names, a String to match for, an index and a boolean as parameters, and answers the
173     * array position in the array of the indexth element which matches (or equals) the String (depending on the state
174     * of the boolean)
175     *
176     * In other words, if the class array consists of: Object [position 0, 0th instance of Object] String [position 1,
177     * 0th instance of String] String [position 2, 1st instance of String] Object [position 3, 1st instance of Object]
178     * Object [position 4, 2nd instance of Object] then matchSpecificPoolEntryIndex(..., "Object", 2, false) will answer
179     * 4. matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1.
180     *
181     * @param nameArray Array of Strings against which the compareString is tested
182     * @param compareString String for which to search
183     * @param desiredIndex nth element with that match (counting from 0)
184     * @return int index into nameArray, or -1 if not found.
185     */
186    protected int matchSpecificPoolEntryIndex(final String[] nameArray, final String compareString,
187        final int desiredIndex) {
188        return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, REGEX_MATCH_ALL, desiredIndex);
189    }
190
191    /**
192     * This method's function is to look through pairs of arrays. It keeps track of the number of hits it finds using
193     * the following basis of comparison for a hit: - the primaryArray[index] must be .equals() to the
194     * primaryCompareString - the secondaryArray[index] .matches() the secondaryCompareString. When the desiredIndex
195     * number of hits has been reached, the index into the original two arrays of the element hit is returned.
196     *
197     * @param primaryArray The first array to search
198     * @param secondaryArray The second array (must be same .length as primaryArray)
199     * @param primaryCompareString The String to compare against primaryArray using .equals()
200     * @param secondaryCompareRegex The String to compare against secondaryArray using .matches()
201     * @param desiredIndex The nth hit whose position we're seeking
202     * @return int index that represents the position of the nth hit in primaryArray and secondaryArray
203     */
204    protected int matchSpecificPoolEntryIndex(final String[] primaryArray, final String[] secondaryArray,
205        final String primaryCompareString, final String secondaryCompareRegex, final int desiredIndex) {
206        int instanceCount = -1;
207        final List<Integer> indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString);
208        if (indexList.isEmpty()) {
209            // Primary key not found, no chance of finding secondary
210            return -1;
211        }
212
213        for (Integer element : indexList) {
214            final int arrayIndex = element.intValue();
215            if (regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) {
216                instanceCount++;
217                if (instanceCount == desiredIndex) {
218                    return arrayIndex;
219                }
220            }
221        }
222        // We didn't return in the for loop, so the desiredMatch
223        // with desiredIndex must not exist in the arrays.
224        return -1;
225    }
226
227    /**
228     * We don't want a dependency on regex in Pack200. The only place one exists is in matchSpecificPoolEntryIndex(). To
229     * eliminate this dependency, we've implemented the world's stupidest regexMatch. It knows about the two forms we
230     * care about: .* (aka REGEX_MATCH_ALL) {@code ^<init>;.*} (aka REGEX_MATCH_INIT) and will answer correctly if those
231     * are passed as the regexString.
232     *
233     * @param regexString String against which the compareString will be matched
234     * @param compareString String to match against the regexString
235     * @return boolean true if the compareString matches the regexString; otherwise false.
236     */
237    protected static boolean regexMatches(final String regexString, final String compareString) {
238        if (REGEX_MATCH_ALL.equals(regexString)) {
239            return true;
240        }
241        if (REGEX_MATCH_INIT.equals(regexString)) {
242            if (compareString.length() < (INITSTRING.length())) {
243                return false;
244            }
245            return (INITSTRING.equals(compareString.substring(0, INITSTRING.length())));
246        }
247        throw new Error("regex trying to match a pattern I don't know: " + regexString);
248    }
249
250    public ConstantPoolEntry getConstantPoolEntry(final int cp, final long value) throws Pack200Exception {
251        final int index = (int) value;
252        if (index == -1) {
253            return null;
254        }
255        if (index < 0) {
256            throw new Pack200Exception("Cannot have a negative range");
257        }
258        if (cp == UTF_8) {
259            return bands.cpUTF8Value(index);
260        }
261        if (cp == CP_INT) {
262            return bands.cpIntegerValue(index);
263        }
264        if (cp == CP_FLOAT) {
265            return bands.cpFloatValue(index);
266        }
267        if (cp == CP_LONG) {
268            return bands.cpLongValue(index);
269        }
270        if (cp == CP_DOUBLE) {
271            return bands.cpDoubleValue(index);
272        }
273        if (cp == CP_STRING) {
274            return bands.cpStringValue(index);
275        }
276        if (cp == CP_CLASS) {
277            return bands.cpClassValue(index);
278        }
279        if (cp == SIGNATURE) {
280            throw new Error("I don't know what to do with signatures yet");
281            // return null /* new CPSignature(bands.getCpSignature()[index]) */;
282        }
283        if (cp == CP_DESCR) {
284            throw new Error("I don't know what to do with descriptors yet");
285            // return null /* new CPDescriptor(bands.getCpDescriptor()[index])
286            // */;
287        }
288        if (cp == CP_FIELD) {
289            return bands.cpFieldValue(index);
290        }
291        if (cp == CP_METHOD) {
292            return bands.cpMethodValue(index);
293        }
294        if (cp == CP_IMETHOD) {
295            return bands.cpIMethodValue(index);
296        }
297        // etc
298        throw new Error("Get value incomplete");
299    }
300}