or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mdattributes.mdcore-immutable.mdindex.mdstyle-configuration.mdvalidation.md

attributes.mddocs/

0

# Attribute Customization

1

2

Advanced attribute behavior annotations for flexible object modeling including default values, lazy computation, derived values, parameter configuration, and auxiliary attributes.

3

4

## Capabilities

5

6

### Default Values

7

8

Specify default values for attributes that are optional during construction.

9

10

```java { .api }

11

/**

12

* Annotates accessor that should be turned into settable generated attribute

13

* that is non-mandatory to set via builder. Default value obtained by calling

14

* the annotated method.

15

*/

16

@interface Value.Default { }

17

```

18

19

**Usage Examples:**

20

21

```java

22

@Value.Immutable

23

public interface Server {

24

String host();

25

26

@Value.Default

27

default int port() { return 8080; }

28

29

@Value.Default

30

default boolean ssl() { return false; }

31

32

@Value.Default

33

default List<String> tags() { return List.of(); }

34

}

35

36

// Builder usage - default values are optional

37

Server server1 = ImmutableServer.builder()

38

.host("localhost")

39

.build(); // port=8080, ssl=false, tags=[]

40

41

Server server2 = ImmutableServer.builder()

42

.host("example.com")

43

.port(443)

44

.ssl(true)

45

.addTags("production", "web")

46

.build();

47

```

48

49

### Lazy Attributes

50

51

Thread-safe lazy computation of expensive attribute values.

52

53

```java { .api }

54

/**

55

* Lazy attributes cannot be set during building but are computed lazily

56

* once and only once in a thread-safe manner. Computed from other attributes.

57

* Always act as auxiliary (excluded from equals, hashCode, toString).

58

*/

59

@interface Value.Lazy { }

60

```

61

62

**Usage Examples:**

63

64

```java

65

@Value.Immutable

66

public interface Order {

67

List<Item> items();

68

69

@Value.Lazy

70

default BigDecimal totalCost() {

71

return items().stream()

72

.map(item -> item.price().multiply(BigDecimal.valueOf(item.quantity())))

73

.reduce(BigDecimal.ZERO, BigDecimal::add);

74

}

75

76

@Value.Lazy

77

default String summary() {

78

return String.format("Order with %d items, total: $%.2f",

79

items().size(), totalCost());

80

}

81

}

82

83

// Usage - lazy values computed on first access

84

Order order = ImmutableOrder.builder()

85

.addItems(item1, item2, item3)

86

.build();

87

88

// First access triggers computation and caches result

89

BigDecimal cost = order.totalCost(); // Computes and caches

90

BigDecimal sameCost = order.totalCost(); // Returns cached value

91

```

92

93

### Derived Attributes

94

95

Eagerly computed attributes that are calculated from other attributes during construction.

96

97

```java { .api }

98

/**

99

* Derived attributes cannot be set during building but are eagerly computed

100

* from other attributes and stored in field. Should be applied to non-abstract

101

* method that serves as the attribute value initializer.

102

*/

103

@interface Value.Derived { }

104

```

105

106

**Usage Examples:**

107

108

```java

109

@Value.Immutable

110

public interface Rectangle {

111

double width();

112

double height();

113

114

@Value.Derived

115

default double area() {

116

return width() * height();

117

}

118

119

@Value.Derived

120

default double perimeter() {

121

return 2 * (width() + height());

122

}

123

124

@Value.Derived

125

default String dimensions() {

126

return width() + "x" + height();

127

}

128

}

129

130

// Derived values are computed during construction

131

Rectangle rect = ImmutableRectangle.builder()

132

.width(10.0)

133

.height(5.0)

134

.build();

135

136

// Values are already computed and stored

137

double area = rect.area(); // Returns pre-computed 50.0

138

String dims = rect.dimensions(); // Returns pre-computed "10.0x5.0"

139

```

140

141

### Parameter Configuration

142

143

Control which attributes become constructor parameters and their ordering.

144

145

```java { .api }

146

/**

147

* Mark abstract accessor method to be included as constructor parameter.

148

* For constructable objects, all non-default and non-derived attributes

149

* should be annotated with @Value.Parameter.

150

*/

151

@interface Value.Parameter {

152

/**

153

* Specify order of constructor argument. Defaults to -1 (unspecified).

154

* Arguments are sorted ascending by this order value.

155

*/

156

int order() default -1;

157

158

/**

159

* Whether to include as parameter. Set false to cancel out parameter

160

* (useful with Style.allParameters flag).

161

*/

162

boolean value() default true;

163

}

164

```

