All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.permazen.cli.cmd.AbstractKVCommand Maven / Gradle / Ivy

The newest version!

/*
 * Copyright (C) 2015 Archie L. Cobbs. All rights reserved.
 */

package io.permazen.cli.cmd;

import com.google.common.base.Preconditions;

import io.permazen.cli.Session;
import io.permazen.cli.SessionMode;
import io.permazen.cli.parse.Parser;
import io.permazen.util.ByteUtil;

import java.util.regex.Pattern;

public abstract class AbstractKVCommand extends AbstractCommand {

    /**
     * Matches the doubly-quoted C strings returnd by {@link #toCString toCString()}.
     */
    public static final Pattern CSTRING_PATTERN = Pattern.compile(
      "\"([\\x20\\x21\\x23-\\x5b\\x5d-\\x7e]|\\\\([\\\\bftrn\"]|x[\\p{XDigit}]{2}))*\"");

    /**
     * Matches hexadecimal byte strings.
     */
    public static final Pattern HEXBYTES_PATTERN = Pattern.compile("([\\p{XDigit}]{2}){1,}");

    protected AbstractKVCommand(String spec) {
        super(spec);
    }

    @Override
    protected Parser getParser(String typeName) {
        if ("bytes".equals(typeName))
            return new BytesParser();
        return super.getParser(typeName);
    }

    /**
     * Convert a {@code byte[]} array into a double-quoted C-string representation, surrounded by double quotes,
     * with non-ASCII bytes, double-quotes, and backslashes escaped with a backslash.
     *
     * 

* Supported escapes are {@code \\}, {@code \"}, {@code \b}, {@code \f}, {@code \t}, {@code \n}, {@code \r}, and {@code \xNN}. * * @param data byte array * @return C string representation */ public static String toCString(byte[] data) { Preconditions.checkArgument(data != null, "null data"); StringBuilder buf = new StringBuilder(data.length + 4); buf.append('"'); for (byte b : data) { final int ch = b & 0xff; // Handle special escapes switch (ch) { case '\\': case '"': buf.append('\\').append((char)ch); continue; case '\b': buf.append('\\').append('b'); continue; case '\f': buf.append('\\').append('f'); continue; case '\t': buf.append('\\').append('t'); continue; case '\n': buf.append('\\').append('n'); continue; case '\r': buf.append('\\').append('r'); continue; default: break; } // Handle printables if (ch >= 0x20 && ch <= 0x7e) { buf.append((char)ch); continue; } // Escape it using 2 digit hex buf.append('\\'); buf.append('x'); buf.append(Character.forDigit(ch >> 4, 16)); buf.append(Character.forDigit(ch & 0x0f, 16)); } buf.append('"'); return buf.toString(); } /** * Parse a {@code byte[]} array encoded as a double-quoted C-string representation by {@link #toCString toCString()}. * * @param string C string * @return byte array * @throws IllegalArgumentException if {@code string} is malformed */ public static byte[] fromCString(String string) { Preconditions.checkArgument(string != null, "null string"); Preconditions.checkArgument(string.length() >= 2 && string.charAt(0) == '"' && string.charAt(string.length() - 1) == '"', "string is not contained in double quotes"); string = string.substring(1, string.length() - 1); byte[] buf = new byte[string.length()]; int index = 0; for (int i = 0; i < string.length(); i++) { char ch = string.charAt(i); // Handle unescaped characters if (ch != '\\') { Preconditions.checkArgument(ch >= 0x20 && ch <= 0x7e, String.format("illegal character 0x%02x in encoded string", ch & 0xff)); buf[index++] = (byte)ch; continue; } // Get next char Preconditions.checkArgument(++i < string.length(), "illegal trailing '\\' in encoded string"); ch = string.charAt(i); // Check for special escapes switch (ch) { case '"': buf[index++] = (byte)'"'; continue; case '\\': buf[index++] = (byte)'\\'; continue; case 'b': buf[index++] = (byte)'\b'; continue; case 't': buf[index++] = (byte)'\t'; continue; case 'n': buf[index++] = (byte)'\n'; continue; case 'f': buf[index++] = (byte)'\f'; continue; case 'r': buf[index++] = (byte)'\r'; continue; default: break; } // Must be hex escape Preconditions.checkArgument(ch == 'x', "illegal escape sequence '\\" + ch + "' in encoded string"); // Decode hex value Preconditions.checkArgument(i + 2 < string.length(), "illegal truncated '\\x' escape sequence in encoded string"); int value = 0; for (int j = 0; j < 2; j++) { int nibble = Character.digit(string.charAt(++i), 16); Preconditions.checkArgument(nibble != -1, "illegal escape sequence '" + string.substring(i - j - 2, i - j + 2) + "' in encoded string"); assert nibble >= 0 && nibble <= 0xf; value = (value << 4) | nibble; } // Append decoded byte buf[index++] = (byte)value; } if (index < buf.length) { final byte[] newbuf = new byte[index]; System.arraycopy(buf, 0, newbuf, 0, index); buf = newbuf; } return buf; } // BytesParser /** * Parses a {@code byte[]} array as hexadecimal or doubly-quoted "C" style string. * * @see AbstractKVCommand#toCString */ public static class BytesParser implements Parser { @Override public byte[] parse(Session session, String text) { Preconditions.checkArgument(session != null, "null session"); Preconditions.checkArgument(text != null, "null text"); try { return AbstractKVCommand.fromCString(text); } catch (IllegalArgumentException e) { // failed } try { return ByteUtil.parse(text); } catch (IllegalArgumentException e) { // failed } throw new IllegalArgumentException("invalid byte array value"); } } // KVAction public interface KVAction extends Session.RetryableTransactionalAction { @Override default SessionMode getTransactionMode(Session session) { return SessionMode.KEY_VALUE; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy