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}