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