or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

advanced-features.mderror-handling.mdevaluation-contexts.mdexpression-evaluation.mdindex.mdmethod-constructor-resolution.mdproperty-index-access.mdspel-configuration.mdtype-system-support.md

property-index-access.mddocs/

0

# Property and Index Access

1

2

This document covers SpEL's property and index access capabilities, including accessor interfaces, standard implementations, and custom accessor development.

3

4

## Core Accessor Interfaces

5

6

### TargetedAccessor Interface

7

8

```java

9

public interface TargetedAccessor {

10

Class<?>[] getSpecificTargetClasses();

11

}

12

```

13

{ .api }

14

15

The base interface for accessors that target specific classes for optimization.

16

17

### PropertyAccessor Interface

18

19

```java

20

public interface PropertyAccessor extends TargetedAccessor {

21

boolean canRead(EvaluationContext context, Object target, String name) throws AccessException;

22

TypedValue read(EvaluationContext context, Object target, String name) throws AccessException;

23

boolean canWrite(EvaluationContext context, Object target, String name) throws AccessException;

24

void write(EvaluationContext context, Object target, String name, Object newValue)

25

throws AccessException;

26

}

27

```

28

{ .api }

29

30

### IndexAccessor Interface

31

32

```java

33

public interface IndexAccessor extends TargetedAccessor {

34

boolean canRead(EvaluationContext context, Object target, Object index) throws AccessException;

35

TypedValue read(EvaluationContext context, Object target, Object index) throws AccessException;

36

boolean canWrite(EvaluationContext context, Object target, Object index) throws AccessException;

37

void write(EvaluationContext context, Object target, Object index, Object newValue)

38

throws AccessException;

39

}

40

```

41

{ .api }

42

43

## Standard Property Accessors

44

45

### ReflectivePropertyAccessor Class

46

47

```java

48

public class ReflectivePropertyAccessor implements PropertyAccessor {

49

public ReflectivePropertyAccessor();

50

public ReflectivePropertyAccessor(boolean allowWrite);

51

52

@Override

53

public Class<?>[] getSpecificTargetClasses();

54

55

@Override

56

public boolean canRead(EvaluationContext context, Object target, String name)

57

throws AccessException;

58

@Override

59

public TypedValue read(EvaluationContext context, Object target, String name)

60

throws AccessException;

61

@Override

62

public boolean canWrite(EvaluationContext context, Object target, String name)

63

throws AccessException;

64

@Override

65

public void write(EvaluationContext context, Object target, String name, Object newValue)

66

throws AccessException;

67

}

68

```

69

{ .api }

70

71

The default property accessor that uses Java reflection to access object properties via getter/setter methods and fields.

72

73

### DataBindingPropertyAccessor Class

74

75

```java

76

public class DataBindingPropertyAccessor implements PropertyAccessor {

77

@Override

78

public Class<?>[] getSpecificTargetClasses();

79

80

@Override

81

public boolean canRead(EvaluationContext context, Object target, String name)

82

throws AccessException;

83

@Override

84

public TypedValue read(EvaluationContext context, Object target, String name)

85

throws AccessException;

86

@Override

87

public boolean canWrite(EvaluationContext context, Object target, String name)

88

throws AccessException;

89

@Override

90

public void write(EvaluationContext context, Object target, String name, Object newValue)

91

throws AccessException;

92

}

93

```

94

{ .api }

95

96

Optimized property accessor for data-binding scenarios with better performance characteristics.

97

98

### Property Access Examples

99

100

