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.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.objectweb.asm.Attribute;
025
026/**
027 * Utility class to manage the various options available for pack200.
028 */
029public class PackingOptions {
030
031    private static final Attribute[] EMPTY_ATTRIBUTE_ARRAY = {};
032    public static final long SEGMENT_LIMIT = 1_000_000L;
033    public static final String STRIP = "strip";
034    public static final String ERROR = "error";
035    public static final String PASS = "pass";
036    public static final String KEEP = "keep";
037
038    // All options are initially set to their defaults
039    private boolean gzip = true;
040    private boolean stripDebug;
041    private boolean keepFileOrder = true;
042    private long segmentLimit = SEGMENT_LIMIT;
043    private int effort = 5;
044    private String deflateHint = KEEP;
045    private String modificationTime = KEEP;
046    private final List<String> passFiles = new ArrayList<>();
047    private String unknownAttributeAction = PASS;
048    private final Map<String, String> classAttributeActions = new HashMap<>();
049    private final Map<String, String> fieldAttributeActions = new HashMap<>();
050    private final Map<String, String> methodAttributeActions = new HashMap<>();
051    private final Map<String, String> codeAttributeActions = new HashMap<>();
052    private boolean verbose;
053    private String logFile;
054
055    private Attribute[] unknownAttributeTypes;
056
057    public void addClassAttributeAction(final String attributeName, final String action) {
058        classAttributeActions.put(attributeName, action);
059    }
060
061    public void addCodeAttributeAction(final String attributeName, final String action) {
062        codeAttributeActions.put(attributeName, action);
063    }
064
065    public void addFieldAttributeAction(final String attributeName, final String action) {
066        fieldAttributeActions.put(attributeName, action);
067    }
068
069    public void addMethodAttributeAction(final String attributeName, final String action) {
070        methodAttributeActions.put(attributeName, action);
071    }
072
073    private void addOrUpdateAttributeActions(final List<Attribute> prototypes, final Map<String, String> attributeActions, final int tag) {
074        if (attributeActions != null && attributeActions.size() > 0) {
075            NewAttribute newAttribute;
076            for (final String name : attributeActions.keySet()) {
077                final String action = attributeActions.get(name);
078                boolean prototypeExists = false;
079                for (final Object prototype : prototypes) {
080                    newAttribute = (NewAttribute) prototype;
081                    if (newAttribute.type.equals(name)) {
082                        // if the attribute exists, update its context
083                        newAttribute.addContext(tag);
084                        prototypeExists = true;
085                        break;
086                    }
087                }
088                // if no attribute is found, add a new attribute
089                if (!prototypeExists) {
090                    if (ERROR.equals(action)) {
091                        newAttribute = new NewAttribute.ErrorAttribute(name, tag);
092                    } else if (STRIP.equals(action)) {
093                        newAttribute = new NewAttribute.StripAttribute(name, tag);
094                    } else if (PASS.equals(action)) {
095                        newAttribute = new NewAttribute.PassAttribute(name, tag);
096                    } else {
097                        newAttribute = new NewAttribute(name, action, tag);
098                    }
099                    prototypes.add(newAttribute);
100                }
101            }
102        }
103    }
104
105    /**
106     * Tell the compressor to pass the file with the given name, or if the name is a directory name all files under that
107     * directory will be passed.
108     *
109     * @param passFileName the file name
110     */
111    public void addPassFile(String passFileName) {
112        String fileSeparator = System.getProperty("file.separator");
113        if (fileSeparator.equals("\\")) {
114            // Need to escape backslashes for replaceAll(), which uses regex
115            fileSeparator += "\\";
116        }
117        passFileName = passFileName.replaceAll(fileSeparator, "/");
118        passFiles.add(passFileName);
119    }
120
121    public String getDeflateHint() {
122        return deflateHint;
123    }
124
125    public int getEffort() {
126        return effort;
127    }
128
129    public String getLogFile() {
130        return logFile;
131    }
132
133    public String getModificationTime() {
134        return modificationTime;
135    }
136
137    private String getOrDefault(final Map<String, String> map, final String type, final String defaultValue) {
138        return map == null ? defaultValue : map.getOrDefault(type, defaultValue);
139    }
140
141    public long getSegmentLimit() {
142        return segmentLimit;
143    }
144
145    public String getUnknownAttributeAction() {
146        return unknownAttributeAction;
147    }
148
149    public Attribute[] getUnknownAttributePrototypes() {
150        if (unknownAttributeTypes == null) {
151            final List<Attribute> prototypes = new ArrayList<>();
152            addOrUpdateAttributeActions(prototypes, classAttributeActions, AttributeDefinitionBands.CONTEXT_CLASS);
153            addOrUpdateAttributeActions(prototypes, methodAttributeActions, AttributeDefinitionBands.CONTEXT_METHOD);
154            addOrUpdateAttributeActions(prototypes, fieldAttributeActions, AttributeDefinitionBands.CONTEXT_FIELD);
155            addOrUpdateAttributeActions(prototypes, codeAttributeActions, AttributeDefinitionBands.CONTEXT_CODE);
156            unknownAttributeTypes = prototypes.toArray(EMPTY_ATTRIBUTE_ARRAY);
157        }
158        return unknownAttributeTypes;
159    }
160
161    public String getUnknownClassAttributeAction(final String type) {
162        return getOrDefault(classAttributeActions, type, unknownAttributeAction);
163    }
164
165    public String getUnknownCodeAttributeAction(final String type) {
166        return getOrDefault(codeAttributeActions, type, unknownAttributeAction);
167    }
168
169    public String getUnknownFieldAttributeAction(final String type) {
170        return getOrDefault(fieldAttributeActions, type, unknownAttributeAction);
171    }
172
173    public String getUnknownMethodAttributeAction(final String type) {
174        return getOrDefault(methodAttributeActions, type, unknownAttributeAction);
175    }
176
177    public boolean isGzip() {
178        return gzip;
179    }
180
181    public boolean isKeepDeflateHint() {
182        return KEEP.equals(deflateHint);
183    }
184
185    public boolean isKeepFileOrder() {
186        return keepFileOrder;
187    }
188
189    public boolean isPassFile(final String passFileName) {
190        for (String pass : passFiles) {
191            if (passFileName.equals(pass)) {
192                return true;
193            }
194            if (!pass.endsWith(".class")) { // a whole directory is
195                // passed
196                if (!pass.endsWith("/")) {
197                    // Make sure we don't get any false positives (e.g.
198                    // exclude "org/apache/harmony/pack" should not match
199                    // files under "org/apache/harmony/pack200/")
200                    pass = pass + "/";
201                }
202                return passFileName.startsWith(pass);
203            }
204        }
205        return false;
206    }
207
208    public boolean isStripDebug() {
209        return stripDebug;
210    }
211
212    public boolean isVerbose() {
213        return verbose;
214    }
215
216    public void removePassFile(final String passFileName) {
217        passFiles.remove(passFileName);
218    }
219
220    public void setDeflateHint(final String deflateHint) {
221        if (!KEEP.equals(deflateHint) && !"true".equals(deflateHint) && !"false".equals(deflateHint)) {
222            throw new IllegalArgumentException("Bad argument: -H " + deflateHint + " ? deflate hint should be either true, false or keep (default)");
223        }
224        this.deflateHint = deflateHint;
225    }
226
227    /**
228     * Sets the compression effort level (0-9, equivalent to -E command line option)
229     *
230     * @param effort the compression effort level, 0-9.
231     */
232    public void setEffort(final int effort) {
233        this.effort = effort;
234    }
235
236    public void setGzip(final boolean gzip) {
237        this.gzip = gzip;
238    }
239
240    public void setKeepFileOrder(final boolean keepFileOrder) {
241        this.keepFileOrder = keepFileOrder;
242    }
243
244    public void setLogFile(final String logFile) {
245        this.logFile = logFile;
246    }
247
248    public void setModificationTime(final String modificationTime) {
249        if (!KEEP.equals(modificationTime) && !"latest".equals(modificationTime)) {
250            throw new IllegalArgumentException("Bad argument: -m " + modificationTime + " ? transmit modtimes should be either latest or keep (default)");
251        }
252        this.modificationTime = modificationTime;
253    }
254
255    public void setQuiet(final boolean quiet) {
256        this.verbose = !quiet;
257    }
258
259    /**
260     * Sets the segment limit (equivalent to -S command line option)
261     *
262     * @param segmentLimit - the limit in bytes
263     */
264    public void setSegmentLimit(final long segmentLimit) {
265        this.segmentLimit = segmentLimit;
266    }
267
268    /**
269     * Sets strip debug attributes. If true, all debug attributes (i.e. LineNumberTable, SourceFile, LocalVariableTable and
270     * LocalVariableTypeTable attributes) are stripped when reading the input class files and not included in the output
271     * archive.
272     *
273     * @param stripDebug If true, all debug attributes.
274     */
275    public void setStripDebug(final boolean stripDebug) {
276        this.stripDebug = stripDebug;
277    }
278
279    /**
280     * Sets the compressor behavior when an unknown attribute is encountered.
281     *
282     * @param unknownAttributeAction - the action to perform
283     */
284    public void setUnknownAttributeAction(final String unknownAttributeAction) {
285        this.unknownAttributeAction = unknownAttributeAction;
286        if (!PASS.equals(unknownAttributeAction) && !ERROR.equals(unknownAttributeAction) && !STRIP.equals(unknownAttributeAction)) {
287            throw new IllegalArgumentException("Incorrect option for -U, " + unknownAttributeAction);
288        }
289    }
290
291    public void setVerbose(final boolean verbose) {
292        this.verbose = verbose;
293    }
294
295}