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 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 public 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)) 058 && (Objects.equals(N, icT.N)); 059 } 060 return false; 061 } 062 063 public boolean isAnonymous() { 064 final String className = C.toString(); 065 final String innerName = className.substring(className.lastIndexOf('$') + 1); 066 return Character.isDigit(innerName.charAt(0)); 067 } 068 069 @Override 070 public String toString() { 071 return C.toString(); 072 } 073 074 } 075 private final Set<IcTuple> innerClasses = new TreeSet<>(); 076 private final CpBands cpBands; 077 private int bit16Count = 0; 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), 095 cpBands.getCPUtf8(innerName)); 096 final boolean added = innerClasses.add(icTuple); 097 if (added) { 098 bit16Count++; 099 addToMap(outerName, icTuple); 100 } 101 } 102 } else { 103 final IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null); 104 addToMap(getOuter(name), innerClass); 105 innerClasses.add(innerClass); 106 } 107 } 108 109 private void addToMap(final String outerName, final IcTuple icTuple) { 110 List<IcTuple> tuples = outerToInner.get(outerName); 111 if (tuples == null) { 112 tuples = new ArrayList<>(); 113 outerToInner.put(outerName, tuples); 114 tuples.add(icTuple); 115 } else { 116 for (final IcTuple tuple : tuples) { 117 if (icTuple.equals(tuple)) { 118 return; 119 } 120 } 121 tuples.add(icTuple); 122 } 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 127 * calculate/complete anything it could not do 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}