```java

101

public class Person {

102

private String name;

103

private int age;

104

private boolean active;

105

106

// Standard getters and setters

107

public String getName() { return name; }

108

public void setName(String name) { this.name = name; }

109

public int getAge() { return age; }

110

public void setAge(int age) { this.age = age; }

111

public boolean isActive() { return active; }

112

public void setActive(boolean active) { this.active = active; }

113

}

114

115

// Using ReflectivePropertyAccessor

116

StandardEvaluationContext context = new StandardEvaluationContext();

117

context.addPropertyAccessor(new ReflectivePropertyAccessor());

118

119

Person person = new Person();

120

person.setName("John");

121

person.setAge(30);

122

person.setActive(true);

123

124

ExpressionParser parser = new SpelExpressionParser();

125

126

// Reading properties

127

Expression exp = parser.parseExpression("name");

128

String name = exp.getValue(context, person, String.class); // "John"

129

130

exp = parser.parseExpression("age");

131

Integer age = exp.getValue(context, person, Integer.class); // 30

132

133

exp = parser.parseExpression("active");

134

Boolean active = exp.getValue(context, person, Boolean.class); // true

135

136

// Writing properties

137

exp = parser.parseExpression("name");

138

exp.setValue(context, person, "Jane");

139

// person.getName() returns "Jane"

140

141

exp = parser.parseExpression("age");

142

exp.setValue(context, person, 25);

143

// person.getAge() returns 25

144

```

145

{ .api }

146

147

## Standard Index Accessors

148

149

### ReflectiveIndexAccessor Class

150

151

```java

152

public class ReflectiveIndexAccessor implements CompilableIndexAccessor {

153

@Override

154

public Class<?>[] getSpecificTargetClasses();

155

156

@Override

157

public boolean canRead(EvaluationContext context, Object target, Object index)

158

throws AccessException;

159

@Override

160

public TypedValue read(EvaluationContext context, Object target, Object index)

161

throws AccessException;

162

@Override

163

public boolean canWrite(EvaluationContext context, Object target, Object index)

164

throws AccessException;

165

@Override

166

public void write(EvaluationContext context, Object target, Object index, Object newValue)

167

throws AccessException;

168

169

// CompilableIndexAccessor methods for compilation support

170

public boolean isCompilable();

171

public String generateCode(String indexedObjectName, String index, CodeFlow cf);

172

}

173

```

174

{ .api }

175

176

### Index Access Examples

177

178

```java

179

// Array access

180

int[] numbers = {1, 2, 3, 4, 5};

181

StandardEvaluationContext context = new StandardEvaluationContext();

182

context.addIndexAccessor(new ReflectiveIndexAccessor());

183

context.setVariable("numbers", numbers);

184

185

ExpressionParser parser = new SpelExpressionParser();

186

187

// Reading array elements

188

Expression exp = parser.parseExpression("#numbers[0]");

189

Integer first = exp.getValue(context, Integer.class); // 1

190

191

exp = parser.parseExpression("#numbers[2]");

192

Integer third = exp.getValue(context, Integer.class); // 3

193

194

// Writing array elements

195

exp = parser.parseExpression("#numbers[0]");

196

exp.setValue(context, 10);

197

// numbers[0] is now 10

198

199

// List access

200

List<String> names = new ArrayList<>(Arrays.asList("John", "Jane", "Bob"));

201

context.setVariable("names", names);

202

203

exp = parser.parseExpression("#names[1]");

204

String name = exp.getValue(context, String.class); // "Jane"

205

206

exp = parser.parseExpression("#names[1]");

207

exp.setValue(context, "Janet");

208

// names.get(1) returns "Janet"

209

210

// Map access

211

Map<String, Integer> ages = new HashMap<>();

212

ages.put("John", 30);

213

ages.put("Jane", 25);

214

context.setVariable("ages", ages);

215

216

exp = parser.parseExpression("#ages['John']");

217

Integer johnAge = exp.getValue(context, Integer.class); // 30

218

219

exp = parser.parseExpression("#ages['Jane']");

220

exp.setValue(context, 26);

221

// ages.get("Jane") returns 26

222

223

// String indexing (read-only)

224

String text = "Hello";

225

context.setVariable("text", text);

226

227

exp = parser.parseExpression("#text[1]");

228

String character = exp.getValue(context, String.class); // "e"

229

```

230

{ .api }

231

232

## Compilation Support

233

234

### CompilablePropertyAccessor Interface

235

236

```java

237

public interface CompilablePropertyAccessor extends PropertyAccessor, Opcodes {

238

boolean isCompilable();

239

String generateCode(String propertyName, String target, CodeFlow cf);

240

}

241

```

242

{ .api }

243

244

### CompilableIndexAccessor Interface

245

246

