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