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}