165

166

**Usage Examples:**

167

168

```java

169

@Value.Immutable

170

public interface Person {

171

@Value.Parameter(order = 1)

172

String firstName();

173

174

@Value.Parameter(order = 2)

175

String lastName();

176

177

@Value.Parameter(order = 3)

178

int age();

179

180

@Value.Default

181

default String email() { return ""; }

182

}

183

184

// Generated constructor: ImmutablePerson.of(firstName, lastName, age)

185

Person person = ImmutablePerson.of("John", "Doe", 30);

186

187

// Builder still available

188

Person person2 = ImmutablePerson.builder()

189

.firstName("Jane")

190

.lastName("Smith")

191

.age(25)

192

.email("jane@example.com")

193

.build();

194

195

// Exclude from parameters while using allParameters style

196

@Value.Immutable

197

@Value.Style(allParameters = true)

198

public interface Coordinate {

199

double x();

200

double y();

201

202

@Value.Parameter(false) // Exclude from constructor

203

@Value.Default

204

default String label() { return ""; }

205

}

206

207

// Constructor: ImmutableCoordinate.of(x, y) - label excluded

208

```

209

210

### Auxiliary Attributes

211

212

Attributes that are stored and accessible but excluded from equals, hashCode, and toString methods.

213

214

```java { .api }

215

/**

216

* Annotate attribute as auxiliary - will be stored and accessible but

217

* excluded from generated equals(), hashCode() and toString() methods.

218

* Lazy attributes are always auxiliary.

219

*/

220

@interface Value.Auxiliary { }

221

```

222

223

**Usage Examples:**

224

225

```java

226

@Value.Immutable

227

public interface CacheEntry {

228

String key();

229

Object value();

230

231

@Value.Auxiliary

232

long createdTimestamp();

233

234

@Value.Auxiliary

235

int accessCount();

236

237

@Value.Auxiliary

238

@Value.Default

239

default String debugInfo() { return ""; }

240

}

241

242

// Auxiliary attributes don't affect equality

243

CacheEntry entry1 = ImmutableCacheEntry.builder()

244

.key("user:123")

245

.value(userData)

246

.createdTimestamp(System.currentTimeMillis())

247

.accessCount(0)

248

.build();

249

250

CacheEntry entry2 = ImmutableCacheEntry.builder()

251

.key("user:123")

252

.value(userData)

253

.createdTimestamp(System.currentTimeMillis() + 1000) // Different timestamp

254

.accessCount(5) // Different access count

255

.build();

256

257

// These are considered equal (auxiliary attributes ignored)

258

assert entry1.equals(entry2); // true

259

assert entry1.hashCode() == entry2.hashCode(); // true

260

261

// But auxiliary values are still accessible

262

long timestamp = entry1.createdTimestamp();

263

int count = entry1.accessCount();

264

```

265

266

### Attribute Validation

267

268

Mark methods as non-attributes to prevent them from becoming generated attributes.

269

270

```java { .api }

271

/**

272

* Mark some abstract no-argument methods in supertypes as regular,

273

* non-attribute methods. Prevents annotation processor from generating

274

* field, accessor, and builder initializer.

275

*/

276

@interface Value.NonAttribute { }

277

```

278

279

**Usage Examples:**

280

281

```java

282

// Base interface with methods that shouldn't be attributes

283

public interface Validatable {

284

@Value.NonAttribute

285

default boolean isValid() {

286

// Custom validation logic

287

return true;

288

}

289

290

@Value.NonAttribute

291

default List<String> validate() {

292

// Return validation errors

293

return List.of();

294

}

295

}

296

297

@Value.Immutable

298

public interface User extends Validatable {

299

String name();

300

String email();

301

302

// Override validation methods

303

@Override

304

default boolean isValid() {

305

return !name().isEmpty() && email().contains("@");

306

}

307

308

@Override

309

default List<String> validate() {

310

List<String> errors = new ArrayList<>();

311

if (name().isEmpty()) errors.add("Name is required");

312

if (!email().contains("@")) errors.add("Invalid email format");

313

return errors;

314

}

315

}

316

317

// isValid() and validate() are regular methods, not attributes

318

User user = ImmutableUser.builder()

319

.name("John")

320

.email("john@example.com")

321

.build();

322

323

boolean valid = user.isValid(); // Method call, not attribute access

324

List<String> errors = user.validate(); // Method call, not attribute access

325

```