mirror of
https://github.com/Genymobile/scrcpy.git
synced 2025-04-19 19:15:08 +00:00
fix #2841 by adding dynamic options
This commit is contained in:
parent
5900e9e39c
commit
9833735952
5 changed files with 301 additions and 222 deletions
|
@ -25,6 +25,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
implementation 'com.fasterxml.jackson.core:jackson-databind:2.19.0-rc2'
|
||||
}
|
||||
|
||||
apply from: "$project.rootDir/config/android-checkstyle.gradle"
|
||||
|
|
|
@ -288,6 +288,216 @@ public class Options {
|
|||
return sendCodecMeta;
|
||||
}
|
||||
|
||||
private void parseKeyValue(String key, String value) {
|
||||
switch (key) {
|
||||
case "scid":
|
||||
int scid = Integer.parseInt(value, 0x10);
|
||||
if (scid < -1) {
|
||||
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
|
||||
}
|
||||
this.scid = scid;
|
||||
break;
|
||||
case "log_level":
|
||||
this.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
||||
break;
|
||||
case "video":
|
||||
this.video = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "audio":
|
||||
this.audio = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "video_codec":
|
||||
VideoCodec videoCodec = VideoCodec.findByName(value);
|
||||
if (videoCodec == null) {
|
||||
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
||||
}
|
||||
this.videoCodec = videoCodec;
|
||||
break;
|
||||
case "audio_codec":
|
||||
AudioCodec audioCodec = AudioCodec.findByName(value);
|
||||
if (audioCodec == null) {
|
||||
throw new IllegalArgumentException("Audio codec " + value + " not supported");
|
||||
}
|
||||
this.audioCodec = audioCodec;
|
||||
break;
|
||||
case "video_source":
|
||||
VideoSource videoSource = VideoSource.findByName(value);
|
||||
if (videoSource == null) {
|
||||
throw new IllegalArgumentException("Video source " + value + " not supported");
|
||||
}
|
||||
this.videoSource = videoSource;
|
||||
break;
|
||||
case "audio_source":
|
||||
AudioSource audioSource = AudioSource.findByName(value);
|
||||
if (audioSource == null) {
|
||||
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
||||
}
|
||||
this.audioSource = audioSource;
|
||||
break;
|
||||
case "audio_dup":
|
||||
this.audioDup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "max_size":
|
||||
this.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||
break;
|
||||
case "video_bit_rate":
|
||||
this.videoBitRate = Integer.parseInt(value);
|
||||
break;
|
||||
case "audio_bit_rate":
|
||||
this.audioBitRate = Integer.parseInt(value);
|
||||
break;
|
||||
case "max_fps":
|
||||
this.maxFps = parseFloat("max_fps", value);
|
||||
break;
|
||||
case "angle":
|
||||
this.angle = parseFloat("angle", value);
|
||||
break;
|
||||
case "tunnel_forward":
|
||||
this.tunnelForward = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "crop":
|
||||
if (!value.isEmpty()) {
|
||||
this.crop = parseCrop(value);
|
||||
}
|
||||
break;
|
||||
case "control":
|
||||
this.control = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "display_id":
|
||||
this.displayId = Integer.parseInt(value);
|
||||
break;
|
||||
case "show_touches":
|
||||
this.showTouches = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "stay_awake":
|
||||
this.stayAwake = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "screen_off_timeout":
|
||||
this.screenOffTimeout = Integer.parseInt(value);
|
||||
if (this.screenOffTimeout < -1) {
|
||||
throw new IllegalArgumentException("Invalid screen off timeout: " + this.screenOffTimeout);
|
||||
}
|
||||
break;
|
||||
case "video_codec_options":
|
||||
this.videoCodecOptions = CodecOption.parse(value);
|
||||
break;
|
||||
case "audio_codec_options":
|
||||
this.audioCodecOptions = CodecOption.parse(value);
|
||||
break;
|
||||
case "video_encoder":
|
||||
if (!value.isEmpty()) {
|
||||
this.videoEncoder = value;
|
||||
}
|
||||
break;
|
||||
case "audio_encoder":
|
||||
if (!value.isEmpty()) {
|
||||
this.audioEncoder = value;
|
||||
}
|
||||
break;
|
||||
case "power_off_on_close":
|
||||
this.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "clipboard_autosync":
|
||||
this.clipboardAutosync = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "downsize_on_error":
|
||||
this.downsizeOnError = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "cleanup":
|
||||
this.cleanup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "power_on":
|
||||
this.powerOn = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_encoders":
|
||||
this.listEncoders = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_displays":
|
||||
this.listDisplays = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_cameras":
|
||||
this.listCameras = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_camera_sizes":
|
||||
this.listCameraSizes = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_apps":
|
||||
this.listApps = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "camera_id":
|
||||
if (!value.isEmpty()) {
|
||||
this.cameraId = value;
|
||||
}
|
||||
break;
|
||||
case "camera_size":
|
||||
if (!value.isEmpty()) {
|
||||
this.cameraSize = parseSize(value);
|
||||
}
|
||||
break;
|
||||
case "camera_facing":
|
||||
if (!value.isEmpty()) {
|
||||
CameraFacing facing = CameraFacing.findByName(value);
|
||||
if (facing == null) {
|
||||
throw new IllegalArgumentException("Camera facing " + value + " not supported");
|
||||
}
|
||||
this.cameraFacing = facing;
|
||||
}
|
||||
break;
|
||||
case "camera_ar":
|
||||
if (!value.isEmpty()) {
|
||||
this.cameraAspectRatio = parseCameraAspectRatio(value);
|
||||
}
|
||||
break;
|
||||
case "camera_fps":
|
||||
this.cameraFps = Integer.parseInt(value);
|
||||
break;
|
||||
case "camera_high_speed":
|
||||
this.cameraHighSpeed = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "new_display":
|
||||
this.newDisplay = parseNewDisplay(value);
|
||||
break;
|
||||
case "vd_destroy_content":
|
||||
this.vdDestroyContent = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "vd_system_decorations":
|
||||
this.vdSystemDecorations = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "capture_orientation":
|
||||
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
||||
this.captureOrientationLock = pair.first;
|
||||
this.captureOrientation = pair.second;
|
||||
break;
|
||||
case "display_ime_policy":
|
||||
this.displayImePolicy = parseDisplayImePolicy(value);
|
||||
break;
|
||||
case "send_device_meta":
|
||||
this.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_frame_meta":
|
||||
this.sendFrameMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_dummy_byte":
|
||||
this.sendDummyByte = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_codec_meta":
|
||||
this.sendCodecMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "raw_stream":
|
||||
boolean rawStream = Boolean.parseBoolean(value);
|
||||
if (rawStream) {
|
||||
this.sendDeviceMeta = false;
|
||||
this.sendFrameMeta = false;
|
||||
this.sendDummyByte = false;
|
||||
this.sendCodecMeta = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Ln.w("Unknown server option: " + key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("MethodLength")
|
||||
public static Options parse(String... args) {
|
||||
if (args.length < 1) {
|
||||
|
@ -310,212 +520,7 @@ public class Options {
|
|||
}
|
||||
String key = arg.substring(0, equalIndex);
|
||||
String value = arg.substring(equalIndex + 1);
|
||||
switch (key) {
|
||||
case "scid":
|
||||
int scid = Integer.parseInt(value, 0x10);
|
||||
if (scid < -1) {
|
||||
throw new IllegalArgumentException("scid may not be negative (except -1 for 'none'): " + scid);
|
||||
}
|
||||
options.scid = scid;
|
||||
break;
|
||||
case "log_level":
|
||||
options.logLevel = Ln.Level.valueOf(value.toUpperCase(Locale.ENGLISH));
|
||||
break;
|
||||
case "video":
|
||||
options.video = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "audio":
|
||||
options.audio = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "video_codec":
|
||||
VideoCodec videoCodec = VideoCodec.findByName(value);
|
||||
if (videoCodec == null) {
|
||||
throw new IllegalArgumentException("Video codec " + value + " not supported");
|
||||
}
|
||||
options.videoCodec = videoCodec;
|
||||
break;
|
||||
case "audio_codec":
|
||||
AudioCodec audioCodec = AudioCodec.findByName(value);
|
||||
if (audioCodec == null) {
|
||||
throw new IllegalArgumentException("Audio codec " + value + " not supported");
|
||||
}
|
||||
options.audioCodec = audioCodec;
|
||||
break;
|
||||
case "video_source":
|
||||
VideoSource videoSource = VideoSource.findByName(value);
|
||||
if (videoSource == null) {
|
||||
throw new IllegalArgumentException("Video source " + value + " not supported");
|
||||
}
|
||||
options.videoSource = videoSource;
|
||||
break;
|
||||
case "audio_source":
|
||||
AudioSource audioSource = AudioSource.findByName(value);
|
||||
if (audioSource == null) {
|
||||
throw new IllegalArgumentException("Audio source " + value + " not supported");
|
||||
}
|
||||
options.audioSource = audioSource;
|
||||
break;
|
||||
case "audio_dup":
|
||||
options.audioDup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "max_size":
|
||||
options.maxSize = Integer.parseInt(value) & ~7; // multiple of 8
|
||||
break;
|
||||
case "video_bit_rate":
|
||||
options.videoBitRate = Integer.parseInt(value);
|
||||
break;
|
||||
case "audio_bit_rate":
|
||||
options.audioBitRate = Integer.parseInt(value);
|
||||
break;
|
||||
case "max_fps":
|
||||
options.maxFps = parseFloat("max_fps", value);
|
||||
break;
|
||||
case "angle":
|
||||
options.angle = parseFloat("angle", value);
|
||||
break;
|
||||
case "tunnel_forward":
|
||||
options.tunnelForward = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "crop":
|
||||
if (!value.isEmpty()) {
|
||||
options.crop = parseCrop(value);
|
||||
}
|
||||
break;
|
||||
case "control":
|
||||
options.control = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "display_id":
|
||||
options.displayId = Integer.parseInt(value);
|
||||
break;
|
||||
case "show_touches":
|
||||
options.showTouches = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "stay_awake":
|
||||
options.stayAwake = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "screen_off_timeout":
|
||||
options.screenOffTimeout = Integer.parseInt(value);
|
||||
if (options.screenOffTimeout < -1) {
|
||||
throw new IllegalArgumentException("Invalid screen off timeout: " + options.screenOffTimeout);
|
||||
}
|
||||
break;
|
||||
case "video_codec_options":
|
||||
options.videoCodecOptions = CodecOption.parse(value);
|
||||
break;
|
||||
case "audio_codec_options":
|
||||
options.audioCodecOptions = CodecOption.parse(value);
|
||||
break;
|
||||
case "video_encoder":
|
||||
if (!value.isEmpty()) {
|
||||
options.videoEncoder = value;
|
||||
}
|
||||
break;
|
||||
case "audio_encoder":
|
||||
if (!value.isEmpty()) {
|
||||
options.audioEncoder = value;
|
||||
}
|
||||
case "power_off_on_close":
|
||||
options.powerOffScreenOnClose = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "clipboard_autosync":
|
||||
options.clipboardAutosync = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "downsize_on_error":
|
||||
options.downsizeOnError = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "cleanup":
|
||||
options.cleanup = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "power_on":
|
||||
options.powerOn = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_encoders":
|
||||
options.listEncoders = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_displays":
|
||||
options.listDisplays = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_cameras":
|
||||
options.listCameras = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_camera_sizes":
|
||||
options.listCameraSizes = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "list_apps":
|
||||
options.listApps = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "camera_id":
|
||||
if (!value.isEmpty()) {
|
||||
options.cameraId = value;
|
||||
}
|
||||
break;
|
||||
case "camera_size":
|
||||
if (!value.isEmpty()) {
|
||||
options.cameraSize = parseSize(value);
|
||||
}
|
||||
break;
|
||||
case "camera_facing":
|
||||
if (!value.isEmpty()) {
|
||||
CameraFacing facing = CameraFacing.findByName(value);
|
||||
if (facing == null) {
|
||||
throw new IllegalArgumentException("Camera facing " + value + " not supported");
|
||||
}
|
||||
options.cameraFacing = facing;
|
||||
}
|
||||
break;
|
||||
case "camera_ar":
|
||||
if (!value.isEmpty()) {
|
||||
options.cameraAspectRatio = parseCameraAspectRatio(value);
|
||||
}
|
||||
break;
|
||||
case "camera_fps":
|
||||
options.cameraFps = Integer.parseInt(value);
|
||||
break;
|
||||
case "camera_high_speed":
|
||||
options.cameraHighSpeed = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "new_display":
|
||||
options.newDisplay = parseNewDisplay(value);
|
||||
break;
|
||||
case "vd_destroy_content":
|
||||
options.vdDestroyContent = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "vd_system_decorations":
|
||||
options.vdSystemDecorations = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "capture_orientation":
|
||||
Pair<Orientation.Lock, Orientation> pair = parseCaptureOrientation(value);
|
||||
options.captureOrientationLock = pair.first;
|
||||
options.captureOrientation = pair.second;
|
||||
break;
|
||||
case "display_ime_policy":
|
||||
options.displayImePolicy = parseDisplayImePolicy(value);
|
||||
break;
|
||||
case "send_device_meta":
|
||||
options.sendDeviceMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_frame_meta":
|
||||
options.sendFrameMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_dummy_byte":
|
||||
options.sendDummyByte = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "send_codec_meta":
|
||||
options.sendCodecMeta = Boolean.parseBoolean(value);
|
||||
break;
|
||||
case "raw_stream":
|
||||
boolean rawStream = Boolean.parseBoolean(value);
|
||||
if (rawStream) {
|
||||
options.sendDeviceMeta = false;
|
||||
options.sendFrameMeta = false;
|
||||
options.sendDummyByte = false;
|
||||
options.sendCodecMeta = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
Ln.w("Unknown server option: " + key);
|
||||
break;
|
||||
}
|
||||
options.parseKeyValue(key, value);
|
||||
}
|
||||
|
||||
if (options.newDisplay != null) {
|
||||
|
@ -526,6 +531,18 @@ public class Options {
|
|||
return options;
|
||||
}
|
||||
|
||||
public void parseAdditional(String... args) {
|
||||
for (String arg : args) {
|
||||
int equalIndex = arg.indexOf('=');
|
||||
if (equalIndex == -1) {
|
||||
throw new IllegalArgumentException("Invalid key=value pair: \"" + arg + "\"");
|
||||
}
|
||||
String key = arg.substring(0, equalIndex);
|
||||
String value = arg.substring(equalIndex + 1);
|
||||
parseKeyValue(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private static Rect parseCrop(String crop) {
|
||||
// input format: "width:height:x:y"
|
||||
String[] tokens = crop.split(":");
|
||||
|
|
|
@ -17,6 +17,7 @@ import com.genymobile.scrcpy.device.Streamer;
|
|||
import com.genymobile.scrcpy.opengl.OpenGLRunner;
|
||||
import com.genymobile.scrcpy.util.Ln;
|
||||
import com.genymobile.scrcpy.util.LogUtils;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
import com.genymobile.scrcpy.video.CameraCapture;
|
||||
import com.genymobile.scrcpy.video.NewDisplayCapture;
|
||||
import com.genymobile.scrcpy.video.ScreenCapture;
|
||||
|
@ -75,22 +76,6 @@ public final class Server {
|
|||
}
|
||||
|
||||
private static void scrcpy(Options options) throws IOException, ConfigurationException {
|
||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||
Ln.e("Camera mirroring is not supported before Android 12");
|
||||
throw new ConfigurationException("Camera mirroring is not supported");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
|
||||
if (options.getNewDisplay() != null) {
|
||||
Ln.e("New virtual display is not supported before Android 10");
|
||||
throw new ConfigurationException("New virtual display is not supported");
|
||||
}
|
||||
if (options.getDisplayImePolicy() != -1) {
|
||||
Ln.e("Display IME policy is not supported before Android 10");
|
||||
throw new ConfigurationException("Display IME policy is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
CleanUp cleanUp = null;
|
||||
|
||||
if (options.getCleanup()) {
|
||||
|
@ -110,6 +95,34 @@ public final class Server {
|
|||
|
||||
DesktopConnection connection = DesktopConnection.open(scid, tunnelForward, video, audio, control, sendDummyByte);
|
||||
try {
|
||||
Ln.d("Waiting for additional args (JSON) ...");
|
||||
String additionalOptions = connection.receiveAdditionalOptions();
|
||||
|
||||
if (additionalOptions != null && !additionalOptions.isEmpty()) {
|
||||
Ln.d("Received additional options: " + additionalOptions);
|
||||
String args = StringUtils.jsonToArgs(additionalOptions);
|
||||
Ln.d("Additional args: " + args);
|
||||
options.parseAdditional(args.split(" "));
|
||||
} else {
|
||||
Ln.d("No additional args received.");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_31_ANDROID_12 && options.getVideoSource() == VideoSource.CAMERA) {
|
||||
Ln.e("Camera mirroring is not supported before Android 12");
|
||||
throw new ConfigurationException("Camera mirroring is not supported");
|
||||
}
|
||||
|
||||
if (Build.VERSION.SDK_INT < AndroidVersions.API_29_ANDROID_10) {
|
||||
if (options.getNewDisplay() != null) {
|
||||
Ln.e("New virtual display is not supported before Android 10");
|
||||
throw new ConfigurationException("New virtual display is not supported");
|
||||
}
|
||||
if (options.getDisplayImePolicy() != -1) {
|
||||
Ln.e("Display IME policy is not supported before Android 10");
|
||||
throw new ConfigurationException("Display IME policy is not supported");
|
||||
}
|
||||
}
|
||||
|
||||
if (options.getSendDeviceMeta()) {
|
||||
connection.sendDeviceMeta(Device.getDeviceName());
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.genymobile.scrcpy.device;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.genymobile.scrcpy.control.ControlChannel;
|
||||
import com.genymobile.scrcpy.util.IO;
|
||||
import com.genymobile.scrcpy.util.StringUtils;
|
||||
|
@ -9,9 +10,11 @@ import android.net.LocalSocket;
|
|||
import android.net.LocalSocketAddress;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.FileDescriptor;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
public final class DesktopConnection implements Closeable {
|
||||
|
||||
|
@ -164,6 +167,28 @@ public final class DesktopConnection implements Closeable {
|
|||
IO.writeFully(fd, buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
public String receiveAdditionalOptions() throws IOException {
|
||||
LocalSocket socket = getFirstSocket(); // or choose a specific one
|
||||
DataInputStream input = new DataInputStream(socket.getInputStream());
|
||||
|
||||
// Read length prefix (4 bytes, big-endian)
|
||||
int length = input.readInt(); // throws if the socket closes or data is invalid
|
||||
|
||||
if (length == 0) {
|
||||
return null; // No additional options sent
|
||||
}
|
||||
|
||||
if (length < 0 || length > 10 * 1024 * 1024) { // Limit to 10MB to avoid OOM
|
||||
throw new IOException("Invalid JSON message length: " + length);
|
||||
}
|
||||
|
||||
// Read the JSON payload
|
||||
byte[] jsonBytes = new byte[length];
|
||||
input.readFully(jsonBytes);
|
||||
|
||||
return new String(jsonBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public FileDescriptor getVideoFd() {
|
||||
return videoFd;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
package com.genymobile.scrcpy.util;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Map;
|
||||
|
||||
public final class StringUtils {
|
||||
private StringUtils() {
|
||||
// not instantiable
|
||||
|
@ -19,4 +24,22 @@ public final class StringUtils {
|
|||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
public static String jsonToArgs(String json) throws IOException {
|
||||
// Parse the JSON string into a map
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, Object> map = mapper.readValue(json, Map.class);
|
||||
|
||||
// Convert to key=value arguments
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (Map.Entry<String, Object> entry : map.entrySet()) {
|
||||
if (sb.length() > 0) {
|
||||
sb.append(' ');
|
||||
}
|
||||
sb.append(entry.getKey()).append('=').append(entry.getValue());
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue