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}