    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}