1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.dx.dex.file;
18
19import com.android.dx.util.AnnotatedOutput;
20import com.android.dx.util.Hex;
21import java.util.List;
22
23/**
24 * Class that represents a contiguous list of uniform items. Each
25 * item in the list, in particular, must have the same write size and
26 * alignment.
27 *
28 * <p>This class inherits its alignment from its items, bumped up to
29 * {@code 4} if the items have a looser alignment requirement. If
30 * it is more than {@code 4}, then there will be a gap after the
31 * output list size (which is four bytes) and before the first item.</p>
32 *
33 * @param <T> type of element contained in an instance
34 */
35public final class UniformListItem<T extends OffsettedItem>
36        extends OffsettedItem {
37    /** the size of the list header */
38    private static final int HEADER_SIZE = 4;
39
40    /** {@code non-null;} the item type */
41    private final ItemType itemType;
42
43    /** {@code non-null;} the contents */
44    private final List<T> items;
45
46    /**
47     * Constructs an instance. It is illegal to modify the given list once
48     * it is used to construct an instance of this class.
49     *
50     * @param itemType {@code non-null;} the type of the item
51     * @param items {@code non-null and non-empty;} list of items to represent
52     */
53    public UniformListItem(ItemType itemType, List<T> items) {
54        super(getAlignment(items), writeSize(items));
55
56        if (itemType == null) {
57            throw new NullPointerException("itemType == null");
58        }
59
60        this.items = items;
61        this.itemType = itemType;
62    }
63
64    /**
65     * Helper for {@link #UniformListItem}, which returns the alignment
66     * requirement implied by the given list. See the header comment for
67     * more details.
68     *
69     * @param items {@code non-null;} list of items being represented
70     * @return {@code >= 4;} the alignment requirement
71     */
72    private static int getAlignment(List<? extends OffsettedItem> items) {
73        try {
74            // Since they all must have the same alignment, any one will do.
75            return Math.max(HEADER_SIZE, items.get(0).getAlignment());
76        } catch (IndexOutOfBoundsException ex) {
77            // Translate the exception.
78            throw new IllegalArgumentException("items.size() == 0");
79        } catch (NullPointerException ex) {
80            // Translate the exception.
81            throw new NullPointerException("items == null");
82        }
83    }
84
85    /**
86     * Calculates the write size for the given list.
87     *
88     * @param items {@code non-null;} the list in question
89     * @return {@code >= 0;} the write size
90     */
91    private static int writeSize(List<? extends OffsettedItem> items) {
92        /*
93         * This class assumes all included items are the same size,
94         * an assumption which is verified in place0().
95         */
96        OffsettedItem first = items.get(0);
97        return (items.size() * first.writeSize()) + getAlignment(items);
98    }
99
100    /** {@inheritDoc} */
101    @Override
102    public ItemType itemType() {
103        return itemType;
104    }
105
106    /** {@inheritDoc} */
107    @Override
108    public String toString() {
109        StringBuilder sb = new StringBuilder(100);
110
111        sb.append(getClass().getName());
112        sb.append(items);
113
114        return sb.toString();
115    }
116
117    /** {@inheritDoc} */
118    @Override
119    public void addContents(DexFile file) {
120        for (OffsettedItem i : items) {
121            i.addContents(file);
122        }
123    }
124
125    /** {@inheritDoc} */
126    @Override
127    public final String toHuman() {
128        StringBuilder sb = new StringBuilder(100);
129        boolean first = true;
130
131        sb.append("{");
132
133        for (OffsettedItem i : items) {
134            if (first) {
135                first = false;
136            } else {
137                sb.append(", ");
138            }
139            sb.append(i.toHuman());
140        }
141
142        sb.append("}");
143        return sb.toString();
144    }
145
146    /**
147     * Gets the underlying list of items.
148     *
149     * @return {@code non-null;} the list
150     */
151    public final List<T> getItems() {
152        return items;
153    }
154
155    /** {@inheritDoc} */
156    @Override
157    protected void place0(Section addedTo, int offset) {
158        offset += headerSize();
159
160        boolean first = true;
161        int theSize = -1;
162        int theAlignment = -1;
163
164        for (OffsettedItem i : items) {
165            int size = i.writeSize();
166            if (first) {
167                theSize = size;
168                theAlignment = i.getAlignment();
169                first = false;
170            } else {
171                if (size != theSize) {
172                    throw new UnsupportedOperationException(
173                            "item size mismatch");
174                }
175                if (i.getAlignment() != theAlignment) {
176                    throw new UnsupportedOperationException(
177                            "item alignment mismatch");
178                }
179            }
180
181            offset = i.place(addedTo, offset) + size;
182        }
183    }
184
185    /** {@inheritDoc} */
186    @Override
187    protected void writeTo0(DexFile file, AnnotatedOutput out) {
188        int size = items.size();
189
190        if (out.annotates()) {
191            out.annotate(0, offsetString() + " " + typeName());
192            out.annotate(4, "  size: " + Hex.u4(size));
193        }
194
195        out.writeInt(size);
196
197        for (OffsettedItem i : items) {
198            i.writeTo(file, out);
199        }
200    }
201
202    /**
203     * Get the size of the header of this list.
204     *
205     * @return {@code >= 0;} the header size
206     */
207    private int headerSize() {
208        /*
209         * Because of how this instance was set up, this is the same
210         * as the alignment.
211         */
212        return getAlignment();
213    }
214}
215