From 589555736884e6b7173cc3fe187c9c7920fbe937 Mon Sep 17 00:00:00 2001 From: Zihlu Wang Date: Sun, 31 Mar 2024 18:34:01 +0800 Subject: [PATCH] refactor: Optimsed codes. Move private and protected methods or fields to the last of java files. --- gradlew | 0 .../codecrafters/simplejwt/SecretCreator.java | 59 +++++------ .../simplejwt/constants/PredefinedKeys.java | 6 +- .../simplejwt/constants/TokenDataType.java | 3 + .../simplejwt/jjwt/JjwtTokenResolver.java | 44 ++++++-- .../jjwt/config/JjwtTokenResolverConfig.java | 32 ++++-- ...uthzeroTokenResolverAutoConfiguration.java | 26 ++--- .../GuidAutoConfiguration.java | 5 + .../JjwtTokenResolverAutoConfiguration.java | 22 ++-- .../conditions/GuidCreatorCondition.java | 12 +++ .../org/codecrafters/webcal/WebCalendar.java | 100 +++++++++--------- .../codecrafters/webcal/WebCalendarNode.java | 4 +- ...rmatter.java => DateAndTimeFormatter.java} | 22 ++-- .../webcal/{ => impl}/WebCalendarEvent.java | 13 +-- .../org/codecrafters/webcal/package-info.java | 2 +- .../webcal/test/TestWebCalendar.java | 2 +- 16 files changed, 200 insertions(+), 152 deletions(-) mode change 100644 => 100755 gradlew rename webcal/src/main/java/cn/org/codecrafters/webcal/config/{Formatter.java => DateAndTimeFormatter.java} (72%) rename webcal/src/main/java/cn/org/codecrafters/webcal/{ => impl}/WebCalendarEvent.java (96%) diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/SecretCreator.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/SecretCreator.java index 4f274ec..c3478fa 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/SecretCreator.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/SecretCreator.java @@ -32,36 +32,6 @@ import java.util.Random; */ public final class SecretCreator { - /** - * Private constructor to prevent instantiation - */ - private SecretCreator() { - } - - /** - * The string containing all lowercase characters that can be used to - * generate the secret. - */ - private static final String LOWERCASE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz"; - - /** - * The string containing all uppercase characters that can be used to - * generate the secret. - */ - private static final String UPPERCASE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - /** - * The string containing all digit characters that can be used to generate - * the secret. - */ - private static final String DIGITS = "0123456789"; - - /** - * The string containing all special sign characters that can be used to - * generate the secret. - */ - private static final String SPECIAL_SIGNS = "!@#$%^&,*()_+-=,[]{}|;:,'\",.<>/?"; - /** * Generates a secure secret with the specified length and character sets. * @@ -153,4 +123,33 @@ public final class SecretCreator { return createSecret(length, false, false, false); } + /** + * Private constructor will protect this class from being instantiated. + */ + private SecretCreator() { + } + + /** + * The string containing all lowercase characters that can be used to + * generate the secret. + */ + private static final String LOWERCASE_CHARACTERS = "abcdefghijklmnopqrstuvwxyz"; + + /** + * The string containing all uppercase characters that can be used to + * generate the secret. + */ + private static final String UPPERCASE_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /** + * The string containing all digit characters that can be used to generate + * the secret. + */ + private static final String DIGITS = "0123456789"; + + /** + * The string containing all special sign characters that can be used to + * generate the secret. + */ + private static final String SPECIAL_SIGNS = "!@#$%^&,*()_+-=,[]{}|;:,'\",.<>/?"; } diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/PredefinedKeys.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/PredefinedKeys.java index 2df9309..f82c45b 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/PredefinedKeys.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/PredefinedKeys.java @@ -88,11 +88,7 @@ public final class PredefinedKeys { public static final List KEYS = List.of(ISSUER, SUBJECT, AUDIENCE, EXPIRATION_TIME, NOT_BEFORE, ISSUED_AT, JWT_ID); /** - * Private constructor to prevent instantiation of the - * {@code PredefinedKeys} class. - *

- * This class is intended to be used as a utility class with only static - * constants and methods. + * Private constructor will protect this class from being instantiated. */ private PredefinedKeys() { } diff --git a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java index 767c205..ee7e348 100644 --- a/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java +++ b/simple-jwt-facade/src/main/java/cn/org/codecrafters/simplejwt/constants/TokenDataType.java @@ -63,6 +63,9 @@ public enum TokenDataType { */ private final Class mappedClass; + /** + * Create a TokenDataType with a mapped class. + */ TokenDataType(Class mappedClass) { this.mappedClass = mappedClass; } diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java index dd32976..51abd91 100644 --- a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/JjwtTokenResolver.java @@ -98,16 +98,6 @@ import java.util.*; @Slf4j public class JjwtTokenResolver implements TokenResolver> { - private final GuidCreator jtiCreator; - - private final SecureDigestAlgorithm algorithm; - - private final String issuer; - - private final SecretKey key; - - private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance(); - /** * Create a resolver with specified algorithm, issuer, secret and guid strategy. * @@ -432,6 +422,15 @@ public class JjwtTokenResolver implements TokenResolver> { return renew(oldToken, Duration.ofMinutes(30), payload); } + /** + * Build a new token with specified data. + * + * @param expireAfter the validity time of the token + * @param audience the audience of the token + * @param subject the subject of the token + * @param claims the data to be included in the token + * @return the built token + */ private String buildToken(Duration expireAfter, String audience, String subject, Map claims) { var now = LocalDateTime.now(); var builder = Jwts.builder() @@ -453,4 +452,29 @@ public class JjwtTokenResolver implements TokenResolver> { return builder.signWith(key, algorithm) .compact(); } + + /** + * The ID creator for creating unique JWT IDs. + */ + private final GuidCreator jtiCreator; + + /** + * The algorithm to sign this token. + */ + private final SecureDigestAlgorithm algorithm; + + /** + * The issuer of this token. + */ + private final String issuer; + + /** + * The signature key of this token. + */ + private final SecretKey key; + + /** + * The config of this token resolver. + */ + private final JjwtTokenResolverConfig config = JjwtTokenResolverConfig.getInstance(); } diff --git a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java index 677682d..b678d18 100644 --- a/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java +++ b/simple-jwt-jjwt/src/main/java/cn/org/codecrafters/simplejwt/jjwt/config/JjwtTokenResolverConfig.java @@ -63,17 +63,6 @@ import java.util.Map; */ public final class JjwtTokenResolverConfig implements TokenResolverConfig> { - private JjwtTokenResolverConfig() { - } - - private static final Map> SUPPORTED_ALGORITHMS = new HashMap<>() {{ - put(TokenAlgorithm.HS256, Jwts.SIG.HS256); - put(TokenAlgorithm.HS384, Jwts.SIG.HS384); - put(TokenAlgorithm.HS512, Jwts.SIG.HS512); - }}; - - private static JjwtTokenResolverConfig instance; - public static JjwtTokenResolverConfig getInstance() { if (instance == null) { instance = new JjwtTokenResolverConfig(); @@ -106,4 +95,25 @@ public final class JjwtTokenResolverConfig implements TokenResolverConfig> SUPPORTED_ALGORITHMS = new HashMap<>() {{ + put(TokenAlgorithm.HS256, Jwts.SIG.HS256); + put(TokenAlgorithm.HS384, Jwts.SIG.HS384); + put(TokenAlgorithm.HS512, Jwts.SIG.HS512); + }}; + + /** + * The instance of this config class. + */ + private static JjwtTokenResolverConfig instance; + } diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java index 8e0c46b..9cfb314 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/AuthzeroTokenResolverAutoConfiguration.java @@ -65,19 +65,6 @@ import org.springframework.context.annotation.Bean; @AutoConfigureAfter(value = GuidAutoConfiguration.class) public class AuthzeroTokenResolverAutoConfiguration { - /** - * The GuidCreator instance to be used for generating JWT IDs (JTI). - */ - private final GuidCreator jtiCreator; - - /** - * The {@code SimpleJwtProperties} instance containing the configuration - * properties for Simple JWT. - */ - private final SimpleJwtProperties simpleJwtProperties; - - private final ObjectMapper objectMapper; - /** * Constructs a new {@code SimpleJwtAutoConfiguration} instance with the * provided SimpleJwtProperties. @@ -112,4 +99,17 @@ public class AuthzeroTokenResolverAutoConfiguration { ); } + /** + * The GuidCreator instance to be used for generating JWT IDs (JTI). + */ + private final GuidCreator jtiCreator; + + /** + * The {@code SimpleJwtProperties} instance containing the configuration + * properties for Simple JWT. + */ + private final SimpleJwtProperties simpleJwtProperties; + + private final ObjectMapper objectMapper; + } diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/GuidAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/GuidAutoConfiguration.java index 755369c..00372a7 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/GuidAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/GuidAutoConfiguration.java @@ -39,6 +39,11 @@ import java.util.UUID; @AutoConfiguration public class GuidAutoConfiguration { + /** + * Create a default {@code jtiCreator} with UUID. + * + * @return UUID creator + */ @Bean(name = "jtiCreator") @Conditional(GuidCreatorCondition.class) public GuidCreator jtiCreator() { diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java index 9e18fcf..677a25f 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/JjwtTokenResolverAutoConfiguration.java @@ -65,17 +65,6 @@ import org.springframework.context.annotation.Bean; @AutoConfigureAfter(value = GuidAutoConfiguration.class) public class JjwtTokenResolverAutoConfiguration { - /** - * The GuidCreator instance to be used for generating JWT IDs (JTI). - */ - private final GuidCreator jtiCreator; - - /** - * The {@code SimpleJwtProperties} instance containing the configuration - * properties for Simple JWT. - */ - private final SimpleJwtProperties simpleJwtProperties; - /** * Constructs a new {@code SimpleJwtAutoConfiguration} instance with the * provided SimpleJwtProperties. @@ -107,4 +96,15 @@ public class JjwtTokenResolverAutoConfiguration { ); } + /** + * The GuidCreator instance to be used for generating JWT IDs (JTI). + */ + private final GuidCreator jtiCreator; + + /** + * The {@code SimpleJwtProperties} instance containing the configuration + * properties for Simple JWT. + */ + private final SimpleJwtProperties simpleJwtProperties; + } diff --git a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/conditions/GuidCreatorCondition.java b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/conditions/GuidCreatorCondition.java index 287b3cd..769a54e 100644 --- a/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/conditions/GuidCreatorCondition.java +++ b/simple-jwt-spring-boot-starter/src/main/java/cn/org/codecrafters/simplejwt/autoconfiguration/conditions/GuidCreatorCondition.java @@ -17,6 +17,18 @@ import java.util.Objects; */ @Slf4j public class GuidCreatorCondition implements Condition { + + /** + * The condition to create bean {@code jtiCreator}. + *

+ * If Spring does not have a bean of type + * {@link cn.org.codecrafters.guid.GuidCreator} named {@code jtiCreator} + * in the application context, then create {@code jtiCreator}. + * + * @param context the spring application context + * @param metadata the metadata of the {@link org.springframework.core.type.AnnotationMetadata class} + * or {@link org.springframework.core.type.MethodMetadata method} being checked + */ @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { final var beanFactory = Objects.requireNonNull(context.getBeanFactory()); diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java index 9a99469..35a21de 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendar.java @@ -17,6 +17,8 @@ package cn.org.codecrafters.webcal; +import cn.org.codecrafters.webcal.impl.WebCalendarEvent; + import java.util.ArrayList; import java.util.List; import java.util.Objects; @@ -50,55 +52,6 @@ import java.util.Objects; */ public final class WebCalendar { - /** - * The {@code VCALENDAR} tag for iCalendar format - */ - private final static String TAG = "VCALENDAR"; - - /** - * The name of this calendar. - */ - private String name; - - /** - * The company who produces this calendar. - *

- * This property will be used in {@code PRODID}. - */ - private String companyName; - - /** - * The product name. - *

- * This property will be used in {@code PRODID} - */ - private String productName; - - /** - * The producer's domain name. - */ - private String domainName; - - /** - * Scale of this calendar. - */ - private final String scale = "GREGORIAN"; - - /** - * The method of this calendar. - */ - private String method; - - /** - * The version of this calendar. - */ - private final String version = "2.0"; - - /** - * List of calendar components and events - */ - private final List nodes; - /** * Constructor for WebCalendar class, initializes the list of calendar * components and events. @@ -209,5 +162,54 @@ public final class WebCalendar { "END:" + TAG; } + /** + * The {@code VCALENDAR} tag for iCalendar format + */ + private final static String TAG = "VCALENDAR"; + + /** + * The name of this calendar. + */ + private String name; + + /** + * The company who produces this calendar. + *

+ * This property will be used in {@code PRODID}. + */ + private String companyName; + + /** + * The product name. + *

+ * This property will be used in {@code PRODID} + */ + private String productName; + + /** + * The producer's domain name. + */ + private String domainName; + + /** + * Scale of this calendar. + */ + private final String scale = "GREGORIAN"; + + /** + * The method of this calendar. + */ + private String method; + + /** + * The version of this calendar. + */ + private final String version = "2.0"; + + /** + * List of calendar components and events + */ + private final List nodes; + } diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java index 75b0e43..a95cebc 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarNode.java @@ -18,6 +18,7 @@ package cn.org.codecrafters.webcal; import cn.org.codecrafters.webcal.config.Classification; +import cn.org.codecrafters.webcal.impl.WebCalendarEvent; import java.time.Duration; import java.time.LocalDateTime; @@ -37,8 +38,7 @@ import java.util.List; * @version 1.1.0 * @since 1.0.0 */ -public abstract sealed class WebCalendarNode - permits WebCalendarEvent { +public abstract class WebCalendarNode { // Common properties for all calendar components and events protected List categories; diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java b/webcal/src/main/java/cn/org/codecrafters/webcal/config/DateAndTimeFormatter.java similarity index 72% rename from webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java rename to webcal/src/main/java/cn/org/codecrafters/webcal/config/DateAndTimeFormatter.java index 4760d79..7b125a2 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/config/Formatter.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/config/DateAndTimeFormatter.java @@ -22,13 +22,19 @@ import java.time.format.DateTimeFormatter; import java.util.Objects; /** - * DatetimeFormatters + * A formatter to format {@link java.time.LocalDateTime}. * * @author Zihlu Wang - * @since 21 Sept, 2023 */ -public final class Formatter { +public final class DateAndTimeFormatter { + /** + * Get the {@link java.time.format.DateTimeFormatter datetime formatter} + * with UTC pattern and timezone. + * + * @return the {@link java.time.format.DateTimeFormatter datetime formatter} + * with UTC pattern and timezone + */ public static DateTimeFormatter getUtcDatetimeFormatter() { if (Objects.isNull(utcDateTimeFormatter)) { utcDateTimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss'Z'").withZone(ZoneOffset.UTC); @@ -37,16 +43,6 @@ public final class Formatter { return utcDateTimeFormatter; } - // public static DateTimeFormatter getLocalDatetimeFormatter() { - // if (Objects.isNull(localDatetimeFormatter)) { - // localDatetimeFormatter = DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss"); - // } - // - // return localDatetimeFormatter; - // } - private static DateTimeFormatter utcDateTimeFormatter; - // private static DateTimeFormatter localDatetimeFormatter; - } diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java b/webcal/src/main/java/cn/org/codecrafters/webcal/impl/WebCalendarEvent.java similarity index 96% rename from webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java rename to webcal/src/main/java/cn/org/codecrafters/webcal/impl/WebCalendarEvent.java index 2870246..ef81ba7 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/WebCalendarEvent.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/impl/WebCalendarEvent.java @@ -15,10 +15,11 @@ * limitations under the License. */ -package cn.org.codecrafters.webcal; +package cn.org.codecrafters.webcal.impl; +import cn.org.codecrafters.webcal.WebCalendarNode; import cn.org.codecrafters.webcal.config.Classification; -import cn.org.codecrafters.webcal.config.Formatter; +import cn.org.codecrafters.webcal.config.DateAndTimeFormatter; import java.text.MessageFormat; import java.time.Duration; @@ -47,8 +48,6 @@ import java.util.UUID; */ public final class WebCalendarEvent extends WebCalendarNode { - private final static String TAG = "VEVENT"; - /** * Add categories to the event. * @@ -270,8 +269,8 @@ public final class WebCalendarEvent extends WebCalendarNode { END:{0}""", TAG, // 0 - tag Optional.ofNullable(uid).orElse(UUID.randomUUID().toString()) + "@" + domainName, // 1 - uid - now.format(Formatter.getUtcDatetimeFormatter()), // 2 - dtstamp - start.atZone(ZoneId.systemDefault()).format(Formatter.getUtcDatetimeFormatter()), // 3 - start time + now.format(DateAndTimeFormatter.getUtcDatetimeFormatter()), // 2 - dtstamp + start.atZone(ZoneId.systemDefault()).format(DateAndTimeFormatter.getUtcDatetimeFormatter()), // 3 - start time Optional.ofNullable(summary).map((item) -> "\nSUMMARY:" + item).orElse(""), // 4 - summary Optional.ofNullable(categories) .map((item) -> !item.isEmpty() ? "\nCATEGORIES:" + resolveCategories() : null).orElse(""), // 5 - categories @@ -289,4 +288,6 @@ public final class WebCalendarEvent extends WebCalendarNode { ); } + private final static String TAG = "VEVENT"; + } diff --git a/webcal/src/main/java/cn/org/codecrafters/webcal/package-info.java b/webcal/src/main/java/cn/org/codecrafters/webcal/package-info.java index 3cfd07d..bb4ce67 100644 --- a/webcal/src/main/java/cn/org/codecrafters/webcal/package-info.java +++ b/webcal/src/main/java/cn/org/codecrafters/webcal/package-info.java @@ -27,7 +27,7 @@ * generating web calendars with customisable settings and events. * *

  • - * {@link cn.org.codecrafters.webcal.WebCalendarEvent}: A class + * {@link cn.org.codecrafters.webcal.impl.WebCalendarEvent}: A class * representing a single event in a web calendar with various * attributes and options. *
  • diff --git a/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java b/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java index 195e065..9b34b97 100644 --- a/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java +++ b/webcal/src/test/java/cn/org/codecrafters/webcal/test/TestWebCalendar.java @@ -18,7 +18,7 @@ package cn.org.codecrafters.webcal.test; import cn.org.codecrafters.webcal.WebCalendar; -import cn.org.codecrafters.webcal.WebCalendarEvent; +import cn.org.codecrafters.webcal.impl.WebCalendarEvent; import cn.org.codecrafters.webcal.config.Classification; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test;