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 *
017 */
018package org.apache.bcel.generic;
019
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.DataInput;
023import java.io.DataInputStream;
024import java.io.DataOutputStream;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.List;
028
029import org.apache.bcel.classfile.AnnotationEntry;
030import org.apache.bcel.classfile.Attribute;
031import org.apache.bcel.classfile.ConstantUtf8;
032import org.apache.bcel.classfile.ElementValuePair;
033import org.apache.bcel.classfile.RuntimeInvisibleAnnotations;
034import org.apache.bcel.classfile.RuntimeInvisibleParameterAnnotations;
035import org.apache.bcel.classfile.RuntimeVisibleAnnotations;
036import org.apache.bcel.classfile.RuntimeVisibleParameterAnnotations;
037
038/**
039 * @since 6.0
040 */
041public class AnnotationEntryGen {
042    private int typeIndex;
043
044    private List<ElementValuePairGen> evs;
045
046    private final ConstantPoolGen cpool;
047
048    private boolean isRuntimeVisible = false;
049
050    /**
051     * Here we are taking a fixed annotation of type Annotation and building a
052     * modifiable AnnotationGen object. If the pool passed in is for a different
053     * class file, then copyPoolEntries should have been passed as true as that
054     * will force us to do a deep copy of the annotation and move the cpool
055     * entries across. We need to copy the type and the element name value pairs
056     * and the visibility.
057     */
058    public AnnotationEntryGen(final AnnotationEntry a, final ConstantPoolGen cpool,
059                              final boolean copyPoolEntries) {
060        this.cpool = cpool;
061        if (copyPoolEntries) {
062            typeIndex = cpool.addUtf8(a.getAnnotationType());
063        } else {
064            typeIndex = a.getAnnotationTypeIndex();
065        }
066        isRuntimeVisible = a.isRuntimeVisible();
067        evs = copyValues(a.getElementValuePairs(), cpool, copyPoolEntries);
068    }
069
070    private List<ElementValuePairGen> copyValues(final ElementValuePair[] in, final ConstantPoolGen cpool,
071                                                 final boolean copyPoolEntries) {
072        final List<ElementValuePairGen> out = new ArrayList<>();
073        for (final ElementValuePair nvp : in) {
074            out.add(new ElementValuePairGen(nvp, cpool, copyPoolEntries));
075        }
076        return out;
077    }
078
079    private AnnotationEntryGen(final ConstantPoolGen cpool) {
080        this.cpool = cpool;
081    }
082
083    /**
084     * Retrieve an immutable version of this AnnotationGen
085     */
086    public AnnotationEntry getAnnotation() {
087        final AnnotationEntry a = new AnnotationEntry(typeIndex, cpool.getConstantPool(),
088                isRuntimeVisible);
089        for (final ElementValuePairGen element : evs) {
090            a.addElementNameValuePair(element.getElementNameValuePair());
091        }
092        return a;
093    }
094
095    public AnnotationEntryGen(final ObjectType type,
096                              final List<ElementValuePairGen> elements, final boolean vis,
097                              final ConstantPoolGen cpool) {
098        this.cpool = cpool;
099        this.typeIndex = cpool.addUtf8(type.getSignature());
100        evs = elements;
101        isRuntimeVisible = vis;
102    }
103
104    public static AnnotationEntryGen read(final DataInput dis,
105                                          final ConstantPoolGen cpool, final boolean b) throws IOException {
106        final AnnotationEntryGen a = new AnnotationEntryGen(cpool);
107        a.typeIndex = dis.readUnsignedShort();
108        final int elemValuePairCount = dis.readUnsignedShort();
109        for (int i = 0; i < elemValuePairCount; i++) {
110            final int nidx = dis.readUnsignedShort();
111            a.addElementNameValuePair(new ElementValuePairGen(nidx,
112                    ElementValueGen.readElementValue(dis, cpool), cpool));
113        }
114        a.isRuntimeVisible(b);
115        return a;
116    }
117
118    public void dump(final DataOutputStream dos) throws IOException {
119        dos.writeShort(typeIndex); // u2 index of type name in cpool
120        dos.writeShort(evs.size()); // u2 element_value pair count
121        for (final ElementValuePairGen envp : evs) {
122            envp.dump(dos);
123        }
124    }
125
126    public void addElementNameValuePair(final ElementValuePairGen evp) {
127        if (evs == null) {
128            evs = new ArrayList<>();
129        }
130        evs.add(evp);
131    }
132
133    public int getTypeIndex() {
134        return typeIndex;
135    }
136
137    public final String getTypeSignature() {
138        // ConstantClass c = (ConstantClass)cpool.getConstant(typeIndex);
139        final ConstantUtf8 utf8 = (ConstantUtf8) cpool
140                .getConstant(typeIndex/* c.getNameIndex() */);
141        return utf8.getBytes();
142    }
143
144    public final String getTypeName() {
145        return getTypeSignature();// BCELBUG: Should I use this instead?
146        // Utility.signatureToString(getTypeSignature());
147    }
148
149    /**
150     * Returns list of ElementNameValuePair objects
151     */
152    public List<ElementValuePairGen> getValues() {
153        return evs;
154    }
155
156    @Override
157    public String toString() {
158        final StringBuilder s = new StringBuilder(32); // CHECKSTYLE IGNORE MagicNumber
159        s.append("AnnotationGen:[").append(getTypeName()).append(" #").append(evs.size()).append(" {");
160        for (int i = 0; i < evs.size(); i++) {
161            s.append(evs.get(i));
162            if (i + 1 < evs.size()) {
163                s.append(",");
164            }
165        }
166        s.append("}]");
167        return s.toString();
168    }
169
170    public String toShortString() {
171        final StringBuilder s = new StringBuilder();
172        s.append("@").append(getTypeName()).append("(");
173        for (int i = 0; i < evs.size(); i++) {
174            s.append(evs.get(i));
175            if (i + 1 < evs.size()) {
176                s.append(",");
177            }
178        }
179        s.append(")");
180        return s.toString();
181    }
182
183    private void isRuntimeVisible(final boolean b) {
184        isRuntimeVisible = b;
185    }
186
187    public boolean isRuntimeVisible() {
188        return isRuntimeVisible;
189    }
190
191
192    /**
193     * Converts a list of AnnotationGen objects into a set of attributes
194     * that can be attached to the class file.
195     *
196     * @param cp  The constant pool gen where we can create the necessary name refs
197     * @param annotationEntryGens An array of AnnotationGen objects
198     */
199    static Attribute[] getAnnotationAttributes(final ConstantPoolGen cp, final AnnotationEntryGen[] annotationEntryGens) {
200        if (annotationEntryGens.length == 0) {
201            return new Attribute[0];
202        }
203
204        try {
205            int countVisible = 0;
206            int countInvisible = 0;
207
208            //  put the annotations in the right output stream
209            for (final AnnotationEntryGen a : annotationEntryGens) {
210                if (a.isRuntimeVisible()) {
211                    countVisible++;
212                } else {
213                    countInvisible++;
214                }
215            }
216
217            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
218            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
219            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes);
220                    DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
221
222                rvaDos.writeShort(countVisible);
223                riaDos.writeShort(countInvisible);
224
225                // put the annotations in the right output stream
226                for (final AnnotationEntryGen a : annotationEntryGens) {
227                    if (a.isRuntimeVisible()) {
228                        a.dump(rvaDos);
229                    } else {
230                        a.dump(riaDos);
231                    }
232                }
233            }
234
235            final byte[] rvaData = rvaBytes.toByteArray();
236            final byte[] riaData = riaBytes.toByteArray();
237
238            int rvaIndex = -1;
239            int riaIndex = -1;
240
241            if (rvaData.length > 2) {
242                rvaIndex = cp.addUtf8("RuntimeVisibleAnnotations");
243            }
244            if (riaData.length > 2) {
245                riaIndex = cp.addUtf8("RuntimeInvisibleAnnotations");
246            }
247
248            final List<Attribute> newAttributes = new ArrayList<>();
249            if (rvaData.length > 2) {
250                newAttributes.add(
251                        new RuntimeVisibleAnnotations(rvaIndex, rvaData.length,
252                            new DataInputStream(new ByteArrayInputStream(rvaData)), cp.getConstantPool()));
253            }
254            if (riaData.length > 2) {
255                newAttributes.add(
256                        new RuntimeInvisibleAnnotations(riaIndex, riaData.length,
257                            new DataInputStream(new ByteArrayInputStream(riaData)), cp.getConstantPool()));
258            }
259
260            return newAttributes.toArray(new Attribute[newAttributes.size()]);
261        } catch (final IOException e) {
262            System.err.println("IOException whilst processing annotations");
263            e.printStackTrace();
264        }
265        return null;
266    }
267
268
269    /**
270     * Annotations against a class are stored in one of four attribute kinds:
271     * - RuntimeVisibleParameterAnnotations
272     * - RuntimeInvisibleParameterAnnotations
273     */
274    static Attribute[] getParameterAnnotationAttributes(
275            final ConstantPoolGen cp,
276            final List<AnnotationEntryGen>[] /*Array of lists, array size depends on #params */vec) {
277        final int[] visCount = new int[vec.length];
278        int totalVisCount = 0;
279        final int[] invisCount = new int[vec.length];
280        int totalInvisCount = 0;
281        try {
282            for (int i = 0; i < vec.length; i++) {
283                if (vec[i] != null) {
284                    for (final AnnotationEntryGen element : vec[i]) {
285                        if (element.isRuntimeVisible()) {
286                            visCount[i]++;
287                            totalVisCount++;
288                        } else {
289                            invisCount[i]++;
290                            totalInvisCount++;
291                        }
292                    }
293                }
294            }
295            // Lets do the visible ones
296            final ByteArrayOutputStream rvaBytes = new ByteArrayOutputStream();
297            try (DataOutputStream rvaDos = new DataOutputStream(rvaBytes)) {
298                rvaDos.writeByte(vec.length); // First goes number of parameters
299                for (int i = 0; i < vec.length; i++) {
300                    rvaDos.writeShort(visCount[i]);
301                    if (visCount[i] > 0) {
302                        for (final AnnotationEntryGen element : vec[i]) {
303                            if (element.isRuntimeVisible()) {
304                                element.dump(rvaDos);
305                            }
306                        }
307                    }
308                }
309            }
310            // Lets do the invisible ones
311            final ByteArrayOutputStream riaBytes = new ByteArrayOutputStream();
312            try (DataOutputStream riaDos = new DataOutputStream(riaBytes)) {
313                riaDos.writeByte(vec.length); // First goes number of parameters
314                for (int i = 0; i < vec.length; i++) {
315                    riaDos.writeShort(invisCount[i]);
316                    if (invisCount[i] > 0) {
317                        for (final AnnotationEntryGen element : vec[i]) {
318                            if (!element.isRuntimeVisible()) {
319                                element.dump(riaDos);
320                            }
321                        }
322                    }
323                }
324            }
325            final byte[] rvaData = rvaBytes.toByteArray();
326            final byte[] riaData = riaBytes.toByteArray();
327            int rvaIndex = -1;
328            int riaIndex = -1;
329            if (totalVisCount > 0) {
330                rvaIndex = cp.addUtf8("RuntimeVisibleParameterAnnotations");
331            }
332            if (totalInvisCount > 0) {
333                riaIndex = cp.addUtf8("RuntimeInvisibleParameterAnnotations");
334            }
335            final List<Attribute> newAttributes = new ArrayList<>();
336            if (totalVisCount > 0) {
337                newAttributes
338                        .add(new RuntimeVisibleParameterAnnotations(rvaIndex,
339                                rvaData.length, new DataInputStream(new ByteArrayInputStream(rvaData)), cp.getConstantPool()));
340            }
341            if (totalInvisCount > 0) {
342                newAttributes
343                        .add(new RuntimeInvisibleParameterAnnotations(riaIndex,
344                                riaData.length, new DataInputStream(new ByteArrayInputStream(riaData)), cp.getConstantPool()));
345            }
346            return newAttributes.toArray(new Attribute[newAttributes.size()]);
347        } catch (final IOException e) {
348            System.err
349                    .println("IOException whilst processing parameter annotations");
350            e.printStackTrace();
351        }
352        return null;
353    }
354
355}