opendj-server-legacy/src/main/java/org/opends/admin/ads/SuffixDescriptor.java
@@ -13,6 +13,7 @@ * * Copyright 2008 Sun Microsystems, Inc. * Portions Copyright 2015-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.admin.ads; @@ -107,6 +108,13 @@ } @Override public boolean equals(Object o) { return this == o || (o instanceof SuffixDescriptor && getId().equals(((SuffixDescriptor) o).getId())); } @Override public int hashCode() { return getId().hashCode(); @@ -114,16 +122,28 @@ /** * Returns an Id that is unique for this suffix. * <p> * The id is built from the suffix DN and the ids of the servers holding a replica of it. * The server ids are sorted because {@link #getReplicas()} hands out a {@link HashSet} of * {@link ReplicaDescriptor}s, which do not override {@code hashCode()}: without the sort, * two descriptors describing the same suffix on the same servers could produce different * ids depending on the iteration order. * * @return an Id that is unique for this suffix. */ public String getId() { StringBuilder buf = new StringBuilder(); buf.append(getDN()); Set<String> serverIds = new TreeSet<>(); for (ReplicaDescriptor replica : getReplicas()) { buf.append("-").append(replica.getServer().getId()); serverIds.add(replica.getServer().getId()); } StringBuilder buf = new StringBuilder(); buf.append(getDN()); for (String serverId : serverIds) { buf.append("-").append(serverId); } return buf.toString(); } opendj-server-legacy/src/main/java/org/opends/guitools/controlpanel/datamodel/ScheduleType.java
@@ -13,6 +13,7 @@ * * Copyright 2009 Sun Microsystems, Inc. * Portions Copyright 2015-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.guitools.controlpanel.datamodel; @@ -119,7 +120,7 @@ { return true; } return o != null return o instanceof ScheduleType && toString().equals(o.toString()); } opendj-server-legacy/src/main/java/org/opends/quicksetup/Application.java
@@ -13,6 +13,7 @@ * * Copyright 2008-2010 Sun Microsystems, Inc. * Portions Copyright 2012-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.quicksetup; @@ -799,9 +800,12 @@ /** Class used to add points periodically to the end of the logs. */ protected class PointAdder implements Runnable { private Thread t; private boolean stopPointAdder; private boolean pointAdderStopped; /** Written by start() and read by stop(), hence volatile. */ private volatile Thread t; /** Written by stop() and read by the point adder thread, hence volatile. */ private volatile boolean stopPointAdder; /** Written by the point adder thread and read by stop(), hence volatile. */ private volatile boolean pointAdderStopped; /** Default constructor. */ public PointAdder() @@ -825,21 +829,31 @@ t.start(); } /** Stops the PointAdder: points are no longer added at the end of the logs periodically. */ public synchronized void stop() /** * Stops the PointAdder: points are no longer added at the end of the logs periodically. * <p> * Calling this method on a PointAdder that was never started is a no-op. */ public void stop() { stopPointAdder = true; final Thread thread = t; if (thread == null) { return; } while (!pointAdderStopped) { thread.interrupt(); try { t.interrupt(); // To allow the thread to set the boolean. Thread.sleep(100); } catch (Throwable t) catch (InterruptedException e) { // do nothing Thread.currentThread().interrupt(); break; } } } opendj-server-legacy/src/main/java/org/opends/server/core/DirectoryServer.java
@@ -13,7 +13,7 @@ * * Copyright 2006-2010 Sun Microsystems, Inc. * Portions Copyright 2010-2016 ForgeRock AS. * Portions Copyright 2022-2025 3A Systems, LLC. * Portions Copyright 2022-2026 3A Systems, LLC. * Portions Copyright 2025 Wren Security. */ package org.opends.server.core; @@ -177,11 +177,27 @@ { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** The singleton Directory Server instance. */ private static DirectoryServer directoryServer = new DirectoryServer(); /** * Guards the life cycle of the {@link #directoryServer} singleton: bootstrapping, * starting, shutting down, and replacing the instance during an in-core restart. * A dedicated lock is used because the {@code directoryServer} field is reassigned * by {@link #getNewInstance(DirectoryEnvironmentConfig)}: synchronizing on the field * itself would let threads acquire different monitors and provide no exclusion at all. * <p> * The lock makes the guarded transitions atomic; it does not make the life cycle flags * visible to the code that reads them without holding it (see {@link #isRunning()} and * {@link #isShuttingDown()}), which is why those flags are {@code volatile}. */ private static final Object LIFECYCLE_LOCK = new Object(); /** Indicates whether the server currently holds an exclusive lock on the server lock file. */ private static boolean serverLocked; /** The singleton Directory Server instance. */ private static volatile DirectoryServer directoryServer = new DirectoryServer(); /** * Indicates whether the server currently holds an exclusive lock on the server lock file. * Written outside {@link #LIFECYCLE_LOCK} on shutdown, hence volatile. */ private static volatile boolean serverLocked; /** The message to be displayed on the command-line when the user asks for the usage. */ private static final LocalizableMessage toolDescription = INFO_DSCORE_TOOL_DESCRIPTION.get(); @@ -228,15 +244,18 @@ /** The configuration manager that will handle the server backends. */ private BackendConfigManager backendConfigManager; /** Indicates whether the server has been bootstrapped. */ private boolean isBootstrapped; /** Indicates whether the server is currently online. */ private boolean isRunning; /** Indicates whether the server has been bootstrapped. Read without holding {@link #LIFECYCLE_LOCK}. */ private volatile boolean isBootstrapped; /** Indicates whether the server is currently online. Read without holding {@link #LIFECYCLE_LOCK}. */ private volatile boolean isRunning; /** Indicates whether the server is currently in "lockdown mode". */ private boolean lockdownMode; /** Indicates whether the server is currently in the process of shutting down. */ private boolean shuttingDown; /** * Indicates whether the server is currently in the process of shutting down. * Read without holding {@link #LIFECYCLE_LOCK}. */ private volatile boolean shuttingDown; /** The configuration manager that will handle the certificate mapper. */ private CertificateMapperConfigManager certificateMapperConfigManager; @@ -284,7 +303,7 @@ * a mapping between the DN of the associated configuration entry and the * policy implementation. */ private ConcurrentMap<DN, AuthenticationPolicy> authenticationPolicies; private final ConcurrentMap<DN, AuthenticationPolicy> authenticationPolicies = new ConcurrentHashMap<>(); /** * The set of password validators registered with the Directory Server, as a @@ -356,7 +375,7 @@ /** The set of alert handlers registered with the Directory Server. */ private List<AlertHandler<?>> alertHandlers; /** The set of connection handlers registered with the Directory Server. */ private List<ConnectionHandler<?>> connectionHandlers; private final List<ConnectionHandler<?>> connectionHandlers = new CopyOnWriteArrayList<>(); /** The set of backup task listeners registered with the Directory Server. */ private CopyOnWriteArrayList<BackupTaskListener> backupTaskListeners; @@ -430,7 +449,7 @@ private KeyManagerProviderConfigManager keyManagerProviderConfigManager; /** The set of connections that are currently established. */ private Set<ClientConnection> establishedConnections; private final Set<ClientConnection> establishedConnections = new LinkedHashSet<>(1000); /** The log rotation policy config manager for the Directory Server. */ private LogRotationPolicyConfigManager rotationPolicyConfigManager; @@ -1059,7 +1078,7 @@ private static DirectoryServer getNewInstance(DirectoryEnvironmentConfig config) { synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { return directoryServer = new DirectoryServer(config); } @@ -1122,7 +1141,7 @@ */ public static void bootstrapClient() { synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { // Schema handler contains a default schema to start with directoryServer.schemaHandler = new SchemaHandler(); @@ -1143,14 +1162,14 @@ directoryServer.rotationPolicies = new ConcurrentHashMap<>(); directoryServer.retentionPolicies = new ConcurrentHashMap<>(); directoryServer.certificateMappers = new ConcurrentHashMap<>(); directoryServer.authenticationPolicies = new ConcurrentHashMap<>(); directoryServer.authenticationPolicies.clear(); directoryServer.defaultPasswordPolicy = null; directoryServer.monitorProviders = new ConcurrentHashMap<>(); directoryServer.initializationCompletedListeners = new CopyOnWriteArrayList<>(); directoryServer.shutdownListeners = new CopyOnWriteArrayList<>(); directoryServer.synchronizationProviders = new CopyOnWriteArrayList<>(); directoryServer.supportedLDAPVersions = new ConcurrentHashMap<>(); directoryServer.connectionHandlers = new CopyOnWriteArrayList<>(); directoryServer.connectionHandlers.clear(); directoryServer.identityMappers = new ConcurrentHashMap<>(); directoryServer.extendedOperationHandlers = new ConcurrentHashMap<>(); directoryServer.saslMechanismHandlers = new ConcurrentHashMap<>(); @@ -1180,7 +1199,7 @@ // First, make sure that the server isn't currently running. If it isn't, // then make sure that no other thread will try to start or bootstrap the // server before this thread is done. synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { if (isRunning) { @@ -1205,7 +1224,10 @@ bootstrapClient(); // Initialize the variables that will be used for connection tracking. establishedConnections = new LinkedHashSet<>(1000); synchronized (establishedConnections) { establishedConnections.clear(); } currentConnections = 0; maxConnections = 0; totalConnections = 0; @@ -1216,7 +1238,7 @@ pluginConfigManager = new PluginConfigManager(serverContext); // If we have gotten here, then the configuration should be properly bootstrapped. synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { isBootstrapped = true; } @@ -1331,7 +1353,7 @@ throw new InitializationException(e.getMessageObject()); } synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { if (! isBootstrapped) { @@ -2758,7 +2780,7 @@ */ public static void setEntryCache(EntryCache<?> entryCache) { synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { directoryServer.entryCache = entryCache; } @@ -4084,7 +4106,7 @@ */ public static void shutDown(String className, LocalizableMessage reason) { synchronized (directoryServer) synchronized (LIFECYCLE_LOCK) { if (directoryServer.shuttingDown) { @@ -4319,7 +4341,9 @@ // doing that, destroy the previous instance. DirectoryEnvironmentConfig envConfig = directoryServer.environmentConfig; directoryServer.destroy(); directoryServer = getNewInstance(envConfig); // getNewInstance() publishes the new instance under LIFECYCLE_LOCK: assigning its // result here again would repeat that write outside the lock. getNewInstance(envConfig); } /** opendj-server-legacy/src/main/java/org/opends/server/loggers/DebugLogPublisher.java
@@ -13,13 +13,13 @@ * * Copyright 2009 Sun Microsystems, Inc. * Portions Copyright 2012-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.loggers; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.ConcurrentHashMap; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.server.config.server.DebugLogPublisherCfg; @@ -42,11 +42,24 @@ /** The default global settings key. */ private static final String GLOBAL= "_global"; /** The map of class names to their trace settings. */ private Map<String,TraceSettings> classTraceSettings; /** * The map of class names to their trace settings. * <p> * The getters below read this map without any lock, so the field is volatile * (the map is created lazily and must be published safely) and the map itself * is concurrent; all writes go through the synchronized mutators below. */ private volatile Map<String,TraceSettings> classTraceSettings; /** The map of class names to their method trace settings. */ private Map<String,Map<String,TraceSettings>> methodTraceSettings; /** * The map of class names to their method trace settings. * <p> * Concurrent for the same reason as {@link #classTraceSettings}. The nested maps * are concurrent as well: {@link DebugTracer} keeps the one returned by * {@link #getMethodSettings(String)} and iterates it while another thread may be * reconfiguring the publisher. */ private volatile Map<String,Map<String,TraceSettings>> methodTraceSettings; @@ -146,6 +159,10 @@ * {@code null} to set the trace settings for the * global scope. * @param settings The trace settings for the specified scope. * Must not be {@code null}. * @throws NullPointerException If {@code settings} is {@code null}: the trace * settings are held in concurrent maps, which do not accept * null values. */ public final void addTraceSettings(String scope, TraceSettings settings) { @@ -213,7 +230,7 @@ * {@code null} if no trace setting is defined for that * scope. */ final TraceSettings removeTraceSettings(String scope) final synchronized TraceSettings removeTraceSettings(String scope) { TraceSettings removedSettings = null; if (scope == null) { @@ -262,7 +279,7 @@ { if (classTraceSettings == null) { classTraceSettings = new HashMap<>(); classTraceSettings = new ConcurrentHashMap<>(); } classTraceSettings.put(className, settings); } @@ -280,12 +297,12 @@ String methodName, TraceSettings settings) { if (methodTraceSettings == null) { methodTraceSettings = new HashMap<>(); methodTraceSettings = new ConcurrentHashMap<>(); } Map<String, TraceSettings> methodLevels = methodTraceSettings.get(className); if (methodLevels == null) { methodLevels = new TreeMap<>(); methodLevels = new ConcurrentHashMap<>(); methodTraceSettings.put(className, methodLevels); } methodLevels.put(methodName, settings); opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfileStack.java
@@ -13,12 +13,12 @@ * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2014-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.plugins.profiler; import java.io.IOException; import org.forgerock.i18n.slf4j.LocalizedLogger; import org.forgerock.opendj.io.ASN1Reader; import org.forgerock.opendj.io.ASN1Writer; @@ -28,11 +28,6 @@ */ public class ProfileStack { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * The line number that will be used for stack frames in which the line number * is unknown but it is not a native method. @@ -240,20 +235,16 @@ @Override public boolean equals(Object o) { if (o == null) { return false; } else if (this == o) if (this == o) { return true; } try if (!(o instanceof ProfileStack)) { ProfileStack s = (ProfileStack) o; return false; } ProfileStack s = (ProfileStack) o; if (numFrames != s.numFrames) { return false; @@ -271,13 +262,6 @@ return true; } catch (Exception e) { logger.traceException(e); return false; } } opendj-server-legacy/src/main/java/org/opends/server/plugins/profiler/ProfileStackFrame.java
@@ -13,6 +13,7 @@ * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2014-2015 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.plugins.profiler; @@ -21,9 +22,6 @@ import java.util.Arrays; import java.util.HashMap; import org.forgerock.i18n.slf4j.LocalizedLogger; /** * This class defines a data structure for holding information about a stack * frame captured by the Directory Server profiler. It will contain the class @@ -34,11 +32,6 @@ public class ProfileStackFrame implements Comparable { private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass(); /** * The mapping between the line numbers for this stack frame and the * number of times that they were encountered. @@ -302,26 +295,17 @@ @Override public boolean equals(Object o) { if (o == null) { return false; } else if (this == o) if (this == o) { return true; } try if (!(o instanceof ProfileStackFrame)) { ProfileStackFrame f = (ProfileStackFrame) o; return className.equals(f.className) && methodName.equals(f.methodName); } catch (Exception e) { logger.traceException(e); return false; } ProfileStackFrame f = (ProfileStackFrame) o; return className.equals(f.className) && methodName.equals(f.methodName); } opendj-server-legacy/src/main/java/org/opends/server/util/cli/PointAdder.java
@@ -13,6 +13,7 @@ * * Copyright 2010 Sun Microsystems, Inc. * Portions Copyright 2014-2016 ForgeRock AS. * Portions Copyright 2026 3A Systems, LLC. */ package org.opends.server.util.cli; @@ -26,9 +27,12 @@ public class PointAdder implements Runnable { private final ConsoleApplication app; private Thread t; private boolean stopPointAdder; private boolean pointAdderStopped; /** Written by start() and read by stop(), hence volatile. */ private volatile Thread t; /** Written by stop() and read by the point adder thread, hence volatile. */ private volatile boolean stopPointAdder; /** Written by the point adder thread and read by stop(), hence volatile. */ private volatile boolean pointAdderStopped; private final long periodTime; private final ProgressMessageFormatter formatter; @@ -78,20 +82,33 @@ t.start(); } /** Stops the PointAdder: points are no longer added at the end of the logs periodically. */ public synchronized void stop() /** * Stops the PointAdder: points are no longer added at the end of the logs periodically. * <p> * Calling this method on a PointAdder that was never started is a no-op: callers commonly * create the PointAdder before the code path that starts it and stop it from a * {@code finally} block. */ public void stop() { stopPointAdder = true; final Thread thread = t; if (thread == null) { return; } while (!pointAdderStopped) { thread.interrupt(); try { t.interrupt(); // To allow the thread to set the boolean. Thread.sleep(100); } catch (Throwable t) catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } opendj-server-legacy/src/test/java/org/opends/admin/ads/SuffixDescriptorTest.java
New file @@ -0,0 +1,132 @@ /* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.admin.ads; import static org.assertj.core.api.Assertions.*; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.forgerock.opendj.ldap.DN; import org.opends.server.DirectoryServerTestCase; import org.testng.annotations.Test; /** Tests the id-based {@code equals()}/{@code hashCode()} contract of {@link SuffixDescriptor}. */ @Test(groups = { "precommit", "admin" }, sequential = true) @SuppressWarnings("javadoc") public class SuffixDescriptorTest extends DirectoryServerTestCase { private static final DN SUFFIX_DN = DN.valueOf("dc=example,dc=com"); private static final DN OTHER_SUFFIX_DN = DN.valueOf("dc=example,dc=org"); @Test public void suffixesWithTheSameIdAreEqual() { ServerDescriptor server1 = server("server1.example.com"); ServerDescriptor server2 = server("server2.example.com"); SuffixDescriptor suffix1 = suffix(SUFFIX_DN, server1, server2); SuffixDescriptor suffix2 = suffix(SUFFIX_DN, server1, server2); assertThat(suffix1.getId()).isEqualTo(suffix2.getId()); assertThat(suffix1).isEqualTo(suffix2); assertThat(suffix1.hashCode()).isEqualTo(suffix2.hashCode()); assertThat(suffix1.compareTo(suffix2)).isZero(); } @Test public void suffixesWithTheSameIdCollapseInASet() { ServerDescriptor server = server("server1.example.com"); Set<SuffixDescriptor> suffixes = new HashSet<>(); suffixes.add(suffix(SUFFIX_DN, server)); suffixes.add(suffix(SUFFIX_DN, server)); assertThat(suffixes).hasSize(1); } @Test public void suffixesOnDifferentDnsAreNotEqual() { ServerDescriptor server = server("server1.example.com"); assertThat(suffix(SUFFIX_DN, server)).isNotEqualTo(suffix(OTHER_SUFFIX_DN, server)); } @Test public void suffixesOnDifferentServersAreNotEqual() { SuffixDescriptor suffix1 = suffix(SUFFIX_DN, server("server1.example.com")); SuffixDescriptor suffix2 = suffix(SUFFIX_DN, server("server2.example.com")); assertThat(suffix1).isNotEqualTo(suffix2); } @Test public void suffixIsNotEqualToItsId() { SuffixDescriptor suffix = suffix(SUFFIX_DN, server("server1.example.com")); assertThat(suffix.equals(suffix.getId())).isFalse(); assertThat(suffix.equals(null)).isFalse(); } /** * The id must not depend on the iteration order of the replica set: {@code getReplicas()} * hands out a {@code HashSet} of {@code ReplicaDescriptor}s, which do not override * {@code hashCode()}. */ @Test public void idIsIndependentOfTheReplicaOrder() { ServerDescriptor server1 = server("server1.example.com"); ServerDescriptor server2 = server("server2.example.com"); assertThat(server1.getId()).isLessThan(server2.getId()); String expectedId = SUFFIX_DN + "-" + server1.getId() + "-" + server2.getId(); assertThat(suffix(SUFFIX_DN, server1, server2).getId()).isEqualTo(expectedId); assertThat(suffix(SUFFIX_DN, server2, server1).getId()).isEqualTo(expectedId); } private ServerDescriptor server(String hostName) { Map<ADSContext.ServerProperty, Object> adsProperties = new HashMap<>(); adsProperties.put(ADSContext.ServerProperty.HOST_NAME, hostName); adsProperties.put(ADSContext.ServerProperty.LDAP_PORT, 1389); return ServerDescriptor.createStandalone(adsProperties); } private SuffixDescriptor suffix(DN suffixDN, ServerDescriptor... servers) { SuffixDescriptor suffix = new SuffixDescriptor(suffixDN, replica(servers[0])); for (int i = 1; i < servers.length; i++) { suffix.addReplica(replica(servers[i])); } return suffix; } private ReplicaDescriptor replica(ServerDescriptor server) { ReplicaDescriptor replica = new ReplicaDescriptor(); replica.setServer(server); return replica; } } opendj-server-legacy/src/test/java/org/opends/guitools/controlpanel/datamodel/ScheduleTypeTest.java
New file @@ -0,0 +1,77 @@ /* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2026 3A Systems, LLC. */ package org.opends.guitools.controlpanel.datamodel; import static org.assertj.core.api.Assertions.*; import java.util.Date; import org.opends.server.DirectoryServerTestCase; import org.testng.annotations.Test; /** Tests the {@code equals()} contract of {@link ScheduleType}. */ @Test(groups = { "precommit", "controlpanel" }, sequential = true) @SuppressWarnings("javadoc") public class ScheduleTypeTest extends DirectoryServerTestCase { @Test public void schedulesOfTheSameTypeAreEqual() { ScheduleType schedule1 = ScheduleType.createCron("0 0 * * *"); ScheduleType schedule2 = ScheduleType.createCron("0 0 * * *"); assertThat(schedule1).isEqualTo(schedule2); assertThat(schedule1.hashCode()).isEqualTo(schedule2.hashCode()); } @Test public void schedulesOfDifferentTypesAreNotEqual() { ScheduleType launchNow = ScheduleType.createLaunchNow(); ScheduleType launchLater = ScheduleType.createLaunchLater(new Date(0)); assertThat(launchNow).isNotEqualTo(launchLater); assertThat(launchNow).isNotEqualTo(ScheduleType.createCron("0 0 * * *")); } /** * A schedule must not compare equal to a plain object sharing its textual form: such a * comparison could never be symmetric. */ @Test public void scheduleIsNotEqualToAStringWithTheSameTextualForm() { ScheduleType schedule = ScheduleType.createLaunchNow(); String sameText = schedule.toString(); assertThat(schedule.equals(sameText)).isFalse(); assertThat(sameText.equals(schedule)).isFalse(); } @Test public void scheduleIsNotEqualToNull() { assertThat(ScheduleType.createLaunchNow().equals(null)).isFalse(); } @Test public void scheduleIsEqualToItself() { ScheduleType schedule = ScheduleType.createLaunchNow(); assertThat(schedule).isEqualTo(schedule); } }