001/*
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2026, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v2.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.rolling.helper;
015
016import java.util.concurrent.ExecutorService;
017import java.util.concurrent.Future;
018
019import ch.qos.logback.core.rolling.RolloverFailure;
020import ch.qos.logback.core.spi.ContextAwareBase;
021import ch.qos.logback.core.util.DynamicClassLoadingException;
022import ch.qos.logback.core.util.IncompatibleClassException;
023
024import static ch.qos.logback.core.rolling.helper.CompressionMode.GZ_SUFFIX;
025import static ch.qos.logback.core.rolling.helper.CompressionMode.XZ_SUFFIX;
026import static ch.qos.logback.core.rolling.helper.CompressionMode.ZIP_SUFFIX;
027import static ch.qos.logback.core.util.OptionHelper.instantiateByClassName;
028
029/**
030 * The <code>Compression</code> class implements ZIP and GZ file
031 * compression/decompression methods.
032 *
033 * @author Ceki G&uuml;lc&uuml;
034 */
035public class Compressor extends ContextAwareBase {
036
037    public static final String COULD_NOT_OBTAIN_COMPRESSION_STRATEGY_MESSAGE = "Could not obtain compression strategy";
038    public static final String XZ_COMPRESSION_STRATEGY_CLASS_NAME = "ch.qos.logback.core.rolling.helper.XZCompressionStrategy";
039
040    final CompressionMode compressionMode;
041
042    static final int BUFFER_SIZE = 8192;
043
044    public Compressor(CompressionMode compressionMode) {
045        this.compressionMode = compressionMode;
046    }
047
048    /**
049     * @param originalFileName
050     * @param compressedFileName
051     * @param innerEntryName       The name of the file within the zip file. Use for
052     *                             ZIP compression.
053     */
054    public void compress(String originalFileName, String compressedFileName, String innerEntryName) {
055        CompressionStrategy compressionStrategy = makeCompressionStrategy(compressionMode);
056        if (compressionStrategy == null) {
057            addWarn(COULD_NOT_OBTAIN_COMPRESSION_STRATEGY_MESSAGE);
058            return;
059        }
060        compressionStrategy.setContext(getContext());
061        compressionStrategy.compress(originalFileName, compressedFileName, innerEntryName);
062    }
063
064    CompressionStrategy makeCompressionStrategy(CompressionMode compressionMode) {
065        switch (compressionMode) {
066        case GZ:
067            return new GZCompressionStrategy();
068        case ZIP:
069            return new ZipCompressionStrategy();
070        case XZ:
071            return dynamicInstantiation(XZ_COMPRESSION_STRATEGY_CLASS_NAME);
072        case NONE:
073            throw new UnsupportedOperationException("compress method called in NONE compression mode");
074        default:
075            return null;
076        }
077    }
078
079    private CompressionStrategy dynamicInstantiation(String className) {
080        try {
081            return (CompressionStrategy) instantiateByClassName(className, CompressionStrategy.class, getContext());
082        } catch (IncompatibleClassException | DynamicClassLoadingException e) {
083            addError("Could not instantiate " + className, e);
084            return null;
085        }
086    }
087
088    static public String computeFileNameStrWithoutCompSuffix(String fileNamePatternStr, CompressionMode compressionMode) {
089        int len = fileNamePatternStr.length();
090        switch (compressionMode) {
091        case GZ:
092            if (fileNamePatternStr.endsWith(GZ_SUFFIX))
093                return fileNamePatternStr.substring(0, len - GZ_SUFFIX.length());
094            else
095                return fileNamePatternStr;
096        case ZIP:
097            if (fileNamePatternStr.endsWith(ZIP_SUFFIX))
098                return fileNamePatternStr.substring(0, len - ZIP_SUFFIX.length());
099            else
100                return fileNamePatternStr;
101        case XZ:
102            if (fileNamePatternStr.endsWith(XZ_SUFFIX))
103                return fileNamePatternStr.substring(0, len - XZ_SUFFIX.length());
104            else
105                return fileNamePatternStr;
106        case NONE:
107            return fileNamePatternStr;
108        }
109        throw new IllegalStateException("Execution should not reach this point");
110    }
111
112    @Override
113    public String toString() {
114        return this.getClass().getName();
115    }
116
117    public Future<?> asyncCompress(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) throws RolloverFailure {
118        CompressionRunnable runnable = new CompressionRunnable(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
119        ExecutorService executorService = context.getExecutorService();
120        Future<?> future = executorService.submit(runnable);
121        return future;
122    }
123
124    class CompressionRunnable implements Runnable {
125        final String nameOfFile2Compress;
126        final String nameOfCompressedFile;
127        final String innerEntryName;
128
129        public CompressionRunnable(String nameOfFile2Compress, String nameOfCompressedFile, String innerEntryName) {
130            this.nameOfFile2Compress = nameOfFile2Compress;
131            this.nameOfCompressedFile = nameOfCompressedFile;
132            this.innerEntryName = innerEntryName;
133        }
134
135        public void run() {
136
137            Compressor.this.compress(nameOfFile2Compress, nameOfCompressedFile, innerEntryName);
138        }
139    }
140
141}