or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

configuration-api.mdindex.mdmultiple-class-api.mdrelaxed-equality-api.mdsingle-class-api.mdwarning-system.md

relaxed-equality-api.mddocs/

0

# Relaxed Equality API

1

2

Specialized verification for classes with relaxed equality rules where multiple instances can be equal despite different internal state. This is common in normalized representations, value objects, and classes that implement canonical forms.

3

4

## Capabilities

5

6

### Relaxed Equal Examples

7

8

Creates a verifier for classes where multiple distinct instances can be equal to each other.

9

10

```java { .api }

11

/**

12

* Factory method. Asks for a list of equal, but not identical, instances of T.

13

*

14

* For use when T is a class which has relaxed equality rules. This happens when two

15

* instances of T are equal even though the its internal state is different.

16

*

17

* This could happen, for example, in a Rational class that doesn't normalize: new

18

* Rational(1, 2).equals(new Rational(2, 4)) would return true.

19

*

20

* Using this factory method requires that andUnequalExamples be called to supply a list of

21

* unequal instances of T.

22

*

23

* This method automatically suppresses ALL_FIELDS_SHOULD_BE_USED.

24

*

25

* @param first An instance of T

26

* @param second Another instance of T, which is equal, but not identical, to first

27

* @param more More instances of T, all of which are equal, but not identical, to one another

28

* and to first and second

29

* @return A fluent API for a more relaxed EqualsVerifier

30

*/

31

@SafeVarargs

32

public static <T> RelaxedEqualsVerifierApi<T> forRelaxedEqualExamples(

33

T first,

34

T second,

35

T... more

36

);

37

```

38

39

**Usage Examples:**

40

41

```java

42

import nl.jqno.equalsverifier.EqualsVerifier;

43

44

// Example with rational numbers that don't normalize

45

Rational oneHalf = new Rational(1, 2);

46

Rational twoQuarters = new Rational(2, 4);

47

Rational fourEighths = new Rational(4, 8);

48

49

EqualsVerifier.forRelaxedEqualExamples(oneHalf, twoQuarters, fourEighths)

50

.andUnequalExamples(new Rational(1, 3), new Rational(3, 4))

51

.verify();

52

53

// Example with normalized string representations

54

NormalizedString str1 = new NormalizedString(" Hello World ");

55

NormalizedString str2 = new NormalizedString("hello world");

56

NormalizedString str3 = new NormalizedString("HELLO WORLD");

57

58

EqualsVerifier.forRelaxedEqualExamples(str1, str2, str3)

59

.andUnequalExamples(new NormalizedString("Different Text"))

60

.verify();

61

```

62

63

### Adding Unequal Examples

64

65

Provides unequal examples to complete the relaxed verification setup.

66

67

```java { .api }

68

/**

69

* Provides single unequal example and returns SingleTypeEqualsVerifierApi

70

* @param example An instance of T that is not equal to any of the equal examples

71

* @return A SingleTypeEqualsVerifierApi for further configuration and verification

72

*/

73

public SingleTypeEqualsVerifierApi<T> andUnequalExample(T example);

74

75

/**

76

* Provides multiple unequal examples and returns SingleTypeEqualsVerifierApi

77

* @param first An instance of T that is not equal to any of the equal examples

78

* @param more More instances of T that are not equal to any of the equal examples

79

* @return A SingleTypeEqualsVerifierApi for further configuration and verification

80

*/

81

@SafeVarargs

82

public final SingleTypeEqualsVerifierApi<T> andUnequalExamples(T first, T... more);

83

```

84

85

**Usage Examples:**

86

87

```java

88

// Single unequal example

89

EqualsVerifier.forRelaxedEqualExamples(oneHalf, twoQuarters)

90

.andUnequalExample(new Rational(1, 3))

91

.verify();

92

93

// Multiple unequal examples

94

EqualsVerifier.forRelaxedEqualExamples(oneHalf, twoQuarters, fourEighths)

95

.andUnequalExamples(

96

new Rational(1, 3),

97

new Rational(3, 4),

98

new Rational(2, 3)

99

)

100

.verify();

101

102

// With additional configuration

103

EqualsVerifier.forRelaxedEqualExamples(normalizedStr1, normalizedStr2)

104

.andUnequalExamples(differentStr1, differentStr2)

105

.suppress(Warning.NONFINAL_FIELDS)

106

.verify();

107

```

108

109

### Common Use Cases

110

111

**Rational Number Classes:**

112

113

```java

114

public class Rational {

115

private final int numerator;

116

private final int denominator;

117

118

public Rational(int numerator, int denominator) {

119

// Note: This implementation doesn't normalize fractions

120

this.numerator = numerator;

121

this.denominator = denominator;

122

}

123

124

@Override

125

public boolean equals(Object obj) {

126

if (!(obj instanceof Rational)) return false;

127

Rational other = (Rational) obj;

128

// Cross multiplication to check equality without normalization

129

return this.numerator * other.denominator == other.numerator * this.denominator;

130

}

131

132

@Override

133

public int hashCode() {

134

// Normalize for consistent hash codes

135

int gcd = gcd(numerator, denominator);

136

return Objects.hash(numerator / gcd, denominator / gcd);

137

}

138

}

139

140

// Test with relaxed equality

141

@Test

142

public void testRationalEquals() {

143

Rational oneHalf = new Rational(1, 2);

144

Rational twoQuarters = new Rational(2, 4);

145

Rational threeHalves = new Rational(3, 6); // Actually equals 1/2

146

147

EqualsVerifier.forRelaxedEqualExamples(oneHalf, twoQuarters, threeHalves)

148

.andUnequalExamples(new Rational(1, 3), new Rational(2, 3))

149

.verify();

150

}

151

```

