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 static 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 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) && Objects.equals(N, icT.N); 058 } 059 return false; 060 } 061 062 public boolean isAnonymous() { 063 final String className = C.toString(); 064 final String innerName = className.substring(className.lastIndexOf('$') + 1); 065 return Character.isDigit(innerName.charAt(0)); 066 } 067 068 @Override 069 public String toString() { 070 return C.toString(); 071 } 072 073 } 074 075 private final Set<IcTuple> innerClasses = new TreeSet<>(); 076 private final CpBands cpBands; 077 private int bit16Count; 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), cpBands.getCPUtf8(innerName)); 095 final boolean added = innerClasses.add(icTuple); 096 if (added) { 097 bit16Count++; 098 addToMap(outerName, icTuple); 099 } 100 } 101 } else { 102 final IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null); 103 addToMap(getOuter(name), innerClass); 104 innerClasses.add(innerClass); 105 } 106 } 107 108 private void addToMap(final String outerName, final IcTuple icTuple) { 109 List<IcTuple> tuples = outerToInner.get(outerName); 110 if (tuples == null) { 111 tuples = new ArrayList<>(); 112 outerToInner.put(outerName, tuples); 113 } else { 114 for (final IcTuple tuple : tuples) { 115 if (icTuple.equals(tuple)) { 116 return; 117 } 118 } 119 } 120 tuples.add(icTuple); 121 } 122 123 /** 124 * 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 125 * while classes were being read. 126 */ 127 public void finaliseBands() { 128 segmentHeader.setIc_count(innerClasses.size()); 129 } 130 131 public IcTuple getIcTuple(final CPClass inner) { 132 for (final IcTuple icTuple : innerClasses) { 133 if (icTuple.C.equals(inner)) { 134 return icTuple; 135 } 136 } 137 return null; 138 } 139 140 public List<IcTuple> getInnerClassesForOuter(final String outerClassName) { 141 return outerToInner.get(outerClassName); 142 } 143 144 private String getOuter(final String name) { 145 return name.substring(0, name.lastIndexOf('$')); 146 } 147 148 private boolean namesArePredictable(final String name, final String outerName, final String innerName) { 149 // TODO: Could be multiple characters, not just $ 150 return name.equals(outerName + '$' + innerName) && innerName.indexOf('$') == -1; 151 } 152 153 @Override 154 public void pack(final OutputStream outputStream) throws IOException, Pack200Exception { 155 PackingUtils.log("Writing internal class bands..."); 156 final int[] ic_this_class = new int[innerClasses.size()]; 157 final int[] ic_flags = new int[innerClasses.size()]; 158 final int[] ic_outer_class = new int[bit16Count]; 159 final int[] ic_name = new int[bit16Count]; 160 161 int index2 = 0; 162 final List<IcTuple> innerClassesList = new ArrayList<>(innerClasses); 163 for (int i = 0; i < ic_this_class.length; i++) { 164 final IcTuple icTuple = innerClassesList.get(i); 165 ic_this_class[i] = icTuple.C.getIndex(); 166 ic_flags[i] = icTuple.F; 167 if ((icTuple.F & 1 << 16) != 0) { 168 ic_outer_class[index2] = icTuple.C2 == null ? 0 : icTuple.C2.getIndex() + 1; 169 ic_name[index2] = icTuple.N == null ? 0 : icTuple.N.getIndex() + 1; 170 index2++; 171 } 172 } 173 byte[] encodedBand = encodeBandInt("ic_this_class", ic_this_class, Codec.UDELTA5); 174 outputStream.write(encodedBand); 175 PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_this_class[" + ic_this_class.length + "]"); 176 177 encodedBand = encodeBandInt("ic_flags", ic_flags, Codec.UNSIGNED5); 178 outputStream.write(encodedBand); 179 PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_flags[" + ic_flags.length + "]"); 180 181 encodedBand = encodeBandInt("ic_outer_class", ic_outer_class, Codec.DELTA5); 182 outputStream.write(encodedBand); 183 PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_outer_class[" + ic_outer_class.length + "]"); 184 185 encodedBand = encodeBandInt("ic_name", ic_name, Codec.DELTA5); 186 outputStream.write(encodedBand); 187 PackingUtils.log("Wrote " + encodedBand.length + " bytes from ic_name[" + ic_name.length + "]"); 188 } 189 190}