From 08d0d147b961473f971f6fc0c8fe998fa50303a3 Mon Sep 17 00:00:00 2001
From: Jean-Noel Rouvignac <jean-noel.rouvignac@forgerock.com>
Date: Thu, 26 Feb 2015 11:45:56 +0000
Subject: [PATCH] Code cleanup
---
opendj-copyright-maven-plugin/src/test/java/org/forgerock/maven/UpdateCopyrightTestCase.java | 113 ++++++++-------
opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java | 289 ++++++++++++++---------------------------
2 files changed, 158 insertions(+), 244 deletions(-)
diff --git a/opendj-copyright-maven-plugin/src/test/java/org/forgerock/maven/UpdateCopyrightTestCase.java b/opendj-copyright-maven-plugin/src/test/java/org/forgerock/maven/UpdateCopyrightTestCase.java
index 05a297b..caab0c9 100644
--- a/opendj-copyright-maven-plugin/src/test/java/org/forgerock/maven/UpdateCopyrightTestCase.java
+++ b/opendj-copyright-maven-plugin/src/test/java/org/forgerock/maven/UpdateCopyrightTestCase.java
@@ -36,6 +36,7 @@
import java.util.List;
import org.forgerock.testng.ForgeRockTestCase;
+import org.forgerock.util.Utils;
import org.testng.annotations.AfterTest;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -43,24 +44,32 @@
@Test
public class UpdateCopyrightTestCase extends ForgeRockTestCase {
+ private final class FilenameExtensionFilter implements FilenameFilter {
+ private final String extension;
+
+ private FilenameExtensionFilter(String suffix) {
+ this.extension = suffix;
+ }
+
+ @Override
+ public boolean accept(File directory, String fileName) {
+ return fileName.endsWith(extension);
+ }
+ }
+
+
private static final String CURRENT_YEAR = Integer.toString(Calendar.getInstance().get(Calendar.YEAR));
private static final String RESOURCE_DIR = "src/test/resources/files/";
- private static final String[] TEST_FOLDERS = new String[] {"openam-copyrights",
- "opendj-copyrights", "openidm-copyrights"};
+ private static final String[] TEST_FOLDERS = { "openam-copyrights", "opendj-copyrights", "openidm-copyrights"};
/** Customs tags in tests files. */
- private static final String MUST_BE_REMOVE_TAG = "MUST BE REMOVED:";
+ private static final String MUST_BE_REMOVED_TAG = "MUST BE REMOVED:";
private static final String EXPECTED_OUTPUT_TAG = "EXPECTED OUTPUT:";
private static final String YEAR_TAG = "YEAR";
-
@AfterTest
public void deleteTempFiles() {
- FilenameFilter tmpFilter = new FilenameFilter() {
- public boolean accept(File directory, String fileName) {
- return fileName.endsWith(".tmp");
- }
- };
+ FilenameFilter tmpFilter = new FilenameExtensionFilter(".tmp");
for (String testFolder : TEST_FOLDERS) {
for (File file : new File(RESOURCE_DIR, testFolder).listFiles(tmpFilter)) {
file.delete();
@@ -68,7 +77,6 @@
}
}
-
@DataProvider
public Object[][] testCases() {
return new Object[][] {
@@ -84,17 +92,13 @@
@Test(dataProvider = "testCases")
public void testUpdateCopyright(String testCaseFolderPath, String lineBeforeCopyrightToken,
- Integer nbLinesToSkip, Integer numberSpacesIndentation, String portionCopyrightToken,
+ int nbLinesToSkip, int numberSpacesIndentation, String portionCopyrightToken,
String copyrightStartToken, String copyrightEndToken) throws Exception {
List<String> testFilePaths = new LinkedList<String>();
List<String> updatedTestFilePaths = new LinkedList<String>();
- FilenameFilter txtFilter = new FilenameFilter() {
- public boolean accept(File directory, String fileName) {
- return fileName.endsWith(".txt");
- }
- };
- File[] changedFiles = new File(RESOURCE_DIR, testCaseFolderPath).listFiles(txtFilter);
+ File[] changedFiles = new File(RESOURCE_DIR, testCaseFolderPath)
+ .listFiles(new FilenameExtensionFilter(".txt"));
for (File file : changedFiles) {
testFilePaths.add(file.getAbsolutePath());
updatedTestFilePaths.add(file.getPath() + ".tmp");
@@ -124,23 +128,25 @@
}
}
-
private void checkMofidiedFile(String filePath) throws Exception {
- final BufferedReader reader = new BufferedReader(new FileReader(filePath));
- String mustBeRemoved = null;
- String expectedOutput = null;
- String currentLine = reader.readLine();
- while (currentLine != null) {
- if (currentLine.contains(MUST_BE_REMOVE_TAG)) {
- mustBeRemoved = currentLine.split(MUST_BE_REMOVE_TAG)[1].trim();
- } else if (currentLine.contains(EXPECTED_OUTPUT_TAG)) {
- expectedOutput = currentLine.split(EXPECTED_OUTPUT_TAG)[1].trim()
- .replace(YEAR_TAG, CURRENT_YEAR);
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(filePath));
+ String mustBeRemoved = null;
+ String expectedOutput = null;
+ String currentLine;
+ while ((currentLine = reader.readLine()) != null) {
+ if (currentLine.contains(MUST_BE_REMOVED_TAG)) {
+ mustBeRemoved = currentLine.split(MUST_BE_REMOVED_TAG)[1].trim();
+ } else if (currentLine.contains(EXPECTED_OUTPUT_TAG)) {
+ expectedOutput = currentLine.split(EXPECTED_OUTPUT_TAG)[1].trim()
+ .replace(YEAR_TAG, CURRENT_YEAR);
+ }
}
- currentLine = reader.readLine();
+ checkIfNewFileIsValid(mustBeRemoved, expectedOutput, filePath + ".tmp");
+ } finally {
+ Utils.closeSilently(reader);
}
- reader.close();
- checkIfNewFileIsValid(mustBeRemoved, expectedOutput, filePath + ".tmp");
}
@@ -149,29 +155,29 @@
return;
}
- final BufferedReader reader = new BufferedReader(new FileReader(filePath));
- String currentLine = reader.readLine();
- boolean expectedOutputFound = false;
+ BufferedReader reader = null;
+ try {
+ reader = new BufferedReader(new FileReader(filePath));
- while (currentLine != null) {
- if (lineContainsTagContent(currentLine, mustBeRemoved)) {
- reader.close();
- throw new Exception("Generated file " + filePath + " must not contains " + mustBeRemoved);
- }
+ boolean expectedOutputFound = false;
+ String currentLine;
+ while ((currentLine = reader.readLine()) != null) {
+ if (lineContainsTagContent(currentLine, mustBeRemoved)) {
+ throw new Exception("Generated file " + filePath + " must not contains " + mustBeRemoved);
+ }
- if (!expectedOutputFound && lineContainsTagContent(currentLine, expectedOutput)) {
- expectedOutputFound = true;
- if (mustBeRemoved == null) {
- reader.close();
- return;
+ if (!expectedOutputFound && lineContainsTagContent(currentLine, expectedOutput)) {
+ expectedOutputFound = true;
+ if (mustBeRemoved == null) {
+ return;
+ }
}
}
- currentLine = reader.readLine();
- }
- reader.close();
-
- if (!expectedOutputFound) {
- throw new Exception("Generated file " + filePath + " should contains " + expectedOutput);
+ if (!expectedOutputFound) {
+ throw new Exception("Generated file " + filePath + " should contains " + expectedOutput);
+ }
+ } finally {
+ Utils.closeSilently(reader);
}
}
@@ -179,11 +185,8 @@
private boolean lineContainsTagContent(String line, String content) {
String trimedLine = line.trim();
return content != null
- && !trimedLine.startsWith(MUST_BE_REMOVE_TAG)
- && !trimedLine.startsWith(MUST_BE_REMOVE_TAG)
+ && !trimedLine.startsWith(MUST_BE_REMOVED_TAG)
+ && !trimedLine.startsWith(MUST_BE_REMOVED_TAG)
&& trimedLine.contains(content);
}
-
-
-
}
diff --git a/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java b/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java
index cc6dacf..4560ba5 100644
--- a/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java
+++ b/opendj-server-legacy/src/main/java/org/opends/server/tools/InstallDS.java
@@ -27,18 +27,15 @@
*/
package org.opends.server.tools;
+import static com.forgerock.opendj.cli.Utils.*;
+import static com.forgerock.opendj.util.OperatingSystem.*;
+
+import static org.forgerock.util.Utils.*;
import static org.opends.messages.AdminToolMessages.*;
import static org.opends.messages.QuickSetupMessages.*;
import static org.opends.messages.ToolMessages.*;
import static org.opends.messages.UtilityMessages.*;
-import static com.forgerock.opendj.cli.Utils.CONFIRMATION_MAX_TRIES;
-import static com.forgerock.opendj.cli.Utils.canWrite;
-
-import static org.forgerock.util.Utils.joinAsString;
-
-import static com.forgerock.opendj.util.OperatingSystem.isWindows;
-
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
@@ -47,13 +44,19 @@
import java.io.OutputStream;
import java.io.PrintStream;
import java.security.KeyStoreException;
-import java.util.*;
-
-import org.forgerock.i18n.LocalizableMessage;
-import org.forgerock.i18n.slf4j.LocalizedLogger;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
import javax.naming.ldap.LdapName;
+import org.forgerock.i18n.LocalizableMessage;
+import org.forgerock.i18n.LocalizableMessageDescriptor.Arg0;
+import org.forgerock.i18n.LocalizableMessageDescriptor.Arg1;
+import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.messages.QuickSetupMessages;
import org.opends.messages.ToolMessages;
import org.opends.quicksetup.ApplicationException;
@@ -79,13 +82,13 @@
import org.opends.server.util.StaticUtils;
import com.forgerock.opendj.cli.ArgumentException;
-import com.forgerock.opendj.cli.IntegerArgument;
-import com.forgerock.opendj.cli.StringArgument;
import com.forgerock.opendj.cli.ClientException;
import com.forgerock.opendj.cli.ConsoleApplication;
+import com.forgerock.opendj.cli.IntegerArgument;
import com.forgerock.opendj.cli.Menu;
import com.forgerock.opendj.cli.MenuBuilder;
import com.forgerock.opendj.cli.MenuResult;
+import com.forgerock.opendj.cli.StringArgument;
/**
* This class provides a very simple mechanism for installing the OpenDS
@@ -616,13 +619,13 @@
{
if (!confirmAction(INFO_CLI_DO_YOU_WANT_TO_CONTINUE.get(), true))
{
- throw new InitializationException(LocalizableMessage.EMPTY, null);
+ throw new InitializationException(LocalizableMessage.EMPTY);
}
}
catch (final ClientException ce)
{
logger.error(LocalizableMessage.raw("Unexpected error: "+ce, ce));
- throw new InitializationException(LocalizableMessage.EMPTY, null);
+ throw new InitializationException(LocalizableMessage.EMPTY, ce);
}
}
else
@@ -632,7 +635,7 @@
}
else if (installStatus.isInstalled())
{
- throw new InitializationException(installStatus.getInstallationMsg(), null);
+ throw new InitializationException(installStatus.getInstallationMsg());
}
}
@@ -974,27 +977,23 @@
true);
uData.setDirectoryManagerDn(dns.getFirst());
- String pwd = argParser.getDirectoryManagerPassword();
int nTries = 0;
+ String pwd = argParser.getDirectoryManagerPassword();
while (pwd == null)
{
if (nTries >= CONFIRMATION_MAX_TRIES)
{
throw new UserDataException(null, ERR_TRIES_LIMIT_REACHED.get(CONFIRMATION_MAX_TRIES));
}
- char[] pwd1 = null;
// Prompt for password and confirm.
- while (pwd1 == null)
+ char[] pwd1 = readPassword(INFO_INSTALLDS_PROMPT_ROOT_PASSWORD.get());
+ while (pwd1 == null || pwd1.length == 0)
{
+ println();
+ println(INFO_EMPTY_PWD.get());
+ println();
pwd1 = readPassword(INFO_INSTALLDS_PROMPT_ROOT_PASSWORD.get());
- if (pwd1 == null || pwd1.length == 0)
- {
- pwd1 = null;
- println();
- println(INFO_EMPTY_PWD.get());
- println();
- }
}
final char[] pwd2 = readPassword(INFO_INSTALLDS_PROMPT_CONFIRM_ROOT_PASSWORD.get());
@@ -1287,69 +1286,12 @@
println();
println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(joinAsString(", ", nonExistingFiles)));
}
- while (importLDIFFiles.isEmpty())
- {
- println();
- try
- {
- final String path = readInput(INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(),
- lastResetImportFile);
- if (!Utils.fileExists(path))
- {
- println();
- println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path));
- }
- else
- {
- importLDIFFiles.add(path);
- }
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- String rejectedFile = argParser.rejectedImportFileArg.getValue();
- if (rejectedFile != null)
- {
- while (!canWrite(rejectedFile))
- {
- println();
- println(ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile));
- println();
- try
- {
- rejectedFile =
- readInput(INFO_INSTALLDS_PROMPT_REJECTED_FILE.get(),
- lastResetRejectedFile);
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- }
- String skippedFile = argParser.skippedImportFileArg.getValue();
- if (skippedFile != null)
- {
- while (!canWrite(skippedFile))
- {
- println();
- println(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get(skippedFile));
- println();
- try
- {
- skippedFile =
- readInput(INFO_INSTALLDS_PROMPT_SKIPPED_FILE.get(),
- lastResetSkippedFile);
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- }
+ readImportLdifFile(importLDIFFiles, lastResetImportFile);
+ String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, lastResetRejectedFile,
+ ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
+ String skippedFile = readValidFilePath(argParser.skippedImportFileArg, lastResetSkippedFile,
+ ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
importLDIFFiles, rejectedFile, skippedFile);
}
@@ -1371,8 +1313,7 @@
final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
numUsers = promptForInteger(message, 2000, 0, Integer.MAX_VALUE);
}
- dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs,
- numUsers);
+ dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
}
else
{
@@ -1454,88 +1395,20 @@
if (populateType == POPULATE_TYPE_IMPORT_FROM_LDIF)
{
final List<String> importLDIFFiles = new LinkedList<String>();
- while (importLDIFFiles.isEmpty())
- {
- LocalizableMessage message = INFO_INSTALLDS_PROMPT_IMPORT_FILE.get();
- println();
- try
- {
- final String path = readInput(message, null);
- if (Utils.fileExists(path))
- {
- importLDIFFiles.add(path);
- }
- else
- {
- message = ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path);
- println();
- println(message);
- }
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- String rejectedFile = argParser.rejectedImportFileArg.getValue();
- if (rejectedFile != null)
- {
- while (!canWrite(rejectedFile))
- {
- println();
- println(
- ERR_INSTALLDS_CANNOT_WRITE_REJECTED.get(rejectedFile));
- println();
- try
- {
- rejectedFile =
- readInput(INFO_INSTALLDS_PROMPT_REJECTED_FILE.get(), null);
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- }
- String skippedFile = argParser.skippedImportFileArg.getValue();
- if (skippedFile != null)
- {
- while (!canWrite(skippedFile))
- {
- println();
- println(ERR_INSTALLDS_CANNOT_WRITE_SKIPPED.get(skippedFile));
- println();
- try
- {
- skippedFile =
- readInput(INFO_INSTALLDS_PROMPT_SKIPPED_FILE.get(), null);
- }
- catch (final ClientException ce)
- {
- logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
- }
- }
- }
+ readImportLdifFile(importLDIFFiles, null);
+ String rejectedFile = readValidFilePath(argParser.rejectedImportFileArg, null,
+ ERR_INSTALLDS_CANNOT_WRITE_REJECTED, INFO_INSTALLDS_PROMPT_REJECTED_FILE);
+ String skippedFile = readValidFilePath(argParser.skippedImportFileArg, null,
+ ERR_INSTALLDS_CANNOT_WRITE_SKIPPED, INFO_INSTALLDS_PROMPT_SKIPPED_FILE);
dataOptions = NewSuffixOptions.createImportFromLDIF(baseDNs,
importLDIFFiles, rejectedFile, skippedFile);
}
else if (populateType == POPULATE_TYPE_GENERATE_SAMPLE_DATA)
{
final LocalizableMessage message = INFO_INSTALLDS_PROMPT_NUM_ENTRIES.get();
- int defaultValue;
- if (lastResetNumEntries != null)
- {
- defaultValue = lastResetNumEntries;
- }
- else
- {
- defaultValue = 2000;
- }
- final int numUsers = promptForInteger(message, defaultValue, 0,
- Integer.MAX_VALUE);
-
- dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs,
- numUsers);
+ int defaultValue = lastResetNumEntries != null ? lastResetNumEntries : 2000;
+ final int numUsers = promptForInteger(message, defaultValue, 0, Integer.MAX_VALUE);
+ dataOptions = NewSuffixOptions.createAutomaticallyGenerated(baseDNs, numUsers);
}
else if (populateType == POPULATE_TYPE_LEAVE_EMPTY)
{
@@ -1547,13 +1420,61 @@
}
else
{
- throw new IllegalStateException("Unexpected populateType: "+
- populateType);
+ throw new IllegalStateException("Unexpected populateType: " + populateType);
}
}
return dataOptions;
}
+ private void readImportLdifFile(final List<String> importLDIFFiles, String defaultValue)
+ {
+ while (importLDIFFiles.isEmpty())
+ {
+ println();
+ try
+ {
+ final String path = readInput(INFO_INSTALLDS_PROMPT_IMPORT_FILE.get(), defaultValue);
+ if (Utils.fileExists(path))
+ {
+ importLDIFFiles.add(path);
+ }
+ else
+ {
+ println();
+ println(ERR_INSTALLDS_NO_SUCH_LDIF_FILE.get(path));
+ }
+ }
+ catch (final ClientException ce)
+ {
+ logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
+ }
+ }
+ }
+
+ private String readValidFilePath(StringArgument arg, String defaultValue, Arg1<Object> errCannotWriteFile,
+ Arg0 infoPromptFile)
+ {
+ String file = arg.getValue();
+ if (file != null)
+ {
+ while (!canWrite(file))
+ {
+ println();
+ println(errCannotWriteFile.get(file));
+ println();
+ try
+ {
+ file = readInput(infoPromptFile.get(), defaultValue);
+ }
+ catch (final ClientException ce)
+ {
+ logger.warn(LocalizableMessage.raw("Error reading input: "+ce, ce));
+ }
+ }
+ }
+ return file;
+ }
+
/**
* This method returns what the user specified in the command-line for the
* security parameters. If the user did not provide explicitly some data or if
@@ -1588,10 +1509,8 @@
println();
try
{
- final boolean defaultValue = lastResetEnableSSL != null ? lastResetEnableSSL :
- false;
- enableSSL = confirmAction(INFO_INSTALLDS_PROMPT_ENABLE_SSL.get(),
- defaultValue);
+ final boolean defaultValue = lastResetEnableSSL != null ? lastResetEnableSSL : false;
+ enableSSL = confirmAction(INFO_INSTALLDS_PROMPT_ENABLE_SSL.get(), defaultValue);
if (enableSSL)
{
ldapsPort = promptIfRequiredForPortData(argParser.ldapsPortArg,
@@ -1957,14 +1876,12 @@
}
if (!found)
{
- errorMessages.add(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND.get(
- aliasString));
+ errorMessages.add(ERR_INSTALLDS_CERTNICKNAME_NOT_FOUND.get(aliasString));
}
}
else if (aliases.length > 1)
{
- errorMessages.add(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME.get(
- aliasString));
+ errorMessages.add(ERR_INSTALLDS_MUST_PROVIDE_CERTNICKNAME.get(aliasString));
}
}
}
@@ -1994,7 +1911,7 @@
errorMessages.add(INFO_ERROR_ACCESSING_PKCS11_KEYSTORE.get());
break;
default:
- throw new IllegalArgumentException("Invalid type: " + type);
+ throw new IllegalArgumentException("Invalid type: " + type, ke);
}
}
}
@@ -2179,10 +2096,8 @@
* @return <CODE>true</CODE> if any of the error messages provided corresponds
* to a problem with the key store path and <CODE>false</CODE> otherwise.
*/
- public static boolean containsKeyStorePathErrorMessage(
- Collection<LocalizableMessage> msgs)
+ public static boolean containsKeyStorePathErrorMessage(Collection<LocalizableMessage> msgs)
{
- boolean found = false;
for (final LocalizableMessage msg : msgs)
{
if (StaticUtils.hasDescriptor(msg, INFO_KEYSTORE_PATH_DOES_NOT_EXIST) ||
@@ -2196,11 +2111,10 @@
StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS12_KEYSTORE) ||
StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE))
{
- found = true;
- break;
+ return true;
}
}
- return found;
+ return false;
}
/**
@@ -2210,10 +2124,8 @@
* @return <CODE>true</CODE> if any of the error messages provided corresponds
* to a problem with the key store password and <CODE>false</CODE> otherwise.
*/
- public static boolean containsKeyStorePasswordErrorMessage(
- Collection<LocalizableMessage> msgs)
+ public static boolean containsKeyStorePasswordErrorMessage(Collection<LocalizableMessage> msgs)
{
- boolean found = false;
for (final LocalizableMessage msg : msgs)
{
if (StaticUtils.hasDescriptor(msg, INFO_JKS_KEYSTORE_DOES_NOT_EXIST) ||
@@ -2226,11 +2138,10 @@
StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_PKCS11_KEYSTORE) ||
StaticUtils.hasDescriptor(msg, INFO_ERROR_ACCESSING_KEYSTORE_JDK_BUG))
{
- found = true;
- break;
+ return true;
}
}
- return found;
+ return false;
}
/**
--
Gitblit v1.10.0