    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.pack200;
    020
    021import java.io.IOException;
    022import java.io.OutputStream;
    023import java.util.ArrayList;
    024import java.util.HashMap;
    025import java.util.List;
    026import java.util.Map;
    027import java.util.Objects;
    028import java.util.Set;
    029import java.util.TreeSet;
    030
    031/**
    032 * Inner class bands (corresponds to the {@code ic_bands} set of bands in the pack200 specification)
    033 */
    034public class IcBands extends BandSet {
    035
    036    static final class IcTuple implements Comparable<IcTuple> {
    037
    038        protected CPClass C; // this class
    039        protected int F; // flags
    040        protected CPClass C2; // outer class
    041        protected CPUTF8 N; // name
    042
    043        IcTuple(final CPClass C, final int F, final CPClass C2, final CPUTF8 N) {
    044            this.C = C;
    045            this.F = F;
    046            this.C2 = C2;
    047            this.N = N;
    048        }
    049
    050        @Override
    051        public int compareTo(final IcTuple arg0) {
    052            return C.compareTo(arg0.C);
    053        }
    054
    055        @Override
    056        public boolean equals(final Object o) {
    057            if (o instanceof IcTuple) {
    058                final IcTuple icT = (IcTuple) o;
    059                return C.equals(icT.C) && F == icT.F && Objects.equals(C2, icT.C2) && Objects.equals(N, icT.N);
    060            }
    061            return false;
    062        }
    063
    064        public boolean isAnonymous() {
    065            final String className = C.toString();
    066            final String innerName = className.substring(className.lastIndexOf('$') + 1);
    067            return Character.isDigit(innerName.charAt(0));
    068        }
    069
    070        @Override
    071        public String toString() {
    072            return C.toString();
    073        }
    074
    075    }
    076
    077    private final Set<IcTuple> innerClasses = new TreeSet<>();
    078    private final CpBands cpBands;
    079    private int bit16Count;
    080
    081    private final Map<String, List<IcTuple>> outerToInner = new HashMap<>();
    082
    083    public IcBands(final SegmentHeader segmentHeader, final CpBands cpBands, final int effort) {
    084        super(effort, segmentHeader);
    085        this.cpBands = cpBands;
    086    }
    087
    088    public void addInnerClass(final String name, final String outerName, final String innerName, int flags) {
    089        if (outerName != null || innerName != null) {
    090            if (namesArePredictable(name, outerName, innerName)) {
    091                final IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null);
    092                addToMap(outerName, innerClass);
    093                innerClasses.add(innerClass);
    094            } else {
    095                flags |= 1 << 16;
    096                final IcTuple icTuple = new IcTuple(cpBands.getCPClass(name), flags, cpBands.getCPClass(outerName), cpBands.getCPUtf8(innerName));
    097                final boolean added = innerClasses.add(icTuple);
    098                if (added) {
    099                    bit16Count++;
    100                    addToMap(outerName, icTuple);
    101                }
    102            }
    103        } else {
    104            final IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null);
    105            addToMap(getOuter(name), innerClass);
    106            innerClasses.add(innerClass);
    107        }
    108    }
    109
    110    private void addToMap(final String outerName, final IcTuple icTuple) {
    111        List<IcTuple> tuples = outerToInner.get(outerName);
    112        if (tuples == null) {
    113            tuples = new ArrayList<>();
    114            outerToInner.put(outerName, tuples);
    115        } else {
    116            for (final IcTuple tuple : tuples) {
    117                if (icTuple.equals(tuple)) {
    118                    return;
    119                }
    120            }
    121        }
    122        tuples.add(icTuple);
    123    }
    124
    125    /**
    126     * All input classes for the segment have now been read in, so this method is called so that this class can calculate/complete anything it could not do
    127     * while classes were being read.
    128     */
    129    public void finaliseBands() {
    130        segmentHeader.setIc_count(innerClasses.size());
    131    }
    132
    133    public IcTuple getIcTuple(final CPClass inner) {
    134        for (final IcTuple icTuple : innerClasses) {
    135            if (icTuple.C.equals(inner)) {
    136                return icTuple;
    137            }
    138        }
    139        return null;
    140    }
    141
    142    public List<IcTuple> getInnerClassesForOuter(final String outerClassName) {
    143        return outerToInner.get(outerClassName);
    144    }
    145
    146    private String getOuter(final String name) {
    147        return name.substring(0, name.lastIndexOf('$'));
    148    }
    149
    150    private boolean namesArePredictable(final String name, final String outerName, final String innerName) {
    151        // TODO: Could be multiple characters, not just $
    152        return name.equals(outerName + '$' + innerName) && innerName.indexOf('$') == -1;
    153    }
    154
    155    @Override
    156    public void pack(final OutputStream outputStream) throws IOException, Pack200Exception {
    157        PackingUtils.log("Writing internal class bands...");
    158        final int[] ic_this_class = new int[innerClasses.size()];
    159        final int[] ic_flags = new int[innerClasses.size()];
    160        final int[] ic_outer_class = new int[bit16Count];
    161        final int[] ic_name = new int[bit16Count];
    162
    163        int index2 = 0;
    164        final List<IcTuple> innerClassesList = new ArrayList<>(innerClasses);
    165        for (int i = 0; i < ic_this_class.length; i++) {
    166            final IcTuple icTuple = innerClassesList.get(i);
    167            ic_this_class[i] = icTuple.C.getIndex();
    168            ic_flags[i] = icTuple.F;
    169            if ((icTuple.F & 1 << 16) != 0) {
    170                ic_outer_class[index2] = icTuple.C2 == null ? 0 : icTuple.C2.getIndex() + 1;
    171                ic_name[index2] = icTuple.N == null ? 0 : icTuple.N.getIndex() + 1;
    172                index2++;
    173            }
    174        }
    175        byte[] encodedBand = encodeBandInt("ic_this_class", ic_this_class, Codec.UDELTA5);
    176        outputStream.write(encodedBand);
    177        PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_this_class[" + ic_this_class.length + "]");
    178
    179        encodedBand = encodeBandInt("ic_flags", ic_flags, Codec.UNSIGNED5);
    180        outputStream.write(encodedBand);
    181        PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_flags[" + ic_flags.length + "]");
    182
    183        encodedBand = encodeBandInt("ic_outer_class", ic_outer_class, Codec.DELTA5);
    184        outputStream.write(encodedBand);
    185        PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_outer_class[" + ic_outer_class.length + "]");
    186
    187        encodedBand = encodeBandInt("ic_name", ic_name, Codec.DELTA5);
    188        outputStream.write(encodedBand);
    189        PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_name[" + ic_name.length + "]");
    190    }
    191
    192}