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.ant;
22
23import org.apache.tools.ant.BuildException;
24import org.apache.tools.ant.types.DataType;
25import proguard.*;
26import proguard.classfile.ClassConstants;
27import proguard.classfile.util.ClassUtil;
28
29import java.util.*;
30
31/**
32 * This DataType represents a class specification in Ant.
33 *
34 * @author Eric Lafortune
35 */
36public class ClassSpecificationElement extends DataType
37{
38    private static final String ANY_CLASS_KEYWORD  = "*";
39
40    private String access;
41    private String annotation;
42    private String type;
43    private String name;
44    private String extendsAnnotation;
45    private String extends_;
46    private List   fieldSpecifications  = new ArrayList();
47    private List   methodSpecifications = new ArrayList();
48
49
50    /**
51     * Adds the contents of this class specification element to the given list.
52     * @param classSpecifications the class specifications to be extended.
53     */
54    public void appendTo(List classSpecifications)
55    {
56        // Get the referenced file set, or else this one.
57        ClassSpecificationElement classSpecificationElement = isReference() ?
58            (ClassSpecificationElement)getCheckedRef(this.getClass(),
59                                                     this.getClass().getName()) :
60            this;
61
62        ClassSpecification classSpecification =
63            createClassSpecification(classSpecificationElement);
64
65        // Add it to the list.
66        classSpecifications.add(classSpecification);
67    }
68
69
70    /**
71     * Creates a new class specification corresponding to the contents of this
72     * class specification element.
73     */
74    protected ClassSpecification createClassSpecification(ClassSpecificationElement classSpecificationElement)
75    {
76        String access            = classSpecificationElement.access;
77        String annotation        = classSpecificationElement.annotation;
78        String type              = classSpecificationElement.type;
79        String name              = classSpecificationElement.name;
80        String extendsAnnotation = classSpecificationElement.extendsAnnotation;
81        String extends_          = classSpecificationElement.extends_;
82
83        // For backward compatibility, allow a single "*" wildcard to match
84        // any class.
85        if (name != null &&
86            name.equals(ANY_CLASS_KEYWORD))
87        {
88            name = null;
89        }
90
91        ClassSpecification classSpecification =
92            new ClassSpecification(null,
93                                   requiredAccessFlags(true,  access, type),
94                                   requiredAccessFlags(false, access, type),
95                                   annotation        != null ? ClassUtil.internalType(annotation)        : null,
96                                   name              != null ? ClassUtil.internalClassName(name)         : null,
97                                   extendsAnnotation != null ? ClassUtil.internalType(extendsAnnotation) : null,
98                                   extends_          != null ? ClassUtil.internalClassName(extends_)     : null);
99
100        for (int index = 0; index < fieldSpecifications.size(); index++)
101        {
102            classSpecification.addField((MemberSpecification)fieldSpecifications.get(index));
103        }
104
105        for (int index = 0; index < methodSpecifications.size(); index++)
106        {
107            classSpecification.addMethod((MemberSpecification)methodSpecifications.get(index));
108        }
109
110        return classSpecification;
111    }
112
113
114    // Ant task attributes.
115
116    public void setAccess(String access)
117    {
118        this.access = access;
119    }
120
121
122    public void setAnnotation(String annotation)
123    {
124        this.annotation = annotation;
125    }
126
127
128    public void setType(String type)
129    {
130        this.type = type;
131    }
132
133
134    public void setName(String name)
135    {
136        this.name = name;
137    }
138
139
140    public void setExtendsannotation(String extendsAnnotation)
141    {
142        this.extendsAnnotation = extendsAnnotation;
143    }
144
145
146    public void setExtends(String extends_)
147    {
148        this.extends_ = extends_;
149    }
150
151
152    public void setImplements(String implements_)
153    {
154        this.extends_ = implements_;
155    }
156
157
158    // Ant task nested elements.
159
160    public void addConfiguredField(MemberSpecificationElement memberSpecificationElement)
161    {
162        if (fieldSpecifications == null)
163        {
164            fieldSpecifications = new ArrayList();
165        }
166
167        memberSpecificationElement.appendTo(fieldSpecifications,
168                                            false,
169                                            false);
170    }
171
172
173    public void addConfiguredMethod(MemberSpecificationElement memberSpecificationElement)
174    {
175        if (methodSpecifications == null)
176        {
177            methodSpecifications = new ArrayList();
178        }
179
180        memberSpecificationElement.appendTo(methodSpecifications,
181                                            true,
182                                            false);
183    }
184
185
186    public void addConfiguredConstructor(MemberSpecificationElement memberSpecificationElement)
187    {
188        if (methodSpecifications == null)
189        {
190            methodSpecifications = new ArrayList();
191        }
192
193        memberSpecificationElement.appendTo(methodSpecifications,
194                                            true,
195                                            true);
196    }
197
198
199    // Small utility methods.
200
201    private int requiredAccessFlags(boolean set,
202                                    String  access,
203                                    String  type)
204    throws BuildException
205    {
206        int accessFlags = 0;
207
208        if (access != null)
209        {
210            StringTokenizer tokenizer = new StringTokenizer(access, " ,");
211            while (tokenizer.hasMoreTokens())
212            {
213                String token = tokenizer.nextToken();
214
215                if (token.startsWith("!") ^ set)
216                {
217                    String strippedToken = token.startsWith("!") ?
218                        token.substring(1) :
219                        token;
220
221                    int accessFlag =
222                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_PUBLIC)     ? ClassConstants.INTERNAL_ACC_PUBLIC      :
223                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_FINAL)      ? ClassConstants.INTERNAL_ACC_FINAL       :
224                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_ABSTRACT)   ? ClassConstants.INTERNAL_ACC_ABSTRACT    :
225                        strippedToken.equals(ClassConstants.EXTERNAL_ACC_ANNOTATION) ? ClassConstants.INTERNAL_ACC_ANNOTATTION :
226                        0;
227
228                    if (accessFlag == 0)
229                    {
230                        throw new BuildException("Incorrect class access modifier ["+strippedToken+"]");
231                    }
232
233                    accessFlags |= accessFlag;
234                }
235            }
236        }
237
238        if (type != null && (type.startsWith("!") ^ set))
239        {
240            int accessFlag =
241                type.equals("class")                                     ? 0                                     :
242                type.equals(      ClassConstants.EXTERNAL_ACC_INTERFACE) ||
243                type.equals("!" + ClassConstants.EXTERNAL_ACC_INTERFACE) ? ClassConstants.INTERNAL_ACC_INTERFACE :
244                type.equals(      ClassConstants.EXTERNAL_ACC_ENUM)      ||
245                type.equals("!" + ClassConstants.EXTERNAL_ACC_ENUM)      ? ClassConstants.INTERNAL_ACC_ENUM      :
246                                                                           -1;
247            if (accessFlag == -1)
248            {
249                throw new BuildException("Incorrect class type ["+type+"]");
250            }
251
252            accessFlags |= accessFlag;
253        }
254
255        return accessFlags;
256    }
257}
258