8344587: Reduce number of "jdk.jpackage.internal" classes used from other packages

Reviewed-by: almatvee
This commit is contained in:
Alexey Semenyuk 2024-11-22 23:33:49 +00:00
parent 11147046aa
commit 70c4e2c0cc
21 changed files with 320 additions and 93 deletions

View File

@ -61,7 +61,7 @@ import static jdk.jpackage.internal.StandardBundlerParam.SIGN_BUNDLE;
import static jdk.jpackage.internal.StandardBundlerParam.APP_STORE;
import jdk.jpackage.internal.util.XmlUtils;
public final class AppImageFile {
final class AppImageFile {
// These values will be loaded from AppImage xml file.
private final String appVersion;

View File

@ -40,7 +40,7 @@ import java.util.List;
*
* A collection of static utility methods.
*/
public class IOUtils {
final class IOUtils {
public static void copyFile(Path sourceFile, Path destFile)
throws IOException {

View File

@ -31,7 +31,7 @@ import java.nio.file.Path;
import java.util.Objects;
import java.util.Optional;
public final class PackageFile {
final class PackageFile {
/**
* Returns path to package file.

View File

@ -26,24 +26,23 @@ package jdk.jpackage.internal.util;
import java.nio.file.Path;
import java.util.Optional;
import jdk.jpackage.internal.IOUtils;
public final class PathUtils {
public static String getSuffix(Path path) {
String filename = replaceSuffix(IOUtils.getFileName(path), null).toString();
return IOUtils.getFileName(path).toString().substring(filename.length());
String filename = replaceSuffix(path.getFileName(), null).toString();
return path.getFileName().toString().substring(filename.length());
}
public static Path addSuffix(Path path, String suffix) {
Path parent = path.getParent();
String filename = IOUtils.getFileName(path).toString() + suffix;
String filename = path.getFileName().toString() + suffix;
return parent != null ? parent.resolve(filename) : Path.of(filename);
}
public static Path replaceSuffix(Path path, String suffix) {
Path parent = path.getParent();
String filename = IOUtils.getFileName(path).toString().replaceAll("\\.[^.]*$",
String filename = path.getFileName().toString().replaceAll("\\.[^.]*$",
"") + Optional.ofNullable(suffix).orElse("");
return parent != null ? parent.resolve(filename) : Path.of(filename);
}

View File

@ -39,7 +39,6 @@ import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stax.StAXResult;
import jdk.jpackage.internal.IOUtils;
public final class XmlUtils {
@ -47,7 +46,7 @@ public final class XmlUtils {
public static void createXml(Path dstFile, XmlConsumer xmlConsumer) throws
IOException {
XMLOutputFactory xmlFactory = XMLOutputFactory.newInstance();
Files.createDirectories(IOUtils.getParent(dstFile));
Files.createDirectories(dstFile.getParent());
try (Writer w = Files.newBufferedWriter(dstFile)) {
// Wrap with pretty print proxy
XMLStreamWriter xml = (XMLStreamWriter) Proxy.newProxyInstance(XMLStreamWriter.class.getClassLoader(),

View File

@ -0,0 +1,122 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.test;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathFactory;
import jdk.internal.util.OperatingSystem;
import jdk.jpackage.internal.util.XmlUtils;
import static jdk.jpackage.internal.util.function.ThrowingSupplier.toSupplier;
import org.w3c.dom.Document;
public record AppImageFile(String mainLauncherName, String mainLauncherClassName,
String version, boolean macSigned, boolean macAppStore) {
public static Path getPathInAppImage(Path appImageDir) {
return ApplicationLayout.platformAppImage()
.resolveAt(appImageDir)
.appDirectory()
.resolve(FILENAME);
}
public AppImageFile(String mainLauncherName, String mainLauncherClassName) {
this(mainLauncherName, mainLauncherClassName, "1.0", false, false);
}
public void save(Path appImageDir) throws IOException {
XmlUtils.createXml(getPathInAppImage(appImageDir), xml -> {
xml.writeStartElement("jpackage-state");
xml.writeAttribute("version", getVersion());
xml.writeAttribute("platform", getPlatform());
xml.writeStartElement("app-version");
xml.writeCharacters(version);
xml.writeEndElement();
xml.writeStartElement("main-launcher");
xml.writeCharacters(mainLauncherName);
xml.writeEndElement();
xml.writeStartElement("main-class");
xml.writeCharacters(mainLauncherClassName);
xml.writeEndElement();
xml.writeStartElement("signed");
xml.writeCharacters(Boolean.toString(macSigned));
xml.writeEndElement();
xml.writeStartElement("app-store");
xml.writeCharacters(Boolean.toString(macAppStore));
xml.writeEndElement();
});
}
public static AppImageFile load(Path appImageDir) {
return toSupplier(() -> {
Document doc = XmlUtils.initDocumentBuilder().parse(
Files.newInputStream(getPathInAppImage(appImageDir)));
XPath xPath = XPathFactory.newInstance().newXPath();
var version = xPath.evaluate("/jpackage-state/app-version/text()", doc);
var mainLauncherName = xPath.evaluate(
"/jpackage-state/main-launcher/text()", doc);
var mainLauncherClassName = xPath.evaluate(
"/jpackage-state/main-class/text()", doc);
var macSigned = Optional.ofNullable(xPath.evaluate(
"/jpackage-state/signed/text()", doc)).map(
Boolean::parseBoolean).orElse(false);
var macAppStore = Optional.ofNullable(xPath.evaluate(
"/jpackage-state/app-store/text()", doc)).map(
Boolean::parseBoolean).orElse(false);
return new AppImageFile(mainLauncherName, mainLauncherClassName,
version, macSigned, macAppStore);
}).get();
}
private static String getVersion() {
return System.getProperty("java.version");
}
private static String getPlatform() {
return PLATFORM_LABELS.get(OperatingSystem.current());
}
private static final String FILENAME = ".jpackage.xml";
private static final Map<OperatingSystem, String> PLATFORM_LABELS = Map.of(
OperatingSystem.LINUX, "linux",
OperatingSystem.WINDOWS, "windows",
OperatingSystem.MACOS, "macOS");
}

View File

@ -0,0 +1,126 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.test;
import java.nio.file.Path;
import java.util.Optional;
public record ApplicationLayout(Path launchersDirectory, Path appDirectory,
Path runtimeDirectory, Path runtimeHomeDirectory, Path appModsDirectory,
Path destktopIntegrationDirectory, Path contentDirectory) {
public ApplicationLayout resolveAt(Path root) {
return new ApplicationLayout(
resolve(root, launchersDirectory),
resolve(root, appDirectory),
resolve(root, runtimeDirectory),
resolve(root, runtimeHomeDirectory),
resolve(root, appModsDirectory),
resolve(root, destktopIntegrationDirectory),
resolve(root, contentDirectory));
}
public static ApplicationLayout linuxAppImage() {
return new ApplicationLayout(
Path.of("bin"),
Path.of("lib/app"),
Path.of("lib/runtime"),
Path.of("lib/runtime"),
Path.of("lib/app/mods"),
Path.of("lib"),
Path.of("lib")
);
}
public static ApplicationLayout windowsAppImage() {
return new ApplicationLayout(
Path.of(""),
Path.of("app"),
Path.of("runtime"),
Path.of("runtime"),
Path.of("app/mods"),
Path.of(""),
Path.of("")
);
}
public static ApplicationLayout macAppImage() {
return new ApplicationLayout(
Path.of("Contents/MacOS"),
Path.of("Contents/app"),
Path.of("Contents/runtime"),
Path.of("Contents/runtime/Contents/Home"),
Path.of("Contents/app/mods"),
Path.of("Contents/Resources"),
Path.of("Contents")
);
}
public static ApplicationLayout platformAppImage() {
if (TKit.isWindows()) {
return windowsAppImage();
}
if (TKit.isLinux()) {
return linuxAppImage();
}
if (TKit.isOSX()) {
return macAppImage();
}
throw new IllegalArgumentException("Unknown platform");
}
public static ApplicationLayout javaRuntime() {
return new ApplicationLayout(
null,
null,
Path.of(""),
null,
null,
null,
null
);
}
public static ApplicationLayout linuxUsrTreePackageImage(Path prefix,
String packageName) {
final Path lib = prefix.resolve(Path.of("lib", packageName));
return new ApplicationLayout(
prefix.resolve("bin"),
lib.resolve("app"),
lib.resolve("runtime"),
lib.resolve("runtime"),
lib.resolve("app/mods"),
lib,
lib
);
}
private static Path resolve(Path base, Path path) {
return Optional.ofNullable(path).map(base::resolve).orElse(null);
}
}

View File

@ -22,7 +22,6 @@
*/
package jdk.jpackage.test;
import java.lang.reflect.InvocationTargetException;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;

View File

@ -45,10 +45,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.internal.PackageFile;
import jdk.jpackage.internal.util.XmlUtils;
import static jdk.jpackage.test.AdditionalLauncher.forEachAdditionalLauncher;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
import jdk.jpackage.internal.util.function.ThrowingFunction;
@ -157,8 +153,10 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
public <T> T getArgumentValue(String argName,
Supplier<T> defaultValueSupplier,
Function<String, T> stringConverter) {
return getArgumentValue(argName, (unused) -> defaultValueSupplier.get(),
stringConverter);
return getArgumentValue(argName,
Optional.ofNullable(defaultValueSupplier).map(supplier -> {
return (Function<JPackageCommand, T>)unused -> supplier.get();
}).orElse(null), stringConverter);
}
public String getArgumentValue(String argName,
@ -217,9 +215,9 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
}
public String name() {
String appImage = getArgumentValue("--app-image", () -> null);
String appImage = getArgumentValue("--app-image");
if (appImage != null) {
String name = AppImageFile.extractAppName(Path.of(appImage));
String name = AppImageFile.load(Path.of(appImage)).mainLauncherName();
// can be null if using foreign app-image
return ((name != null) ? name : getArgumentValue("--name"));
}
@ -233,7 +231,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
if (installerName == null) {
String appImage = getArgumentValue("--app-image");
if (appImage != null) {
installerName = AppImageFile.extractAppName(Path.of(appImage));
installerName = AppImageFile.load(Path.of(appImage)).mainLauncherName();
}
}
return installerName;
@ -306,42 +304,6 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
return this;
}
public void createJPackageXMLFile(String mainLauncher, String mainClass)
throws IOException {
Path jpackageXMLFile = AppImageFile.getPathInAppImage(
Optional.ofNullable(getArgumentValue("--app-image")).map(
Path::of).orElseThrow(() -> {
return new RuntimeException(
"Error: --app-image expected");
}));
XmlUtils.createXml(jpackageXMLFile, xml -> {
xml.writeStartElement("jpackage-state");
xml.writeAttribute("version", AppImageFile.getVersion());
xml.writeAttribute("platform", AppImageFile.getPlatform());
xml.writeStartElement("app-version");
xml.writeCharacters("1.0");
xml.writeEndElement();
xml.writeStartElement("main-launcher");
xml.writeCharacters(mainLauncher);
xml.writeEndElement();
xml.writeStartElement("main-class");
xml.writeCharacters(mainClass);
xml.writeEndElement();
xml.writeStartElement("signed");
xml.writeCharacters("false");
xml.writeEndElement();
xml.writeStartElement("app-store");
xml.writeCharacters("false");
xml.writeEndElement();
});
}
JPackageCommand addPrerequisiteAction(ThrowingConsumer<JPackageCommand> action) {
verifyMutable();
prerequisiteActions.add(action);
@ -935,7 +897,7 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
private void assertAppImageFile() {
Path appImageDir = Path.of("");
if (isImagePackageType() && hasArgument("--app-image")) {
appImageDir = Path.of(getArgumentValue("--app-image", () -> null));
appImageDir = Path.of(getArgumentValue("--app-image"));
}
final Path lookupPath = AppImageFile.getPathInAppImage(appImageDir);
@ -956,12 +918,12 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
AppImageFile aif = AppImageFile.load(rootDir);
boolean expectedValue = hasArgument("--mac-sign");
boolean actualValue = aif.isSigned();
boolean actualValue = aif.macSigned();
TKit.assertEquals(Boolean.toString(expectedValue), Boolean.toString(actualValue),
"Check for unexptected value in app image file for <signed>");
expectedValue = hasArgument("--mac-app-store");
actualValue = aif.isAppStore();
actualValue = aif.macAppStore();
TKit.assertEquals(Boolean.toString(expectedValue), Boolean.toString(actualValue),
"Check for unexptected value in app image file for <app-store>");
}
@ -975,9 +937,8 @@ public final class JPackageCommand extends CommandArguments<JPackageCommand> {
assertFileInAppImage(lookupPath, null);
} else {
if (TKit.isOSX() && hasArgument("--app-image")) {
String appImage = getArgumentValue("--app-image",
() -> null);
if (AppImageFile.load(Path.of(appImage)).isSigned()) {
String appImage = getArgumentValue("--app-image");
if (AppImageFile.load(Path.of(appImage)).macSigned()) {
assertFileInAppImage(lookupPath, null);
} else {
assertFileInAppImage(lookupPath, lookupPath);

View File

@ -40,7 +40,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.internal.util.PathUtils;
import jdk.jpackage.internal.util.function.ThrowingConsumer;
import jdk.jpackage.test.PackageTest.PackageHandlers;

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.jpackage.test;
import java.nio.file.Path;
public final class PackageFile {
public static Path getPathInAppImage(Path appImageDir) {
return ApplicationLayout.platformAppImage()
.resolveAt(appImageDir)
.appDirectory()
.resolve(FILENAME);
}
private static final String FILENAME = ".package";
}

View File

@ -45,7 +45,6 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.internal.util.function.ThrowingBiConsumer;
import static jdk.jpackage.internal.util.function.ThrowingBiConsumer.toBiConsumer;
import jdk.jpackage.internal.util.function.ThrowingConsumer;

View File

@ -22,7 +22,7 @@
*/
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.PackageTest;

View File

@ -22,7 +22,7 @@
*/
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.PackageType;

View File

@ -22,7 +22,7 @@
*/
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.PackageTest;

View File

@ -25,7 +25,7 @@ import java.nio.file.Path;
import java.nio.file.Files;
import java.io.IOException;
import java.util.List;
import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.JPackageCommand;
@ -87,7 +87,7 @@ public class AppImagePackageTest {
cmd.addArguments("--icon", iconPath("icon"));
}
cmd.removeArgumentWithValue("--input");
cmd.createJPackageXMLFile("EmptyAppImagePackageTest", "Hello");
new AppImageFile("EmptyAppImagePackageTest", "Hello").save(appImageDir);
// on mac, with --app-image and without --mac-package-identifier,
// will try to infer it from the image, so foreign image needs it.

View File

@ -26,17 +26,13 @@ import java.io.IOException;
import java.util.Collection;
import java.util.ArrayList;
import java.util.List;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.PackageTest;
import jdk.jpackage.test.TKit;
import jdk.jpackage.internal.AppImageFile;
import org.w3c.dom.Document;
/*
* @test
@ -116,10 +112,7 @@ public final class AppVersionTest {
}
cmd.executeAndAssertHelloAppImageCreated();
Document xml = AppImageFile.readXml(cmd.outputBundle());
String actualVersion = XPathFactory.newInstance().newXPath().evaluate(
"/jpackage-state/app-version/text()", xml, XPathConstants.STRING).toString();
String actualVersion = AppImageFile.load(cmd.outputBundle()).version();
TKit.assertEquals(expectedVersion, actualVersion,
"Check application version");
}

View File

@ -31,9 +31,9 @@ import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Stream;
import jdk.internal.util.OperatingSystem;
import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.internal.PackageFile;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.PackageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.internal.util.function.ThrowingConsumer;

View File

@ -29,20 +29,16 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import jdk.jpackage.internal.AppImageFile;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.HelloApp;
import jdk.jpackage.test.JavaAppDesc;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.Annotations.Parameter;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Executor;
import jdk.jpackage.test.JPackageCommand;
import jdk.jpackage.test.JavaTool;
import jdk.jpackage.test.PackageType;
import jdk.jpackage.test.TKit;
import org.w3c.dom.Document;
/*
@ -105,11 +101,7 @@ public final class ModulePathTest3 {
cmd.executeAndAssertHelloAppImageCreated();
if (appDesc.moduleVersion() != null) {
Document xml = AppImageFile.readXml(cmd.outputBundle());
String actualVersion = XPathFactory.newInstance().newXPath().evaluate(
"/jpackage-state/app-version/text()", xml,
XPathConstants.STRING).toString();
String actualVersion = AppImageFile.load(cmd.outputBundle()).version();
TKit.assertEquals(appDesc.moduleVersion(), actualVersion,
"Check application version");
}

View File

@ -28,6 +28,7 @@ import java.nio.file.Files;
import java.util.Collection;
import java.util.List;
import jdk.jpackage.test.AppImageFile;
import jdk.jpackage.test.Annotations.Parameters;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;
@ -111,7 +112,7 @@ public final class PredefinedAppImageErrorTest {
Files.createFile(dummyAppFile);
cmd.addArguments("--app-image", dummyAppFolder.toString());
cmd.createJPackageXMLFile("PredefinedAppImageErrorTest", "Hello");
new AppImageFile("PredefinedAppImageErrorTest", "Hello").save(dummyAppFolder);
}
}

View File

@ -23,7 +23,7 @@
import java.nio.file.Files;
import java.nio.file.Path;
import jdk.jpackage.internal.ApplicationLayout;
import jdk.jpackage.test.ApplicationLayout;
import jdk.jpackage.test.TKit;
import jdk.jpackage.test.Annotations.Test;
import jdk.jpackage.test.JPackageCommand;