152

153

**Case-Insensitive String Wrappers:**

154

155

```java

156

public class CaseInsensitiveString {

157

private final String value;

158

159

public CaseInsensitiveString(String value) {

160

this.value = value;

161

}

162

163

@Override

164

public boolean equals(Object obj) {

165

if (!(obj instanceof CaseInsensitiveString)) return false;

166

CaseInsensitiveString other = (CaseInsensitiveString) obj;

167

return this.value.equalsIgnoreCase(other.value);

168

}

169

170

@Override

171

public int hashCode() {

172

return value.toLowerCase().hashCode();

173

}

174

}

175

176

// Test with relaxed equality

177

@Test

178

public void testCaseInsensitiveStringEquals() {

179

CaseInsensitiveString lower = new CaseInsensitiveString("hello");

180

CaseInsensitiveString upper = new CaseInsensitiveString("HELLO");

181

CaseInsensitiveString mixed = new CaseInsensitiveString("HeLLo");

182

183

EqualsVerifier.forRelaxedEqualExamples(lower, upper, mixed)

184

.andUnequalExamples(new CaseInsensitiveString("world"))

185

.verify();

186

}

187

```

188

189

**Normalized Path Classes:**

190

191

```java

192

public class NormalizedPath {

193

private final String path;

194

195

public NormalizedPath(String path) {

196

this.path = path; // Store original, normalize in equals/hashCode

197

}

198

199

@Override

200

public boolean equals(Object obj) {

201

if (!(obj instanceof NormalizedPath)) return false;

202

NormalizedPath other = (NormalizedPath) obj;

203

return normalize(this.path).equals(normalize(other.path));

204

}

205

206

@Override

207

public int hashCode() {

208

return normalize(this.path).hashCode();

209

}

210

211

private String normalize(String path) {

212

return path.replaceAll("/+", "/")

213

.replaceAll("/\\./", "/")

214

.replaceAll("/$", "");

215

}

216

}

217

218

// Test with relaxed equality

219

@Test

220

public void testNormalizedPathEquals() {

221

NormalizedPath path1 = new NormalizedPath("/home/user/documents");

222

NormalizedPath path2 = new NormalizedPath("/home//user/./documents/");

223

NormalizedPath path3 = new NormalizedPath("/home/user/documents/");

224

225

EqualsVerifier.forRelaxedEqualExamples(path1, path2, path3)

226

.andUnequalExamples(new NormalizedPath("/home/user/downloads"))

227

.verify();

228

}

229

```

230

231

**URL Classes with Different Representations:**

232

233

```java

234

public class FlexibleUrl {

235

private final String scheme;

236

private final String host;

237

private final Integer port;

238

private final String path;

239

240

public FlexibleUrl(String url) {

241

// Parse URL and store components

242

// This constructor could create different internal representations

243

// for the same logical URL

244

}

245

246

@Override

247

public boolean equals(Object obj) {

248

if (!(obj instanceof FlexibleUrl)) return false;

249

FlexibleUrl other = (FlexibleUrl) obj;

250

251

// Normalize comparison - default ports, case-insensitive hosts, etc.

252

return normalizeScheme(this.scheme).equals(normalizeScheme(other.scheme)) &&

253

normalizeHost(this.host).equals(normalizeHost(other.host)) &&

254

normalizePort(this.port, this.scheme).equals(normalizePort(other.port, other.scheme)) &&

255

normalizePath(this.path).equals(normalizePath(other.path));

256

}

257

258

@Override

259

public int hashCode() {

260

return Objects.hash(

261

normalizeScheme(scheme),

262

normalizeHost(host),

263

normalizePort(port, scheme),

264

normalizePath(path)

265

);

266

}

267

}

268

269

// Test with relaxed equality

270

@Test

271

public void testFlexibleUrlEquals() {

272

FlexibleUrl url1 = new FlexibleUrl("http://Example.COM:80/path/");

273

FlexibleUrl url2 = new FlexibleUrl("HTTP://example.com/path");

274

FlexibleUrl url3 = new FlexibleUrl("http://example.com:80/path/");

275

276

EqualsVerifier.forRelaxedEqualExamples(url1, url2, url3)

277

.andUnequalExamples(new FlexibleUrl("https://example.com/path"))

278

.verify();

279

}

280

```

281

282

### Advanced Configuration

283

284

Since `RelaxedEqualsVerifierApi` returns a `SingleTypeEqualsVerifierApi`, all single-class configuration options are available:

285

286

```java

287

// Complex relaxed equality verification with full configuration

288

EqualsVerifier.forRelaxedEqualExamples(

289

new ComplexRational(1, 2, metadata1),

290

new ComplexRational(2, 4, metadata2),

291

new ComplexRational(4, 8, metadata3)

292

)

293

.andUnequalExamples(new ComplexRational(1, 3, metadata4))

294

.suppress(Warning.NONFINAL_FIELDS)

295

.withPrefabValues(Metadata.class, redMetadata, blueMetadata)

296

.withIgnoredFields("creationTime", "lastModified")

297

.verify();

298

```

299

300

### Important Notes

301

302

1. **Automatic Warning Suppression**: The `forRelaxedEqualExamples` method automatically suppresses `Warning.ALL_FIELDS_SHOULD_BE_USED` because relaxed equality typically doesn't use all fields in the same way.

303

304

2. **Equal Examples Requirement**: All examples provided to `forRelaxedEqualExamples` must be equal to each other according to the class's `equals` method.

305

306

3. **Unequal Examples Requirement**: All examples provided to `andUnequalExamples` must not be equal to any of the equal examples.

307

308

4. **hashCode Consistency**: The class must still maintain the contract that equal objects have equal hash codes, even if they have different internal representations.