    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one
    003 * or more contributor license agreements.  See the NOTICE file
    004 * distributed with this work for additional information
    005 * regarding copyright ownership.  The ASF licenses this file
    006 * to you under the Apache License, Version 2.0 (the
    007 * "License"); you may not use this file except in compliance
    008 * with the License.  You may obtain a copy of the License at
    009 *
    010 *   https://www.apache.org/licenses/LICENSE-2.0
    011 *
    012 * Unless required by applicable law or agreed to in writing,
    013 * software distributed under the License is distributed on an
    014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015 * KIND, either express or implied.  See the License for the
    016 * specific language governing permissions and limitations
    017 * under the License.
    018 */
    019package org.apache.commons.compress.harmony.pack200;
    020
    021import java.nio.file.FileSystems;
    022import java.util.ArrayList;
    023import java.util.HashMap;
    024import java.util.List;
    025import java.util.Map;
    026import java.util.Map.Entry;
    027
    028import org.objectweb.asm.Attribute;
    029
    030/**
    031 * Manages the various options available for pack200.
    032 */
    033public class PackingOptions {
    034
    035    private static final Attribute[] EMPTY_ATTRIBUTE_ARRAY = {};
    036    public static final long SEGMENT_LIMIT = 1_000_000L;
    037    public static final String STRIP = "strip";
    038    public static final String ERROR = "error";
    039    public static final String PASS = "pass";
    040    public static final String KEEP = "keep";
    041
    042    // All options are initially set to their defaults
    043    private boolean gzip = true;
    044    private boolean stripDebug;
    045    private boolean keepFileOrder = true;
    046    private long segmentLimit = SEGMENT_LIMIT;
    047    private int effort = 5;
    048    private String deflateHint = KEEP;
    049    private String modificationTime = KEEP;
    050    private final List<String> passFiles = new ArrayList<>();
    051    private String unknownAttributeAction = PASS;
    052    private final Map<String, String> classAttributeActions = new HashMap<>();
    053    private final Map<String, String> fieldAttributeActions = new HashMap<>();
    054    private final Map<String, String> methodAttributeActions = new HashMap<>();
    055    private final Map<String, String> codeAttributeActions = new HashMap<>();
    056    private boolean verbose;
    057    private String logFile;
    058
    059    private Attribute[] unknownAttributeTypes;
    060
    061    public void addClassAttributeAction(final String attributeName, final String action) {
    062        classAttributeActions.put(attributeName, action);
    063    }
    064
    065    public void addCodeAttributeAction(final String attributeName, final String action) {
    066        codeAttributeActions.put(attributeName, action);
    067    }
    068
    069    public void addFieldAttributeAction(final String attributeName, final String action) {
    070        fieldAttributeActions.put(attributeName, action);
    071    }
    072
    073    public void addMethodAttributeAction(final String attributeName, final String action) {
    074        methodAttributeActions.put(attributeName, action);
    075    }
    076
    077    private void addOrUpdateAttributeActions(final List<Attribute> prototypes, final Map<String, String> attributeActions, final int tag) {
    078        if (attributeActions != null && attributeActions.size() > 0) {
    079            NewAttribute newAttribute;
    080            for (final Entry<String, String> entry : attributeActions.entrySet()) {
    081                final String name = entry.getKey();
    082                final String action = entry.getValue();
    083                boolean prototypeExists = false;
    084                for (final Object prototype : prototypes) {
    085                    newAttribute = (NewAttribute) prototype;
    086                    if (newAttribute.type.equals(name)) {
    087                        // if the attribute exists, update its context
    088                        newAttribute.addContext(tag);
    089                        prototypeExists = true;
    090                        break;
    091                    }
    092                }
    093                // if no attribute is found, add a new attribute
    094                if (!prototypeExists) {
    095                    switch (action) {
    096                    case ERROR:
    097                        newAttribute = new NewAttribute.ErrorAttribute(name, tag);
    098                        break;
    099                    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}