```java

247

public interface CompilableIndexAccessor extends IndexAccessor, Opcodes {

248

boolean isCompilable();

249

String generateCode(String indexedObjectName, String index, CodeFlow cf);

250

}

251

```

252

{ .api }

253

254

## Custom Property Accessors

255

256

### Basic Custom Property Accessor

257

258

```java

259

public class CustomObjectPropertyAccessor implements PropertyAccessor {

260

261

@Override

262

public Class<?>[] getSpecificTargetClasses() {

263

return new Class<?>[] { CustomObject.class };

264

}

265

266

@Override

267

public boolean canRead(EvaluationContext context, Object target, String name)

268

throws AccessException {

269

return target instanceof CustomObject &&

270

((CustomObject) target).hasProperty(name);

271

}

272

273

@Override

274

public TypedValue read(EvaluationContext context, Object target, String name)

275

throws AccessException {

276

if (target instanceof CustomObject) {

277

CustomObject obj = (CustomObject) target;

278

Object value = obj.getProperty(name);

279

return new TypedValue(value);

280

}

281

throw new AccessException("Cannot read property: " + name);

282

}

283

284

@Override

285

public boolean canWrite(EvaluationContext context, Object target, String name)

286

throws AccessException {

287

return target instanceof CustomObject &&

288

((CustomObject) target).isPropertyWritable(name);

289

}

290

291

@Override

292

public void write(EvaluationContext context, Object target, String name, Object newValue)

293

throws AccessException {

294

if (target instanceof CustomObject) {

295

CustomObject obj = (CustomObject) target;

296

obj.setProperty(name, newValue);

297

} else {

298

throw new AccessException("Cannot write property: " + name);

299

}

300

}

301

}

302

303

// Custom object implementation

304

public class CustomObject {

305

private final Map<String, Object> properties = new HashMap<>();

306

private final Set<String> readOnlyProperties = new HashSet<>();

307

308

public boolean hasProperty(String name) {

309

return properties.containsKey(name);

310

}

311

312

public Object getProperty(String name) {

313

return properties.get(name);

314

}

315

316

public void setProperty(String name, Object value) {

317

if (!readOnlyProperties.contains(name)) {

318

properties.put(name, value);

319

}

320

}

321

322

public boolean isPropertyWritable(String name) {

323

return !readOnlyProperties.contains(name);

324

}

325

326

public void markReadOnly(String name) {

327

readOnlyProperties.add(name);

328

}

329

}

330

331

// Usage

332

CustomObject obj = new CustomObject();

333

obj.setProperty("name", "John");

334

obj.setProperty("status", "active");

335

obj.markReadOnly("status");

336

337

StandardEvaluationContext context = new StandardEvaluationContext(obj);

338

context.addPropertyAccessor(new CustomObjectPropertyAccessor());

339

340

ExpressionParser parser = new SpelExpressionParser();

341

342

// Reading custom properties

343

Expression exp = parser.parseExpression("name");

344

String name = exp.getValue(context, String.class); // "John"

345

346

// Writing to writable property

347

exp = parser.parseExpression("name");

348

exp.setValue(context, "Jane");

349

350

// Attempting to write to read-only property (will be ignored)

351

exp = parser.parseExpression("status");

352

exp.setValue(context, "inactive"); // No change due to read-only

353

```

354

{ .api }

355

356

### Map-based Property Accessor

357

358

