    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}