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}