```java

359

public class MapAccessor implements PropertyAccessor {

360

361

@Override

362

public Class<?>[] getSpecificTargetClasses() {

363

return new Class<?>[] { Map.class };

364

}

365

366

@Override

367

public boolean canRead(EvaluationContext context, Object target, String name)

368

throws AccessException {

369

return target instanceof Map;

370

}

371

372

@Override

373

public TypedValue read(EvaluationContext context, Object target, String name)

374

throws AccessException {

375

if (target instanceof Map) {

376

Map<?, ?> map = (Map<?, ?>) target;

377

Object value = map.get(name);

378

return new TypedValue(value);

379

}

380

throw new AccessException("Cannot read from non-Map object");

381

}

382

383

@Override

384

public boolean canWrite(EvaluationContext context, Object target, String name)

385

throws AccessException {

386

return target instanceof Map;

387

}

388

389

@Override

390

@SuppressWarnings("unchecked")

391

public void write(EvaluationContext context, Object target, String name, Object newValue)

392

throws AccessException {

393

if (target instanceof Map) {

394

Map<String, Object> map = (Map<String, Object>) target;

395

map.put(name, newValue);

396

} else {

397

throw new AccessException("Cannot write to non-Map object");

398

}

399

}

400

}

401

402

// Usage example

403

Map<String, Object> dataMap = new HashMap<>();

404

dataMap.put("user", "john");

405

dataMap.put("count", 42);

406

407

StandardEvaluationContext context = new StandardEvaluationContext(dataMap);

408

context.addPropertyAccessor(new MapAccessor());

409

410

ExpressionParser parser = new SpelExpressionParser();

411

412

// Access map values as properties

413

Expression exp = parser.parseExpression("user");

414

String user = exp.getValue(context, String.class); // "john"

415

416

exp = parser.parseExpression("count");

417

Integer count = exp.getValue(context, Integer.class); // 42

418

419

// Set new values

420

exp = parser.parseExpression("status");

421

exp.setValue(context, "active");

422

// dataMap now contains "status" -> "active"

423

```

424

{ .api }

425

426

## Custom Index Accessors

427

428

### Range-based Index Accessor

429

430

```java

431

public class RangeIndexAccessor implements IndexAccessor {

432

433

@Override

434

public Class<?>[] getSpecificTargetClasses() {

435

return new Class<?>[] { List.class, String.class };

436

}

437

438

@Override

439

public boolean canRead(EvaluationContext context, Object target, Object index)

440

throws AccessException {

441

return (target instanceof List || target instanceof String) &&

442

index instanceof Range;

443

}

444

445

@Override

446

public TypedValue read(EvaluationContext context, Object target, Object index)

447

throws AccessException {

448

if (index instanceof Range) {

449

Range range = (Range) index;

450

451

if (target instanceof List) {

452

List<?> list = (List<?>) target;

453

List<Object> result = new ArrayList<>();

454

for (int i = range.getStart(); i <= range.getEnd() && i < list.size(); i++) {

455

result.add(list.get(i));

456

}

457

return new TypedValue(result);

458

} else if (target instanceof String) {

459

String str = (String) target;

460

int start = Math.max(0, range.getStart());

461

int end = Math.min(str.length(), range.getEnd() + 1);

462

return new TypedValue(str.substring(start, end));

463

}

464

}

465

throw new AccessException("Unsupported range access");

466

}

467

468

@Override

469

public boolean canWrite(EvaluationContext context, Object target, Object index)

470

throws AccessException {

471

return false; // Read-only for simplicity

472

}

473

474

@Override

475

public void write(EvaluationContext context, Object target, Object index, Object newValue)

476

throws AccessException {

477

throw new AccessException("Range write not supported");

478

}

479

}

480

481

// Range class for index specification

482

public class Range {

483

private final int start;

484

private final int end;

485

486

public Range(int start, int end) {

487

this.start = start;

488

this.end = end;

489

}

490

491

public int getStart() { return start; }

492

public int getEnd() { return end; }

493

}

494

495

// Usage would require custom expression parsing or function registration

496

StandardEvaluationContext context = new StandardEvaluationContext();

497

context.addIndexAccessor(new RangeIndexAccessor());

498

499

// This would require extending the parser or using functions

500

// Example conceptual usage: list[range(0, 2)] -> first 3 elements

501

```

502

{ .api }

503

504

## Accessor Chain Management

505

506

### Accessor Priority and Ordering

507

508

