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