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 */
017package org.apache.commons.compress.harmony.unpack200;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.util.ArrayList;
022import java.util.Collections;
023import java.util.HashMap;
024import java.util.HashSet;
025import java.util.List;
026import java.util.Map;
027import java.util.Set;
028
029import org.apache.commons.compress.harmony.pack200.Codec;
030import org.apache.commons.compress.harmony.pack200.Pack200Exception;
031import org.apache.commons.compress.harmony.unpack200.bytecode.CPClass;
032import org.apache.commons.compress.harmony.unpack200.bytecode.ClassConstantPool;
033import org.apache.commons.compress.harmony.unpack200.bytecode.ConstantPoolEntry;
034
035/**
036 * Inner Class Bands
037 */
038public class IcBands extends BandSet {
039
040    private IcTuple[] icAll;
041
042    private final String[] cpUTF8;
043
044    private final String[] cpClass;
045
046    private Map thisClassToTuple;
047    private Map outerClassToTuples;
048
049    /**
050     * @param segment TODO
051     */
052    public IcBands(final Segment segment) {
053        super(segment);
054        this.cpClass = segment.getCpBands().getCpClass();
055        this.cpUTF8 = segment.getCpBands().getCpUTF8();
056    }
057
058    /*
059     * (non-Javadoc)
060     *
061     * @see org.apache.commons.compress.harmony.unpack200.BandSet#unpack(java.io.InputStream)
062     */
063    @Override
064    public void read(final InputStream in) throws IOException, Pack200Exception {
065        // Read IC bands
066        final int innerClassCount = header.getInnerClassCount();
067        final int[] icThisClassInts = decodeBandInt("ic_this_class", in, Codec.UDELTA5, innerClassCount);
068        final String[] icThisClass = getReferences(icThisClassInts, cpClass);
069        final int[] icFlags = decodeBandInt("ic_flags", in, Codec.UNSIGNED5, innerClassCount);
070        final int outerClasses = SegmentUtils.countBit16(icFlags);
071        final int[] icOuterClassInts = decodeBandInt("ic_outer_class", in, Codec.DELTA5, outerClasses);
072        final String[] icOuterClass = new String[outerClasses];
073        for (int i = 0; i < icOuterClass.length; i++) {
074            if (icOuterClassInts[i] == 0) {
075                icOuterClass[i] = null;
076            } else {
077                icOuterClass[i] = cpClass[icOuterClassInts[i] - 1];
078            }
079        }
080        final int[] icNameInts = decodeBandInt("ic_name", in, Codec.DELTA5, outerClasses);
081        final String[] icName = new String[outerClasses];
082        for (int i = 0; i < icName.length; i++) {
083            if (icNameInts[i] == 0) {
084                icName[i] = null;
085            } else {
086                icName[i] = cpUTF8[icNameInts[i] - 1];
087            }
088        }
089
090        // Construct IC tuples
091        icAll = new IcTuple[icThisClass.length];
092        int index = 0;
093        for (int i = 0; i < icThisClass.length; i++) {
094            final String icTupleC = icThisClass[i];
095            final int icTupleF = icFlags[i];
096            String icTupleC2 = null;
097            String icTupleN = null;
098            final int cIndex = icThisClassInts[i];
099            int c2Index = -1;
100            int nIndex = -1;
101            if ((icFlags[i] & 1 << 16) != 0) {
102                icTupleC2 = icOuterClass[index];
103                icTupleN = icName[index];
104                c2Index = icOuterClassInts[index] - 1;
105                nIndex = icNameInts[index] - 1;
106                index++;
107            }
108            icAll[i] = new IcTuple(icTupleC, icTupleF, icTupleC2, icTupleN, cIndex, c2Index, nIndex, i);
109        }
110    }
111
112    @Override
113    public void unpack() throws IOException, Pack200Exception {
114        final IcTuple[] allTuples = getIcTuples();
115        thisClassToTuple = new HashMap(allTuples.length);
116        outerClassToTuples = new HashMap(allTuples.length);
117        for (int index = 0; index < allTuples.length; index++) {
118
119            final IcTuple tuple = allTuples[index];
120
121            // generate mapping thisClassString -> IcTuple
122            // presumably this relation is 1:1
123            //
124            final Object result = thisClassToTuple.put(tuple.thisClassString(), tuple);
125            if (result != null) {
126                throw new Error("Collision detected in <thisClassString, IcTuple> mapping. "
127                    + "There are at least two inner clases with the same name.");
128            }
129
130            // generate mapping outerClassString -> IcTuple
131            // this relation is 1:M
132
133            // If it's not anon and the outer is not anon, it could be relevant
134            if ((!tuple.isAnonymous() && !tuple.outerIsAnonymous()) || (tuple.nestedExplicitFlagSet())) {
135
136                // add tuple to corresponding bucket
137                final String key = tuple.outerClassString();
138                List bucket = (List) outerClassToTuples.get(key);
139                if (bucket == null) {
140                    bucket = new ArrayList();
141                    outerClassToTuples.put(key, bucket);
142                }
143                bucket.add(tuple);
144            }
145        }
146    }
147
148    public IcTuple[] getIcTuples() {
149        return icAll;
150    }
151
152    /**
153     * Answer the relevant IcTuples for the specified className and class constant pool.
154     *
155     * @param className String name of the class X for ic_relevant(X)
156     * @param cp ClassConstantPool used to generate ic_relevant(X)
157     * @return array of IcTuple
158     */
159    public IcTuple[] getRelevantIcTuples(final String className, final ClassConstantPool cp) {
160        final Set relevantTuplesContains = new HashSet();
161        final List relevantTuples = new ArrayList();
162
163        final List relevantCandidates = (List) outerClassToTuples.get(className);
164        if (relevantCandidates != null) {
165            for (int index = 0; index < relevantCandidates.size(); index++) {
166                final IcTuple tuple = (IcTuple) relevantCandidates.get(index);
167                relevantTuplesContains.add(tuple);
168                relevantTuples.add(tuple);
169            }
170        }
171
172        final List entries = cp.entries();
173
174        // For every class constant in both ic_this_class and cp,
175        // add it to ic_relevant. Repeat until no more
176        // changes to ic_relevant.
177
178        for (int eIndex = 0; eIndex < entries.size(); eIndex++) {
179            final ConstantPoolEntry entry = (ConstantPoolEntry) entries.get(eIndex);
180            if (entry instanceof CPClass) {
181                final CPClass clazz = (CPClass) entry;
182                final IcTuple relevant = (IcTuple) thisClassToTuple.get(clazz.name);
183                if (relevant != null && relevantTuplesContains.add(relevant)) {
184                    relevantTuples.add(relevant);
185                }
186            }
187        }
188
189        // Not part of spec: fix up by adding to relevantTuples the parents
190        // of inner classes which are themselves inner classes.
191        // i.e., I think that if Foo$Bar$Baz gets added, Foo$Bar needs to be
192        // added
193        // as well.
194
195        final ArrayList tuplesToScan = new ArrayList(relevantTuples);
196        final ArrayList tuplesToAdd = new ArrayList();
197
198        while (tuplesToScan.size() > 0) {
199
200            tuplesToAdd.clear();
201            for (int index = 0; index < tuplesToScan.size(); index++) {
202                final IcTuple aRelevantTuple = (IcTuple) tuplesToScan.get(index);
203                final IcTuple relevant = (IcTuple) thisClassToTuple.get(aRelevantTuple.outerClassString());
204                if (relevant != null && !aRelevantTuple.outerIsAnonymous()) {
205                    tuplesToAdd.add(relevant);
206                }
207            }
208
209            tuplesToScan.clear();
210            for (int index = 0; index < tuplesToAdd.size(); index++) {
211                final IcTuple tuple = (IcTuple) tuplesToAdd.get(index);
212                if (relevantTuplesContains.add(tuple)) {
213                    relevantTuples.add(tuple);
214                    tuplesToScan.add(tuple);
215                }
216            }
217
218        }
219
220        // End not part of the spec. Ugh.
221
222        // Now order the result as a subsequence of ic_all
223        Collections.sort(relevantTuples, (arg0, arg1) -> {
224            final Integer index1 = Integer.valueOf(((IcTuple) arg0).getTupleIndex());
225            final Integer index2 = Integer.valueOf(((IcTuple) arg1).getTupleIndex());
226            return index1.compareTo(index2);
227        });
228
229        final IcTuple[] relevantTuplesArray = new IcTuple[relevantTuples.size()];
230        for (int i = 0; i < relevantTuplesArray.length; i++) {
231            relevantTuplesArray[i] = (IcTuple) relevantTuples.get(i);
232        }
233
234        return relevantTuplesArray;
235    }
236
237}