DescriptorClassEnumeration.java revision db267bc191f906f55eaef21a27110cce2ec57fdf
1/*
2 * ProGuard -- shrinking, optimization, obfuscation, and preverification
3 *             of Java bytecode.
4 *
5 * Copyright (c) 2002-2009 Eric Lafortune (eric@graphics.cornell.edu)
6 *
7 * This program is free software; you can redistribute it and/or modify it
8 * under the terms of the GNU General Public License as published by the Free
9 * Software Foundation; either version 2 of the License, or (at your option)
10 * any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
15 * more details.
16 *
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20 */
21package proguard.classfile.util;
22
23import proguard.classfile.ClassConstants;
24
25import java.util.Stack;
26
27/**
28 * A <code>DescriptorClassEnumeration</code> provides an enumeration of all
29 * classes mentioned in a given descriptor or signature.
30 *
31 * @author Eric Lafortune
32 */
33public class DescriptorClassEnumeration
34{
35    private String  descriptor;
36
37    private int     index;
38    private int     nestingLevel;
39    private boolean isInnerClassName;
40    private String  accumulatedClassName;
41    private Stack   accumulatedClassNames;
42
43
44    /**
45     * Creates a new DescriptorClassEnumeration for the given descriptor.
46     */
47    public DescriptorClassEnumeration(String descriptor)
48    {
49        this.descriptor = descriptor;
50    }
51
52
53    /**
54     * Returns the number of classes contained in the descriptor. This
55     * is the number of class names that the enumeration will return.
56     */
57    public int classCount()
58    {
59        int count = 0;
60
61        nextFluff();
62        while (hasMoreClassNames())
63        {
64            count++;
65
66            nextClassName();
67            nextFluff();
68        }
69
70        index = 0;
71
72        return count;
73    }
74
75
76    /**
77     * Returns whether the enumeration can provide more class names from the
78     * descriptor.
79     */
80    public boolean hasMoreClassNames()
81    {
82        return index < descriptor.length();
83    }
84
85
86    /**
87     * Returns the next fluff (surrounding class names) from the descriptor.
88     */
89    public String nextFluff()
90    {
91        int fluffStartIndex = index;
92
93        // Find the first token marking the start of a class name 'L' or '.'.
94        loop: while (index < descriptor.length())
95        {
96            switch (descriptor.charAt(index++))
97            {
98                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
99                {
100                    nestingLevel++;
101
102                    // Make sure we have a stack.
103                    if (accumulatedClassNames == null)
104                    {
105                        accumulatedClassNames = new Stack();
106                    }
107
108                    // Remember the accumulated class name.
109                    accumulatedClassNames.push(accumulatedClassName);
110
111                    break;
112                }
113                case ClassConstants.INTERNAL_TYPE_GENERIC_END:
114                {
115                    nestingLevel--;
116
117                    // Return to the accumulated class name outside the
118                    // generic block.
119                    accumulatedClassName = (String)accumulatedClassNames.pop();
120
121                    continue loop;
122                }
123                case ClassConstants.INTERNAL_TYPE_GENERIC_BOUND:
124                {
125                    continue loop;
126                }
127                case ClassConstants.INTERNAL_TYPE_CLASS_START:
128                {
129                    // We've found the start of an ordinary class name.
130                    nestingLevel += 2;
131                    isInnerClassName = false;
132                    break loop;
133                }
134                case ClassConstants.INTERNAL_TYPE_CLASS_END:
135                {
136                    nestingLevel -= 2;
137                    break;
138                }
139                case ClassConstants.EXTERNAL_INNER_CLASS_SEPARATOR:
140                {
141                    // We've found the start of an inner class name in a signature.
142                    isInnerClassName = true;
143                    break loop;
144                }
145                case ClassConstants.INTERNAL_TYPE_GENERIC_VARIABLE_START:
146                {
147                    // We've found the start of a type identifier. Skip to the end.
148                    while (descriptor.charAt(index++) != ClassConstants.INTERNAL_TYPE_CLASS_END);
149                    break;
150                }
151            }
152
153            if (nestingLevel == 1 &&
154                descriptor.charAt(index) != ClassConstants.INTERNAL_TYPE_GENERIC_END)
155            {
156                // We're at the start of a type parameter. Skip to the start
157                // of the bounds.
158                while (descriptor.charAt(index++) != ClassConstants.INTERNAL_TYPE_GENERIC_BOUND);
159            }
160        }
161
162        return descriptor.substring(fluffStartIndex, index);
163    }
164
165
166    /**
167     * Returns the next class name from the descriptor.
168     */
169    public String nextClassName()
170    {
171        int classNameStartIndex = index;
172
173        // Find the first token marking the end of a class name '<' or ';'.
174        loop: while (true)
175        {
176            switch (descriptor.charAt(index))
177            {
178                case ClassConstants.INTERNAL_TYPE_GENERIC_START:
179                case ClassConstants.INTERNAL_TYPE_CLASS_END:
180                case ClassConstants.EXTERNAL_INNER_CLASS_SEPARATOR:
181                {
182                    break loop;
183                }
184            }
185
186            index++;
187        }
188
189        String className = descriptor.substring(classNameStartIndex, index);
190
191        // Recompose the inner class name if necessary.
192        accumulatedClassName = isInnerClassName ?
193            accumulatedClassName + ClassConstants.INTERNAL_INNER_CLASS_SEPARATOR + className :
194            className;
195
196        return accumulatedClassName;
197    }
198
199
200    /**
201     * Returns whether the most recently returned class name was a recomposed
202     * inner class name from a signature.
203     */
204    public boolean isInnerClassName()
205    {
206        return isInnerClassName;
207    }
208
209
210    /**
211     * A main method for testing the class name enumeration.
212     */
213    public static void main(String[] args)
214    {
215        try
216        {
217            for (int index = 0; index < args.length; index++)
218            {
219                String descriptor = args[index];
220
221                System.out.println("Descriptor ["+descriptor+"]");
222                DescriptorClassEnumeration enumeration = new DescriptorClassEnumeration(descriptor);
223                System.out.println("  Fluff: ["+enumeration.nextFluff()+"]");
224                while (enumeration.hasMoreClassNames())
225                {
226                    System.out.println("  Name:  ["+enumeration.nextClassName()+"]");
227                    System.out.println("  Fluff: ["+enumeration.nextFluff()+"]");
228                }
229            }
230        }
231        catch (Exception ex)
232        {
233            ex.printStackTrace();
234        }
235    }
236}
237