912c90c2创建于 2025年7月3日历史提交
/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2025-2025. All rights reserved.
 */

package brotli4cj

internal import std.collection.*
internal import std.io.*
internal import std.sync.Mutex

/**
 * Base class for OutputStream / Channel implementations.
 */
public open class Encoder {
    private let destination: BufferedOutputStream<OutputStream>

    private let dictionaries: ArrayList<PreparedDictionary>

    private let encoder: Wrapper

    private var buffer: ?ByteBuffer = None

    let inputBuffer: ByteBuffer

    var closed: Bool = false

    public init(destination: BufferedOutputStream<OutputStream>, params: Encoder_Parameters, inputBufferSize: Int64) {
        if (inputBufferSize <= 0) {
            throw IllegalArgumentException("buffer size must be positive")
        }

        this.dictionaries = ArrayList<PreparedDictionary>()
        this.destination = destination
        this.encoder = Wrapper(inputBufferSize, params.quality, params.lgwin, params.mode)
        this.inputBuffer = this.encoder.getInputBuffer()
    }

    private func fail(message: String): Unit {
        try {
            close()
        } catch (ex: IOException) { }
        throw IOException(message)
    }

    public func attachDictionary(dictionary: PreparedDictionary): Unit {
        if (!encoder.attachDictionary(dictionary.getData())) {
            fail("failed to attach dictionary")
        }
        dictionaries.add(dictionary)
    }

  /**
   * @param force repeat pushing until all output is consumed
   * @return true if all encoder output is consumed
   */
    func pushOutput(force: Bool): Bool {
        while (buffer.isSome()) {
            if (buffer.getOrThrow().remainLength > 0) {
                destination.write(readToEnd(buffer.getOrThrow()))
            }
            if (!(buffer.getOrThrow().remainLength > 0)) {
                buffer = None
            } else if (!force) {
                return false
            }
        }
        return true
    }

    public func encode(op: Operation): Bool {
        var limit = inputBuffer.capacity
        let force = !(op == Operation.PROCESS)
        if (force) {
            limit = inputBuffer.position
        } else if (inputBuffer.capacity - inputBuffer.position > 0) {
            return true
        }
        var hasInput = true
        while (true) {
            if (!encoder.isSuccess()) {
                fail("encoding failed")
            } else if (!pushOutput(force)) {
                return false
            } else if (encoder.hasMoreOutput()) {
                let bytes = encoder.pull()
                buffer = ByteBuffer(bytes)
            } else if (encoder.hasRemainingInput()) {
                encoder.push(op, 0)
            } else if (hasInput) {
                encoder.push(op, limit)
                hasInput = false
            } else {
                inputBuffer.clear()
                return true
            }
        }
        throw IOException()
    }

    public func flush(): Unit {
        encode(Operation.FLUSH)
    }

    public func close(): Unit {
        if (closed) {
            return
        }
        closed = true
        try {
            encode(Operation.FINISH)
        } finally {
            encoder.destroy()
            destination.flush()
        }
    }

    public static func compress(data: Array<UInt8>, offset: Int64, length: Int64, params: Encoder_Parameters): Array<UInt8> {
        if (length == 0) {
            let empty = Array<UInt8>(1) { _ => 0 }
            empty[0] = 6
            return empty
        }
        let encoder = Wrapper(length, params.quality, params.lgwin, params.mode)
        let output = ArrayList<Array<UInt8>>()
        var totalOutputSize = 0
        try {
            encoder.getInputBuffer().write(data[offset..offset+length])
            encoder.push(Operation.FINISH, length)
            while (true) {
                if (!encoder.isSuccess()) {
                    throw IOException("encoding failed")
                } else if (encoder.hasMoreOutput()) {
                    let bytes: Array<UInt8> = encoder.pull()
                    output.add(bytes)
                    totalOutputSize += Int64(bytes.size)
                } else if (!encoder.isFinished()) {
                    encoder.push(Operation.FINISH, 0)
                } else {
                    break
                }
            }
        } finally {
            encoder.destroy()
        }
        if (Int64(output.size) == 1) {
            return output[0]
        }
        let result = Array<UInt8>(Int64(totalOutputSize)) { _ => 0 }
        var resultOffset = 0
        for (chunk in output) {
            chunk.copyTo(result, 0, resultOffset, chunk.size)
            resultOffset += Int64(chunk.size)
        }
        return result
    }

    public static func compress(data: Array<UInt8>, params: Encoder_Parameters): Array<UInt8> {
        return compress(data, 0, Int64(data.size), params)
    }

    public static func compress(data: Array<UInt8>): Array<UInt8> {
        return compress(data, Encoder_Parameters())
    }

    public static func compress(data: Array<UInt8>, offset: Int64, length: Int64): Array<UInt8> {
        return compress(data, offset, length, Encoder_Parameters())
    }

  /**
   * Prepares raw or serialized dictionary for being used by encoder.
   *
   * @param dictionary raw / serialized dictionary data; MUST be direct
   * @param sharedDictionaryType dictionary data type
   */
    public static func prepareDictionary(dictionary: ByteBuffer, sharedDictionaryType: Int64): PreparedDictionary {
        return EncoderJNI.prepareDictionary(dictionary, sharedDictionaryType)
    }
}


public enum EMODE {
    | GENERIC 

    | TEXT

    | FONT

    public static func allValues(): Array<EMODE> {
        return [EMODE.GENERIC, EMODE.TEXT, EMODE.FONT]
    }

    public func ordinal(): Int64 {
        match (this) {
            case GENERIC => 0
            case TEXT => 1
            case FONT => 2
        }
    }

    public static func valueOf(value: Int64): EMODE {
        return allValues()[value]
    }

    public operator func ==(b: EMODE): Bool {
        match ((this, b)) {
            case (GENERIC, GENERIC) => true
            case (TEXT, TEXT) => true
            case (FONT, FONT) => true
            case _ => false
        }
    }

}

public class Encoder_Parameters {
    var quality = -1

    var lgwin = -1

    var mode: ?EMODE = Option<EMODE>.None

    public init() {

    }
    public init(quality: Int64, lgwin: Int64, mode: EMODE) {
        this.quality = quality
        this.lgwin = lgwin
        this.mode = mode
    }

    public init(other: Encoder_Parameters) {
        this.quality = other.quality
        this.lgwin = other.lgwin
        this.mode = other.mode
    }

    /**
     * Setup encoder quality.
     *
     * @param quality compression quality, or -1 for default
     */
    public func setQuality(quality: Int64): Encoder_Parameters {
        if (quality < -1 || quality > 11) {
            throw IllegalArgumentException("quality should be in range [0, 11], or -1")
        }
        this.quality = quality
        return this
    }

    /**
     * Setup encoder window size.
     *
     * @param lgwin log2(LZ window size), or -1 for default
     */
    public func setWindow(lgwin: Int64): Encoder_Parameters {
        if ((lgwin != -1) && ((lgwin < 10) || (lgwin > 24))) {
            throw IllegalArgumentException("lgwin should be in range [10, 24], or -1")
        }
        this.lgwin = lgwin
        return this
    }

    /**
     * Setup encoder compression mode.
     *
     * @param mode compression mode, or {@code null} for default
     */
    public func setMode(mode: EMODE): Encoder_Parameters {
        this.mode = mode
        return this
    }
}