View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one or more
3    *  contributor license agreements.  See the NOTICE file distributed with
4    *  this work for additional information regarding copyright ownership.
5    *  The ASF licenses this file to You under the Apache License, Version 2.0
6    *  (the "License"); you may not use this file except in compliance with
7    *  the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   *  Unless required by applicable law or agreed to in writing, software
12   *  distributed under the License is distributed on an "AS IS" BASIS,
13   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   *  See the License for the specific language governing permissions and
15   *  limitations under the License.
16   */
17  package org.apache.commons.compress.harmony.unpack200;
18  
19  import java.util.List;
20  
21  import org.apache.commons.compress.harmony.pack200.Pack200Exception;
22  import org.apache.commons.compress.harmony.unpack200.bytecode.ClassFileEntry;
23  import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantPoolEntry;
24  
25  /**
26   * SegmentConstantPool manages the constant pool used for re-creating class files.
27   */
28  public class SegmentConstantPool {
29  
30      public static final int ALL = 0;
31      public static final int UTF_8 = 1;
32  
33      public static final int CP_INT = 2;
34  
35      // define in archive order
36  
37      public static final int CP_FLOAT = 3;
38      public static final int CP_LONG = 4;
39      public static final int CP_DOUBLE = 5;
40      public static final int CP_STRING = 6;
41      public static final int CP_CLASS = 7;
42      public static final int SIGNATURE = 8; // TODO and more to come --
43      public static final int CP_DESCR = 9;
44      public static final int CP_FIELD = 10;
45      public static final int CP_METHOD = 11;
46      public static final int CP_IMETHOD = 12;
47      protected static final String REGEX_MATCH_ALL = ".*";
48      protected static final String INITSTRING = "<init>";
49      protected static final String REGEX_MATCH_INIT = "^" + INITSTRING + ".*";
50  
51      /**
52       * We don't want a dependency on regex in Pack200. The only place one exists is in matchSpecificPoolEntryIndex(). To eliminate this dependency, we've
53       * implemented the world's stupidest regexMatch. It knows about the two forms we care about: .* (aka REGEX_MATCH_ALL) {@code ^<init>;.*} (aka
54       * REGEX_MATCH_INIT) and will answer correctly if those are passed as the regexString.
55       *
56       * @param regexString   String against which the compareString will be matched
57       * @param compareString String to match against the regexString
58       * @return boolean true if the compareString matches the regexString; otherwise false.
59       */
60      protected static boolean regexMatches(final String regexString, final String compareString) {
61          if (REGEX_MATCH_ALL.equals(regexString)) {
62              return true;
63          }
64          if (REGEX_MATCH_INIT.equals(regexString)) {
65              if (compareString.length() < INITSTRING.length()) {
66                  return false;
67              }
68              return INITSTRING.equals(compareString.substring(0, INITSTRING.length()));
69          }
70          throw new Error("regex trying to match a pattern I don't know: " + regexString);
71      }
72  
73      private final CpBands bands;
74      private final SegmentConstantPoolArrayCache arrayCache = new SegmentConstantPoolArrayCache();
75  
76      /**
77       * @param bands TODO
78       */
79      public SegmentConstantPool(final CpBands bands) {
80          this.bands = bands;
81      }
82  
83      /**
84       * Given the name of a class, answer the CPClass associated with that class. Answer null if the class doesn't exist.
85       *
86       * @param name Class name to look for (form: java/lang/Object)
87       * @return CPClass for that class name, or null if not found.
88       */
89      public ConstantPoolEntry getClassPoolEntry(final String name) {
90          final String[] classes = bands.getCpClass();
91          final int index = matchSpecificPoolEntryIndex(classes, name, 0);
92          if (index == -1) {
93              return null;
94          }
95          try {
96              return getConstantPoolEntry(CP_CLASS, index);
97          } catch (final Pack200Exception ex) {
98              throw new Error("Error getting class pool entry");
99          }
100     }
101 
102     /**
103      * Subset the constant pool of the specified type to be just that which has the specified class name. Answer the ConstantPoolEntry at the desiredIndex of
104      * the subsetted pool.
105      *
106      * @param cp               type of constant pool array to search
107      * @param desiredIndex     index of the constant pool
108      * @param desiredClassName class to use to generate a subset of the pool
109      * @return ConstantPoolEntry
110      * @throws Pack200Exception TODO
111      */
112     public ConstantPoolEntry getClassSpecificPoolEntry(final int cp, final long desiredIndex, final String desiredClassName) throws Pack200Exception {
113         final int index = (int) desiredIndex;
114         int realIndex = -1;
115         String[] array;
116         switch (cp) {
117         case CP_FIELD:
118             array = bands.getCpFieldClass();
119             break;
120         case CP_METHOD:
121             array = bands.getCpMethodClass();
122             break;
123         case CP_IMETHOD:
124             array = bands.getCpIMethodClass();
125             break;
126         default:
127             throw new Error("Don't know how to handle " + cp);
128         }
129         realIndex = matchSpecificPoolEntryIndex(array, desiredClassName, index);
130         return getConstantPoolEntry(cp, realIndex);
131     }
132 
133     public ConstantPoolEntry getConstantPoolEntry(final int cp, final long value) throws Pack200Exception {
134         final int index = (int) value;
135         if (index == -1) {
136             return null;
137         }
138         if (index < 0) {
139             throw new Pack200Exception("Cannot have a negative range");
140         }
141         switch (cp) {
142         case UTF_8:
143             return bands.cpUTF8Value(index);
144         case CP_INT:
145             return bands.cpIntegerValue(index);
146         case CP_FLOAT:
147             return bands.cpFloatValue(index);
148         case CP_LONG:
149             return bands.cpLongValue(index);
150         case CP_DOUBLE:
151             return bands.cpDoubleValue(index);
152         case CP_STRING:
153             return bands.cpStringValue(index);
154         case CP_CLASS:
155             return bands.cpClassValue(index);
156         case SIGNATURE:
157             throw new Error("I don't know what to do with signatures yet");
158         // return null /* new CPSignature(bands.getCpSignature()[index]) */;
159         case CP_DESCR:
160             throw new Error("I don't know what to do with descriptors yet");
161         // return null /* new CPDescriptor(bands.getCpDescriptor()[index])
162         // */;
163         case CP_FIELD:
164             return bands.cpFieldValue(index);
165         case CP_METHOD:
166             return bands.cpMethodValue(index);
167         case CP_IMETHOD:
168             return bands.cpIMethodValue(index);
169         default:
170             break;
171         }
172         // etc
173         throw new Error("Get value incomplete");
174     }
175 
176     /**
177      * Answer the init method for the specified class.
178      *
179      * @param cp               constant pool to search (must be CP_METHOD)
180      * @param value            index of init method
181      * @param desiredClassName String class name of the init method
182      * @return CPMethod init method
183      * @throws Pack200Exception TODO
184      */
185     public ConstantPoolEntry getInitMethodPoolEntry(final int cp, final long value, final String desiredClassName) throws Pack200Exception {
186         int realIndex = -1;
187         if (cp != CP_METHOD) {
188             // TODO really an error?
189             throw new Error("Nothing but CP_METHOD can be an <init>");
190         }
191         realIndex = matchSpecificPoolEntryIndex(bands.getCpMethodClass(), bands.getCpMethodDescriptor(), desiredClassName, REGEX_MATCH_INIT, (int) value);
192         return getConstantPoolEntry(cp, realIndex);
193     }
194 
195     public ClassFileEntry getValue(final int cp, final long value) throws Pack200Exception {
196         final int index = (int) value;
197         if (index == -1) {
198             return null;
199         }
200         if (index < 0) {
201             throw new Pack200Exception("Cannot have a negative range");
202         }
203         switch (cp) {
204         case UTF_8:
205             return bands.cpUTF8Value(index);
206         case CP_INT:
207             return bands.cpIntegerValue(index);
208         case CP_FLOAT:
209             return bands.cpFloatValue(index);
210         case CP_LONG:
211             return bands.cpLongValue(index);
212         case CP_DOUBLE:
213             return bands.cpDoubleValue(index);
214         case CP_STRING:
215             return bands.cpStringValue(index);
216         case CP_CLASS:
217             return bands.cpClassValue(index);
218         case SIGNATURE:
219             return bands.cpSignatureValue(index);
220         case CP_DESCR:
221             return bands.cpNameAndTypeValue(index);
222         default:
223             break;
224         }
225         throw new Error("Tried to get a value I don't know about: " + cp);
226     }
227 
228     /**
229      * 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
230      * just those methods / fields defined in the superclass. Similarly, _this bytecodes use just those methods/fields defined in this class, and _init
231      * bytecodes use just those methods that start with {@code <init>}.
232      *
233      * 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
234      * indexth element which matches (or equals) the String (depending on the state of the boolean)
235      *
236      * 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
237      * 2, 1st instance of String] Object [position 3, 1st instance of Object] Object [position 4, 2nd instance of Object] then matchSpecificPoolEntryIndex(...,
238      * "Object", 2, false) will answer 4. matchSpecificPoolEntryIndex(..., "String", 0, false) will answer 1.
239      *
240      * @param nameArray     Array of Strings against which the compareString is tested
241      * @param compareString String for which to search
242      * @param desiredIndex  nth element with that match (counting from 0)
243      * @return int index into nameArray, or -1 if not found.
244      */
245     protected int matchSpecificPoolEntryIndex(final String[] nameArray, final String compareString, final int desiredIndex) {
246         return matchSpecificPoolEntryIndex(nameArray, nameArray, compareString, REGEX_MATCH_ALL, desiredIndex);
247     }
248 
249     /**
250      * 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
251      * hit: - the primaryArray[index] must be .equals() to the primaryCompareString - the secondaryArray[index] .matches() the secondaryCompareString. When the
252      * desiredIndex number of hits has been reached, the index into the original two arrays of the element hit is returned.
253      *
254      * @param primaryArray          The first array to search
255      * @param secondaryArray        The second array (must be same .length as primaryArray)
256      * @param primaryCompareString  The String to compare against primaryArray using .equals()
257      * @param secondaryCompareRegex The String to compare against secondaryArray using .matches()
258      * @param desiredIndex          The nth hit whose position we're seeking
259      * @return int index that represents the position of the nth hit in primaryArray and secondaryArray
260      */
261     protected int matchSpecificPoolEntryIndex(final String[] primaryArray, final String[] secondaryArray, final String primaryCompareString,
262             final String secondaryCompareRegex, final int desiredIndex) {
263         int instanceCount = -1;
264         final List<Integer> indexList = arrayCache.indexesForArrayKey(primaryArray, primaryCompareString);
265         if (indexList.isEmpty()) {
266             // Primary key not found, no chance of finding secondary
267             return -1;
268         }
269 
270         for (final Integer element : indexList) {
271             final int arrayIndex = element.intValue();
272             if (regexMatches(secondaryCompareRegex, secondaryArray[arrayIndex])) {
273                 instanceCount++;
274                 if (instanceCount == desiredIndex) {
275                     return arrayIndex;
276                 }
277             }
278         }
279         // We didn't return in the for loop, so the desiredMatch
280         // with desiredIndex must not exist in the arrays.
281         return -1;
282     }
283 }