View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.commons.compress.harmony.pack200;
20  
21  import java.io.IOException;
22  import java.io.OutputStream;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  import java.util.Set;
29  import java.util.TreeSet;
30  
31  /**
32   * Inner class bands (corresponds to the {@code ic_bands} set of bands in the pack200 specification)
33   */
34  public class IcBands extends BandSet {
35  
36      static final class IcTuple implements Comparable<IcTuple> {
37  
38          protected CPClass C; // this class
39          protected int F; // flags
40          protected CPClass C2; // outer class
41          protected CPUTF8 N; // name
42  
43          IcTuple(final CPClass C, final int F, final CPClass C2, final CPUTF8 N) {
44              this.C = C;
45              this.F = F;
46              this.C2 = C2;
47              this.N = N;
48          }
49  
50          @Override
51          public int compareTo(final IcTuple arg0) {
52              return C.compareTo(arg0.C);
53          }
54  
55          @Override
56          public boolean equals(final Object o) {
57              if (o instanceof IcTuple) {
58                  final IcTuple icT = (IcTuple) o;
59                  return C.equals(icT.C) && F == icT.F && Objects.equals(C2, icT.C2) && Objects.equals(N, icT.N);
60              }
61              return false;
62          }
63  
64          public boolean isAnonymous() {
65              final String className = C.toString();
66              final String innerName = className.substring(className.lastIndexOf('$') + 1);
67              return Character.isDigit(innerName.charAt(0));
68          }
69  
70          @Override
71          public String toString() {
72              return C.toString();
73          }
74  
75      }
76  
77      private final Set<IcTuple> innerClasses = new TreeSet<>();
78      private final CpBands cpBands;
79      private int bit16Count;
80  
81      private final Map<String, List<IcTuple>> outerToInner = new HashMap<>();
82  
83      public IcBands(final SegmentHeader segmentHeader, final CpBands cpBands, final int effort) {
84          super(effort, segmentHeader);
85          this.cpBands = cpBands;
86      }
87  
88      public void addInnerClass(final String name, final String outerName, final String innerName, int flags) {
89          if (outerName != null || innerName != null) {
90              if (namesArePredictable(name, outerName, innerName)) {
91                  final IcTuple innerClass = new IcTuple(cpBands.getCPClass(name), flags, null, null);
92                  addToMap(outerName, innerClass);
93                  innerClasses.add(innerClass);
94              } else {
95                  flags |= 1 << 16;
96                  final IcTuple icTuple = new IcTuple(cpBands.getCPClass(name), flags, cpBands.getCPClass(outerName), cpBands.getCPUtf8(innerName));
97                  final boolean added = innerClasses.add(icTuple);
98                  if (added) {
99                      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 }