```java

509

public class AccessorManager {

510

511

public static StandardEvaluationContext createOptimizedContext() {

512

StandardEvaluationContext context = new StandardEvaluationContext();

513

514

// Add accessors in priority order (most specific first)

515

context.addPropertyAccessor(new MapAccessor()); // Maps

516

context.addPropertyAccessor(new DataBindingPropertyAccessor()); // Optimized

517

context.addPropertyAccessor(new ReflectivePropertyAccessor()); // General

518

519

context.addIndexAccessor(new ReflectiveIndexAccessor()); // General indexing

520

521

return context;

522

}

523

524

public static void configureForSecurity(StandardEvaluationContext context) {

525

// Clear default accessors and add only safe ones

526

context.setPropertyAccessors(Arrays.asList(

527

new SafePropertyAccessor(),

528

new WhitelistPropertyAccessor()

529

));

530

531

context.setIndexAccessors(Arrays.asList(

532

new SafeIndexAccessor()

533

));

534

}

535

}

536

537

public class SafePropertyAccessor implements PropertyAccessor {

538

private final Set<Class<?>> allowedTypes = Set.of(

539

String.class, Number.class, Boolean.class, Date.class

540

);

541

542

@Override

543

public Class<?>[] getSpecificTargetClasses() {

544

return allowedTypes.toArray(new Class<?>[0]);

545

}

546

547

@Override

548

public boolean canRead(EvaluationContext context, Object target, String name) {

549

return allowedTypes.contains(target.getClass()) &&

550

isAllowedProperty(name);

551

}

552

553

private boolean isAllowedProperty(String name) {

554

// Whitelist approach

555

return name.matches("^[a-zA-Z][a-zA-Z0-9]*$") && // Simple names only

556

name.length() < 50; // Reasonable length

557

}

558

559

// ... implement other methods with safety checks

560

}

561

```

562

{ .api }

563

564

## Performance Optimization

565

566

### Cached Accessor Results

567

568

```java

569

public class CachingPropertyAccessor implements PropertyAccessor {

570

private final PropertyAccessor delegate;

571

private final Map<String, TypedValue> readCache = new ConcurrentHashMap<>();

572

private final long cacheTimeout;

573

private final Map<String, Long> cacheTimestamps = new ConcurrentHashMap<>();

574

575

public CachingPropertyAccessor(PropertyAccessor delegate, long cacheTimeoutMs) {

576

this.delegate = delegate;

577

this.cacheTimeout = cacheTimeoutMs;

578

}

579

580

@Override

581

public Class<?>[] getSpecificTargetClasses() {

582

return delegate.getSpecificTargetClasses();

583

}

584

585

@Override

586

public boolean canRead(EvaluationContext context, Object target, String name) {

587

return delegate.canRead(context, target, name);

588

}

589

590

@Override

591

public TypedValue read(EvaluationContext context, Object target, String name)

592

throws AccessException {

593

String cacheKey = target.getClass().getName() + ":" + name;

594

Long timestamp = cacheTimestamps.get(cacheKey);

595

596

if (timestamp != null &&

597

System.currentTimeMillis() - timestamp < cacheTimeout) {

598

TypedValue cached = readCache.get(cacheKey);

599

if (cached != null) {

600

return cached;

601

}

602

}

603

604

TypedValue result = delegate.read(context, target, name);

605

readCache.put(cacheKey, result);

606

cacheTimestamps.put(cacheKey, System.currentTimeMillis());

607

608

return result;

609

}

610

611

@Override

612

public boolean canWrite(EvaluationContext context, Object target, String name) {

613

return delegate.canWrite(context, target, name);

614

}

615

616

@Override

617

public void write(EvaluationContext context, Object target, String name, Object newValue)

618

throws AccessException {

619

delegate.write(context, target, name, newValue);

620

621

// Invalidate cache on write

622

String cacheKey = target.getClass().getName() + ":" + name;

623

readCache.remove(cacheKey);

624

cacheTimestamps.remove(cacheKey);

625

}

626

}

627

```

628

{ .api }

629

630

### Specialized Accessors for Performance

631

632

