diff --git a/src/main/java/io/github/eggy03/dmidecode/constant/DMIType.java b/src/main/java/io/github/eggy03/dmidecode/constant/DMIType.java
index 64dc320..a49283e 100644
--- a/src/main/java/io/github/eggy03/dmidecode/constant/DMIType.java
+++ b/src/main/java/io/github/eggy03/dmidecode/constant/DMIType.java
@@ -5,6 +5,8 @@
*/
package io.github.eggy03.dmidecode.constant;
+import org.jspecify.annotations.NonNull;
+
/**
* Enumeration of DMI types as defined by the
* {@code dmidecode} specification.
@@ -72,7 +74,8 @@ public enum DMIType {
this.value = value;
}
- public static String getCommandFor(DMIType type) {
- return "sudo /usr/sbin/dmidecode --type " + type.value;
+ @NonNull
+ public String getCommand() {
+ return "sudo /usr/sbin/dmidecode --type " + this.value;
}
}
\ No newline at end of file
diff --git a/src/main/java/io/github/eggy03/dmidecode/exception/TerminalExecutionException.java b/src/main/java/io/github/eggy03/dmidecode/exception/TerminalIOException.java
similarity index 67%
rename from src/main/java/io/github/eggy03/dmidecode/exception/TerminalExecutionException.java
rename to src/main/java/io/github/eggy03/dmidecode/exception/TerminalIOException.java
index 3a7597f..29d8cf2 100644
--- a/src/main/java/io/github/eggy03/dmidecode/exception/TerminalExecutionException.java
+++ b/src/main/java/io/github/eggy03/dmidecode/exception/TerminalIOException.java
@@ -10,20 +10,20 @@
*
* @since 0.1.0
*/
-public class TerminalExecutionException extends RuntimeException {
+public class TerminalIOException extends RuntimeException {
@SuppressWarnings("unused")
- public TerminalExecutionException(String message, Throwable cause) {
+ public TerminalIOException(String message, Throwable cause) {
super(message, cause);
}
@SuppressWarnings("unused")
- public TerminalExecutionException(String message) {
+ public TerminalIOException(String message) {
super(message);
}
@SuppressWarnings("unused")
- public TerminalExecutionException(Throwable cause) {
+ public TerminalIOException(Throwable cause) {
super("Terminal Execution Failure", cause);
}
diff --git a/src/main/java/io/github/eggy03/dmidecode/mapper/CommonDMIMapper.java b/src/main/java/io/github/eggy03/dmidecode/mapper/CommonDMIMapper.java
index b6ddd9c..720569f 100644
--- a/src/main/java/io/github/eggy03/dmidecode/mapper/CommonDMIMapper.java
+++ b/src/main/java/io/github/eggy03/dmidecode/mapper/CommonDMIMapper.java
@@ -47,7 +47,24 @@
*/
public interface CommonDMIMapper {
- ObjectMapper jacksonMapper = new ObjectMapper();
+ /**
+ * Configure the {@link ObjectMapper} to be used for JSON processing.
+ *
+ *
+ * The default implementation returns a new {@link ObjectMapper} instance with default configuration. + *
+ * + *+ * Custom implementations may override this method to provide a custom-configured + * {@link ObjectMapper}. + *
+ * + * @return the {@link ObjectMapper} to use + * @since 0.3.0 + */ + default @NonNull ObjectMapper configureObjectMapper() { + return new ObjectMapper(); + } /** * Maps raw {@code dmidecode} output into a single entity of type {@code+ * This class encapsulates both the standard output (stdout) and standard error (stderr) + * produced during execution. + *
+ * + * @since 0.3.0 + */ +public class TerminalResult { + + private final @NonNull String result; + private final @NonNull String error; + + /** + * Constructs a {@link TerminalResult}. + * + * @param result the non-null standard output (stdout) produced by the execution + * @param error the non-null standard error (stderr) produced by the execution + * @throws NullPointerException if either {@code result} or {@code error} is {@code null} + */ + public TerminalResult(@NonNull String result, @NonNull String error) { + this.result = Objects.requireNonNull(result, "result cannot be null"); + this.error = Objects.requireNonNull(error, "error cannot be null"); + } + + + /** + * Returns the standard output (stdout) produced by the execution. + * + * @return a non-null string containing the command output + */ + public @NonNull String getResult() { + return result; + } + + /** + * Returns the standard error (stderr) produced by the execution. + * + * @return a non-null string containing the error output + */ + public @NonNull String getError() { + return error; + } + +} diff --git a/src/main/java/io/github/eggy03/dmidecode/terminal/TerminalService.java b/src/main/java/io/github/eggy03/dmidecode/terminal/TerminalService.java new file mode 100644 index 0000000..101139d --- /dev/null +++ b/src/main/java/io/github/eggy03/dmidecode/terminal/TerminalService.java @@ -0,0 +1,97 @@ +/* + * © 2026 The dmidecode4j contributors + * Licensed under the MIT License. + * See the LICENSE file in the project root for more information. + */ +package io.github.eggy03.dmidecode.terminal; + +import io.github.eggy03.dmidecode.constant.DMIType; +import io.github.eggy03.dmidecode.exception.TerminalIOException; +import org.apache.commons.exec.CommandLine; +import org.apache.commons.exec.DefaultExecutor; +import org.apache.commons.exec.ExecuteException; +import org.apache.commons.exec.ExecuteWatchdog; +import org.apache.commons.exec.PumpStreamHandler; +import org.jspecify.annotations.NonNull; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.time.Duration; +import java.util.Objects; + +/** + * A service class that provides a way to launch a terminal session + *+ * for internal use + * + * @since 0.1.0 + */ +public class TerminalService { + + private static final Logger log = LoggerFactory.getLogger(TerminalService.class); + + /** + * Launches a standalone PowerShell session, executes {@link DMIType#getCommand()} and returns the result + * + * @param dmiType The non-null enum value containing the command which shall be executed + * @param timeoutSeconds The non-null, positive value of time in seconds after which the session will be force stopped. + * @return The result of the query executed, wrapped in {@link TerminalResult} + * @since 0.3.0 + */ + @NonNull + public TerminalResult executeCommand(@NonNull DMIType dmiType, long timeoutSeconds) { + Objects.requireNonNull(dmiType, "dmiType cannot be null"); + return execute(dmiType.getCommand(), timeoutSeconds); + } + + + /** + * Launches a standalone Terminal session and executes commands and returns the result + * + * @param command The command to be executed in the Terminal, must not be null + * @param timeoutSeconds Time in seconds after which the session will be force stopped, must not be null. + * @return The result of the command executed, wrapped in {@link TerminalResult} + * @throws IllegalArgumentException if timeout is in negative. + * @since 0.1.0 + */ + @NonNull TerminalResult execute(@NonNull String command, long timeoutSeconds) { + + Objects.requireNonNull(command, "command or script to be executed cannot be null"); + + if (timeoutSeconds < 0) + throw new IllegalArgumentException("Timeout cannot be negative"); + + CommandLine cmdLine = new CommandLine("bash"); + cmdLine.addArgument("-c"); + cmdLine.addArgument(command, false); + + ByteArrayOutputStream resultStream = new ByteArrayOutputStream(); + ByteArrayOutputStream errorStream = new ByteArrayOutputStream(); + + ExecuteWatchdog watchdog = ExecuteWatchdog.builder().setTimeout(Duration.ofSeconds(timeoutSeconds)).get(); + + DefaultExecutor executor = DefaultExecutor.builder().get(); + executor.setStreamHandler(new PumpStreamHandler(resultStream, errorStream)); + executor.setWatchdog(watchdog); + + try { + int exitCode = executor.execute(cmdLine); + log.debug("\nTerminal Execution - SUCCESS\nExit code: {}\nCommand: {}\nStdout: {}\nStderr: {}\n", exitCode, command, resultStream, errorStream); + return new TerminalResult(resultStream.toString(), errorStream.toString()); + } catch (ExecuteException e) { + + boolean processKilled = watchdog.killedProcess(); + if (log.isDebugEnabled()) + log.debug("\nTerminal Execution - FAILURE\nProcess Killed: {}\nTimeout: {}\nCommand: {}\nStdout: {}\nStderr: {}\n", processKilled, timeoutSeconds, command, resultStream, errorStream, e); + else + log.warn("\nTerminal Execution - FAILURE\nProcess Killed: {}\nEnable DEBUG mode to see the commands it tried to execute\n", processKilled, e); + + return new TerminalResult(resultStream.toString(), errorStream.toString()); + + } catch (IOException e) { + throw new TerminalIOException("An I/O Exception occurred while running Terminal", e); + } + } +} diff --git a/src/main/java/io/github/eggy03/dmidecode/utility/TerminalUtility.java b/src/main/java/io/github/eggy03/dmidecode/utility/TerminalUtility.java deleted file mode 100644 index 442938e..0000000 --- a/src/main/java/io/github/eggy03/dmidecode/utility/TerminalUtility.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * © 2026 The dmidecode4j contributors - * Licensed under the MIT License. - * See the LICENSE file in the project root for more information. - */ -package io.github.eggy03.dmidecode.utility; - -import io.github.eggy03.dmidecode.exception.TerminalExecutionException; -import org.apache.commons.exec.CommandLine; -import org.apache.commons.exec.DefaultExecutor; -import org.apache.commons.exec.ExecuteException; -import org.apache.commons.exec.ExecuteWatchdog; -import org.apache.commons.exec.PumpStreamHandler; -import org.jspecify.annotations.NonNull; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.time.Duration; - -/** - * A utility class that provides a way to launch a terminal session - *
- * Mostly for internal use - * - * @since 0.1.0 - */ -public class TerminalUtility { - - private static final Logger log = LoggerFactory.getLogger(TerminalUtility.class); - - private TerminalUtility() { - throw new IllegalStateException("Utility Class"); - } - - /** - * Launches a standalone terminal session, executes the given command - * and returns the result - * - * @param command The command/script to be executed in the terminal - * @param timeoutSeconds Time in seconds after which the session will be force stopped - * @return The result of the command executed - * @throws TerminalExecutionException When the process is killed pre-maturely upon reaching the timeout - * or when the command yields an error, or when the terminal cannot be accessed. - * @throws IllegalArgumentException If the provided timeout is in the negative - */ - public static @NonNull String executeCommand(@NonNull String command, long timeoutSeconds) { - - if (timeoutSeconds < 0) - throw new IllegalArgumentException("Timeout cannot be negative"); - - CommandLine cmdLine = new CommandLine("bash"); - cmdLine.addArgument("-c"); - cmdLine.addArgument(command, false); - - ByteArrayOutputStream result = new ByteArrayOutputStream(); - ByteArrayOutputStream err = new ByteArrayOutputStream(); - - ExecuteWatchdog watchdog = ExecuteWatchdog.builder().setTimeout(Duration.ofSeconds(timeoutSeconds)).get(); - - DefaultExecutor executor = DefaultExecutor.builder().get(); - executor.setStreamHandler(new PumpStreamHandler(result, err)); - executor.setWatchdog(watchdog); - - try { - int exitCode = executor.execute(cmdLine); - log.debug("\nCommand Executed: {}\nExit code: {}\nError Stream: {}\nResult Stream: {}\n", command, exitCode, err, result); - return result.toString(); - } catch (ExecuteException e) { - String reason = watchdog.killedProcess() ? - "\nProcess executing the following command: " + command + "\nWas killed after a timeout of " + timeoutSeconds + " seconds\n" : - "\nProcess executing the following command: " + command + "\nExited with a non-zero exit code\nTerminal Error Output: " + err; - - throw new TerminalExecutionException(reason, e); - } catch (IOException e) { - String reason = "An I/O Exception occurred during executing the following command:\n" + command; - throw new TerminalExecutionException(reason, e); - } - } -}