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