```java

633

// Highly optimized accessor for specific data structures

634

public class OptimizedDataObjectAccessor implements CompilablePropertyAccessor {

635

636

@Override

637

public Class<?>[] getSpecificTargetClasses() {

638

return new Class<?>[] { OptimizedDataObject.class };

639

}

640

641

@Override

642

public boolean canRead(EvaluationContext context, Object target, String name) {

643

return target instanceof OptimizedDataObject;

644

}

645

646

@Override

647

public TypedValue read(EvaluationContext context, Object target, String name)

648

throws AccessException {

649

OptimizedDataObject obj = (OptimizedDataObject) target;

650

651

// Use optimized access methods

652

return switch (name) {

653

case "id" -> new TypedValue(obj.getId());

654

case "name" -> new TypedValue(obj.getName());

655

case "status" -> new TypedValue(obj.getStatus());

656

default -> throw new AccessException("Unknown property: " + name);

657

};

658

}

659

660

@Override

661

public boolean canWrite(EvaluationContext context, Object target, String name) {

662

return target instanceof OptimizedDataObject &&

663

!"id".equals(name); // ID is read-only

664

}

665

666

@Override

667

public void write(EvaluationContext context, Object target, String name, Object newValue)

668

throws AccessException {

669

OptimizedDataObject obj = (OptimizedDataObject) target;

670

671

switch (name) {

672

case "name" -> obj.setName((String) newValue);

673

case "status" -> obj.setStatus((String) newValue);

674

default -> throw new AccessException("Cannot write property: " + name);

675

}

676

}

677

678

// Compilation support for even better performance

679

@Override

680

public boolean isCompilable() {

681

return true;

682

}

683

684

@Override

685

public String generateCode(String propertyName, String target, CodeFlow cf) {

686

// Generate bytecode for direct property access

687

return switch (propertyName) {

688

case "id" -> target + ".getId()";

689

case "name" -> target + ".getName()";

690

case "status" -> target + ".getStatus()";

691

default -> null;

692

};

693

}

694

}

695

```

696

{ .api }

697

698

## Best Practices

699

700

### Security Considerations

701

702

```java

703

// 1. Use whitelisting for property names

704

public class SecurePropertyAccessor implements PropertyAccessor {

705

private final Set<String> allowedProperties;

706

private final PropertyAccessor delegate;

707

708

public SecurePropertyAccessor(PropertyAccessor delegate, String... allowedProperties) {

709

this.delegate = delegate;

710

this.allowedProperties = Set.of(allowedProperties);

711

}

712

713

@Override

714

public boolean canRead(EvaluationContext context, Object target, String name) {

715

return allowedProperties.contains(name) &&

716

delegate.canRead(context, target, name);

717

}

718

719

// ... implement other methods with security checks

720

}

721

722

// 2. Validate input types and ranges

723

public class ValidatingIndexAccessor implements IndexAccessor {

724

private final IndexAccessor delegate;

725

726

@Override

727

public TypedValue read(EvaluationContext context, Object target, Object index)

728

throws AccessException {

729

validateIndex(index);

730

return delegate.read(context, target, index);

731

}

732

733

private void validateIndex(Object index) throws AccessException {

734

if (index instanceof Integer) {

735

int idx = (Integer) index;

736

if (idx < 0 || idx > 10000) { // Reasonable bounds

737

throw new AccessException("Index out of safe range: " + idx);

738

}

739

}

740

}

741

}

742

```

743

{ .api }

744

745

### Performance Tips

746

747

1. **Order accessors by specificity**: Place most specific accessors first in the chain

748

2. **Use targeted accessors**: Implement `getSpecificTargetClasses()` to avoid unnecessary checks

749

3. **Cache expensive operations**: Implement caching for costly property access

750

4. **Consider compilation**: Implement `CompilablePropertyAccessor` for hot paths

751

5. **Minimize allocations**: Reuse `TypedValue` instances when possible

752

6. **Profile accessor chains**: Monitor which accessors are called most frequently

753

754

### Error Handling

755

756

```java

757

public class RobustPropertyAccessor implements PropertyAccessor {

758

private final PropertyAccessor delegate;

759

private final boolean failSilently;

760

761

public RobustPropertyAccessor(PropertyAccessor delegate, boolean failSilently) {

762

this.delegate = delegate;

763

this.failSilently = failSilently;

764

}

765

766

@Override

767

public TypedValue read(EvaluationContext context, Object target, String name)

768

throws AccessException {

769

try {

770

return delegate.read(context, target, name);

771

} catch (Exception e) {

772

if (failSilently) {

773

return TypedValue.NULL; // Return null instead of throwing

774

}

775

throw new AccessException("Failed to read property '" + name + "'", e);

776

}

777

}

778

779

// ... implement other methods with similar error handling

780

}

781

```

782

{ .api }