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.nio.file.FileSystems;
22  import java.util.ArrayList;
23  import java.util.HashMap;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Map.Entry;
27  
28  import org.objectweb.asm.Attribute;
29  
30  /**
31   * Manages the various options available for pack200.
32   */
33  public class PackingOptions {
34  
35      private static final Attribute[] EMPTY_ATTRIBUTE_ARRAY = {};
36      public static final long SEGMENT_LIMIT = 1_000_000L;
37      public static final String STRIP = "strip";
38      public static final String ERROR = "error";
39      public static final String PASS = "pass";
40      public static final String KEEP = "keep";
41  
42      // All options are initially set to their defaults
43      private boolean gzip = true;
44      private boolean stripDebug;
45      private boolean keepFileOrder = true;
46      private long segmentLimit = SEGMENT_LIMIT;
47      private int effort = 5;
48      private String deflateHint = KEEP;
49      private String modificationTime = KEEP;
50      private final List<String> passFiles = new ArrayList<>();
51      private String unknownAttributeAction = PASS;
52      private final Map<String, String> classAttributeActions = new HashMap<>();
53      private final Map<String, String> fieldAttributeActions = new HashMap<>();
54      private final Map<String, String> methodAttributeActions = new HashMap<>();
55      private final Map<String, String> codeAttributeActions = new HashMap<>();
56      private boolean verbose;
57      private String logFile;
58  
59      private Attribute[] unknownAttributeTypes;
60  
61      public void addClassAttributeAction(final String attributeName, final String action) {
62          classAttributeActions.put(attributeName, action);
63      }
64  
65      public void addCodeAttributeAction(final String attributeName, final String action) {
66          codeAttributeActions.put(attributeName, action);
67      }
68  
69      public void addFieldAttributeAction(final String attributeName, final String action) {
70          fieldAttributeActions.put(attributeName, action);
71      }
72  
73      public void addMethodAttributeAction(final String attributeName, final String action) {
74          methodAttributeActions.put(attributeName, action);
75      }
76  
77      private void addOrUpdateAttributeActions(final List<Attribute> prototypes, final Map<String, String> attributeActions, final int tag) {
78          if (attributeActions != null && attributeActions.size() > 0) {
79              NewAttribute newAttribute;
80              for (final Entry<String, String> entry : attributeActions.entrySet()) {
81                  final String name = entry.getKey();
82                  final String action = entry.getValue();
83                  boolean prototypeExists = false;
84                  for (final Object prototype : prototypes) {
85                      newAttribute = (NewAttribute) prototype;
86                      if (newAttribute.type.equals(name)) {
87                          // if the attribute exists, update its context
88                          newAttribute.addContext(tag);
89                          prototypeExists = true;
90                          break;
91                      }
92                  }
93                  // if no attribute is found, add a new attribute
94                  if (!prototypeExists) {
95                      switch (action) {
96                      case ERROR:
97                          newAttribute = new NewAttribute.ErrorAttribute(name, tag);
98                          break;
99                      case STRIP:
100                         newAttribute = new NewAttribute.StripAttribute(name, tag);
101                         break;
102                     case PASS:
103                         newAttribute = new NewAttribute.PassAttribute(name, tag);
104                         break;
105                     default:
106                         newAttribute = new NewAttribute(name, action, tag);
107                         break;
108                     }
109                     prototypes.add(newAttribute);
110                 }
111             }
112         }
113     }
114 
115     /**
116      * Tell the compressor to pass the file with the given name, or if the name is a directory name all files under that directory will be passed.
117      *
118      * @param passFileName the file name
119      */
120     public void addPassFile(final String passFileName) {
121         String fileSeparator = FileSystems.getDefault().getSeparator();
122         if (fileSeparator.equals("\\")) {
123             // Need to escape backslashes for replaceAll(), which uses regex
124             fileSeparator += "\\";
125         }
126         passFiles.add(passFileName.replaceAll(fileSeparator, "/"));
127     }
128 
129     public String getDeflateHint() {
130         return deflateHint;
131     }
132 
133     public int getEffort() {
134         return effort;
135     }
136 
137     public String getLogFile() {
138         return logFile;
139     }
140 
141     public String getModificationTime() {
142         return modificationTime;
143     }
144 
145     private String getOrDefault(final Map<String, String> map, final String type, final String defaultValue) {
146         return map == null ? defaultValue : map.getOrDefault(type, defaultValue);
147     }
148 
149     public long getSegmentLimit() {
150         return segmentLimit;
151     }
152 
153     public String getUnknownAttributeAction() {
154         return unknownAttributeAction;
155     }
156 
157     public Attribute[] getUnknownAttributePrototypes() {
158         if (unknownAttributeTypes == null) {
159             final List<Attribute> prototypes = new ArrayList<>();
160             addOrUpdateAttributeActions(prototypes, classAttributeActions, AttributeDefinitionBands.CONTEXT_CLASS);
161             addOrUpdateAttributeActions(prototypes, methodAttributeActions, AttributeDefinitionBands.CONTEXT_METHOD);
162             addOrUpdateAttributeActions(prototypes, fieldAttributeActions, AttributeDefinitionBands.CONTEXT_FIELD);
163             addOrUpdateAttributeActions(prototypes, codeAttributeActions, AttributeDefinitionBands.CONTEXT_CODE);
164             unknownAttributeTypes = prototypes.toArray(EMPTY_ATTRIBUTE_ARRAY);
165         }
166         return unknownAttributeTypes;
167     }
168 
169     public String getUnknownClassAttributeAction(final String type) {
170         return getOrDefault(classAttributeActions, type, unknownAttributeAction);
171     }
172 
173     public String getUnknownCodeAttributeAction(final String type) {
174         return getOrDefault(codeAttributeActions, type, unknownAttributeAction);
175     }
176 
177     public String getUnknownFieldAttributeAction(final String type) {
178         return getOrDefault(fieldAttributeActions, type, unknownAttributeAction);
179     }
180 
181     public String getUnknownMethodAttributeAction(final String type) {
182         return getOrDefault(methodAttributeActions, type, unknownAttributeAction);
183     }
184 
185     public boolean isGzip() {
186         return gzip;
187     }
188 
189     public boolean isKeepDeflateHint() {
190         return KEEP.equals(deflateHint);
191     }
192 
193     public boolean isKeepFileOrder() {
194         return keepFileOrder;
195     }
196 
197     public boolean isPassFile(final String passFileName) {
198         for (String pass : passFiles) {
199             if (passFileName.equals(pass)) {
200                 return true;
201             }
202             if (!pass.endsWith(".class")) { // a whole directory is
203                 // passed
204                 if (!pass.endsWith("/")) {
205                     // Make sure we don't get any false positives (for example
206                     // exclude "org/apache/harmony/pack" should not match
207                     // files under "org/apache/harmony/pack200/")
208                     pass += "/";
209                 }
210                 return passFileName.startsWith(pass);
211             }
212         }
213         return false;
214     }
215 
216     public boolean isStripDebug() {
217         return stripDebug;
218     }
219 
220     public boolean isVerbose() {
221         return verbose;
222     }
223 
224     public void removePassFile(final String passFileName) {
225         passFiles.remove(passFileName);
226     }
227 
228     public void setDeflateHint(final String deflateHint) {
229         if (!KEEP.equals(deflateHint) && !"true".equals(deflateHint) && !"false".equals(deflateHint)) {
230             throw new IllegalArgumentException("Bad argument: -H " + deflateHint + " ? deflate hint should be either true, false or keep (default)");
231         }
232         this.deflateHint = deflateHint;
233     }
234 
235     /**
236      * Sets the compression effort level (0-9, equivalent to -E command line option)
237      *
238      * @param effort the compression effort level, 0-9.
239      */
240     public void setEffort(final int effort) {
241         this.effort = effort;
242     }
243 
244     public void setGzip(final boolean gzip) {
245         this.gzip = gzip;
246     }
247 
248     public void setKeepFileOrder(final boolean keepFileOrder) {
249         this.keepFileOrder = keepFileOrder;
250     }
251 
252     public void setLogFile(final String logFile) {
253         this.logFile = logFile;
254     }
255 
256     public void setModificationTime(final String modificationTime) {
257         if (!KEEP.equals(modificationTime) && !"latest".equals(modificationTime)) {
258             throw new IllegalArgumentException("Bad argument: -m " + modificationTime + " ? transmit modtimes should be either latest or keep (default)");
259         }
260         this.modificationTime = modificationTime;
261     }
262 
263     public void setQuiet(final boolean quiet) {
264         this.verbose = !quiet;
265     }
266 
267     /**
268      * Sets the segment limit (equivalent to -S command line option)
269      *
270      * @param segmentLimit   the limit in bytes
271      */
272     public void setSegmentLimit(final long segmentLimit) {
273         this.segmentLimit = segmentLimit;
274     }
275 
276     /**
277      * Sets strip debug attributes. If true, all debug attributes (i.e. LineNumberTable, SourceFile, LocalVariableTable and LocalVariableTypeTable attributes)
278      * are stripped when reading the input class files and not included in the output archive.
279      *
280      * @param stripDebug If true, all debug attributes.
281      */
282     public void setStripDebug(final boolean stripDebug) {
283         this.stripDebug = stripDebug;
284     }
285 
286     /**
287      * Sets the compressor behavior when an unknown attribute is encountered.
288      *
289      * @param unknownAttributeAction   the action to perform
290      */
291     public void setUnknownAttributeAction(final String unknownAttributeAction) {
292         this.unknownAttributeAction = unknownAttributeAction;
293         if (!PASS.equals(unknownAttributeAction) && !ERROR.equals(unknownAttributeAction) && !STRIP.equals(unknownAttributeAction)) {
294             throw new IllegalArgumentException("Incorrect option for -U, " + unknownAttributeAction);
295         }
296     }
297 
298     public void setVerbose(final boolean verbose) {
299         this.verbose = verbose;
300     }
301 
302 }