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