Skip to content

Commit 94544be

Browse files
committed
v1.7.3: reader field exclude plus strategies
1 parent 809c354 commit 94544be

22 files changed

+1022
-56
lines changed

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,12 @@ for a higher control compared to the EasyML Facade.
6060

6161
### Release Notes
6262

63+
Release 1.7.3
64+
- feature: XMLReader exclude fields (similar to XMLWriter exclude fields)
65+
- feature: added OptionalDouble, OptionalInt, OptionalLong strategies.
66+
- feature: added ArrayDeque, PriorityQueue strategies.
67+
68+
6369
Release 1.7.2
6470
- refactor: improved XML writer, reader and EasyML APIs
6571
- feature: added HexFormat strategy.

easyml/src/net/sourceforge/easyml/EasyML.java

Lines changed: 61 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import java.io.*;
4444
import java.lang.reflect.Field;
4545
import java.util.Map;
46+
import java.util.Objects;
4647
import java.util.Set;
4748
import java.util.concurrent.ConcurrentHashMap;
4849
import java.util.function.Supplier;
@@ -84,7 +85,7 @@
8485
* objects.<br/>
8586
*
8687
* @author Victor Cordis ( cordis.victor at gmail.com)
87-
* @version 1.7.2
88+
* @version 1.7.3
8889
* @see XMLReader
8990
* @see XMLWriter
9091
* @since 1.0
@@ -246,6 +247,7 @@ public static void defaultConfiguration(XMLWriter writer) {
246247
simple.add(ZonedDateTimeStrategy.INSTANCE);
247248
simple.add(ZoneIdStrategy.INSTANCE);
248249
// util:
250+
composite.add(ArrayDequeStrategy.INSTANCE);
249251
composite.add(ArrayListStrategy.INSTANCE);
250252
composite.add(BitSetStrategy.INSTANCE);
251253
composite.add(CalendarStrategy.INSTANCE);
@@ -266,6 +268,10 @@ public static void defaultConfiguration(XMLWriter writer) {
266268
composite.add(LinkedListStrategy.INSTANCE);
267269
simple.add(LocaleStrategy.INSTANCE);
268270
composite.add(OptionalStrategy.INSTANCE);
271+
composite.add(OptionalDoubleStrategy.INSTANCE);
272+
composite.add(OptionalIntStrategy.INSTANCE);
273+
composite.add(OptionalLongStrategy.INSTANCE);
274+
composite.add(PriorityQueueStrategy.INSTANCE);
269275
composite.add(PropertiesStrategy.INSTANCE);
270276
composite.add(SingletonListStrategy.INSTANCE);
271277
composite.add(SingletonMapStrategy.INSTANCE);
@@ -343,6 +349,7 @@ public static void defaultConfiguration(XMLReader reader) {
343349
simple.put(ZonedDateTimeStrategy.NAME, ZonedDateTimeStrategy.INSTANCE);
344350
simple.put(ZoneIdStrategy.NAME, ZoneIdStrategy.INSTANCE);
345351
// util:
352+
composite.put(ArrayDequeStrategy.NAME, ArrayDequeStrategy.INSTANCE);
346353
composite.put(ArrayListStrategy.NAME, ArrayListStrategy.INSTANCE);
347354
composite.put(BitSetStrategy.NAME, BitSetStrategy.INSTANCE);
348355
composite.put(CalendarStrategy.NAME, CalendarStrategy.INSTANCE);
@@ -363,6 +370,10 @@ public static void defaultConfiguration(XMLReader reader) {
363370
composite.put(LinkedListStrategy.NAME, LinkedListStrategy.INSTANCE);
364371
simple.put(LocaleStrategy.NAME, LocaleStrategy.INSTANCE);
365372
composite.put(OptionalStrategy.NAME, OptionalStrategy.INSTANCE);
373+
composite.put(OptionalDoubleStrategy.NAME, OptionalDoubleStrategy.INSTANCE);
374+
composite.put(OptionalIntStrategy.NAME, OptionalIntStrategy.INSTANCE);
375+
composite.put(OptionalLongStrategy.NAME, OptionalLongStrategy.INSTANCE);
376+
composite.put(PriorityQueueStrategy.NAME, PriorityQueueStrategy.INSTANCE);
366377
composite.put(PropertiesStrategy.NAME, PropertiesStrategy.INSTANCE);
367378
composite.put(SingletonListStrategy.NAME, SingletonListStrategy.INSTANCE);
368379
composite.put(SingletonMapStrategy.NAME, SingletonMapStrategy.INSTANCE);
@@ -387,11 +398,19 @@ public static void defaultConfiguration(XMLReader reader) {
387398
/**
388399
* To be used by {@linkplain EasyMLBuilder} only.
389400
*/
390-
EasyML(Style style, Supplier<XmlPullParser> xmlPullParserProvider,
391-
String dateFormat, String customRootTag, Map<Class, String> classToAlias, Map<Field, String> fieldToAlias,
392-
Set<Field> excludedFields, XMLReader.SecurityPolicy deserializationSecurityPolicy,
393-
Set<SimpleStrategy> registeredSimple, Set<CompositeStrategy> registeredComposite,
394-
Set<SimpleStrategy> unregisteredSimple, Set<CompositeStrategy> unregisteredComposite) {
401+
EasyML(Style style,
402+
Supplier<XmlPullParser> xmlPullParserProvider,
403+
String dateFormat,
404+
String customRootTag,
405+
Map<Class, String> classToAlias,
406+
Map<Field, String> fieldToAlias,
407+
Set<Field> excludedFields,
408+
Set<ExcludedName> excludedFieldNames,
409+
XMLReader.SecurityPolicy deserializationSecurityPolicy,
410+
Set<SimpleStrategy> registeredSimple,
411+
Set<CompositeStrategy> registeredComposite,
412+
Set<SimpleStrategy> unregisteredSimple,
413+
Set<CompositeStrategy> unregisteredComposite) {
395414
this();
396415
// style:
397416
if (style != null) {
@@ -429,9 +448,15 @@ public static void defaultConfiguration(XMLReader reader) {
429448
this.writerPrototype.exclude(excludedField);
430449
}
431450
}
451+
// excludedFieldNames:
452+
if (excludedFieldNames != null) {
453+
for (ExcludedName excludedName : excludedFieldNames) {
454+
this.readerPrototype.exclude(excludedName.declaring, excludedName.fieldOrAlias);
455+
}
456+
}
432457
// deserializationSecurityPolicy:
433458
if (deserializationSecurityPolicy != null) {
434-
this.readerPrototype.securityPolicy = deserializationSecurityPolicy;
459+
this.readerPrototype.maybeSecurityPolicy = deserializationSecurityPolicy;
435460
}
436461
// registeredSimple:
437462
if (registeredSimple != null) {
@@ -787,4 +812,33 @@ public void clearCache() {
787812
// clear writer cache:
788813
this.writerPrototype.clearCache();
789814
}
815+
816+
/* default */ static final class ExcludedName {
817+
private final Class declaring;
818+
private final String fieldOrAlias;
819+
820+
/* default */ ExcludedName(Class declaring, String fieldOrAlias) {
821+
if (declaring == null) {
822+
throw new IllegalArgumentException("declaring: null");
823+
}
824+
if (fieldOrAlias == null) {
825+
throw new IllegalArgumentException("fieldOrAlias: null");
826+
}
827+
this.declaring = declaring;
828+
this.fieldOrAlias = fieldOrAlias;
829+
}
830+
831+
@Override
832+
public boolean equals(Object o) {
833+
if (this == o) return true;
834+
if (o == null || getClass() != o.getClass()) return false;
835+
ExcludedName other = (ExcludedName) o;
836+
return Objects.equals(declaring, other.declaring) && Objects.equals(fieldOrAlias, other.fieldOrAlias);
837+
}
838+
839+
@Override
840+
public int hashCode() {
841+
return Objects.hash(declaring, fieldOrAlias);
842+
}
843+
}
790844
}

easyml/src/net/sourceforge/easyml/EasyMLBuilder.java

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@
5050
* <b>Note:</b> this builder implementation is <b>not</b> thread-safe
5151
*
5252
* @author Victor Cordis ( cordis.victor at gmail.com)
53-
* @version 1.5.1
53+
* @version 1.7.3
5454
* @see EasyML
5555
* @see XMLReader
5656
* @see XMLWriter
@@ -65,6 +65,7 @@ public final class EasyMLBuilder implements Supplier<EasyML> {
6565
private Map<Class, String> classToAlias;
6666
private Map<Field, String> fieldToAlias;
6767
private Set<Field> excludedFields;
68+
private Set<EasyML.ExcludedName> excludedFieldNames;
6869
private XMLReader.SecurityPolicy deserializationSecurityPolicy;
6970
private Set<SimpleStrategy> registeredSimple;
7071
private Set<CompositeStrategy> registeredComposite;
@@ -160,6 +161,7 @@ public EasyMLBuilder withAlias(Class declaring, String field, String alias) thro
160161

161162
/**
162163
* Sets exclusion on the given field.
164+
* For serialization.
163165
*
164166
* @param f to exclude
165167
*/
@@ -173,6 +175,7 @@ public EasyMLBuilder withExcluded(Field f) {
173175

174176
/**
175177
* Sets exclusion on the given field, of the <code>declaring</code> class.
178+
* For serialization.
176179
*
177180
* @param declaring class declaring the field
178181
* @param field the name of the field to exclude
@@ -182,6 +185,21 @@ public EasyMLBuilder withExcluded(Class declaring, String field) throws NoSuchFi
182185
return withExcluded(declaring.getDeclaredField(field));
183186
}
184187

188+
/**
189+
* Sets exclusion on the given field name or alias, of the <code>declaring</code> class.
190+
* For deserialization.
191+
*
192+
* @param declaring class declaring the field
193+
* @param fieldOrAlias the name of the field to exclude
194+
*/
195+
public EasyMLBuilder withExcludedName(Class declaring, String fieldOrAlias) {
196+
if (this.excludedFieldNames == null) {
197+
this.excludedFieldNames = new HashSet<>();
198+
}
199+
this.excludedFieldNames.add(new EasyML.ExcludedName(declaring, fieldOrAlias));
200+
return this;
201+
}
202+
185203
/**
186204
* Sets the deserialization security policy, which is used to define black-
187205
* or whitelists of classes to be checked at deserialization time.
@@ -284,6 +302,7 @@ public EasyML build() {
284302
classToAlias,
285303
fieldToAlias,
286304
excludedFields,
305+
excludedFieldNames,
287306
deserializationSecurityPolicy,
288307
registeredSimple,
289308
registeredComposite,
@@ -299,4 +318,5 @@ public EasyML build() {
299318
public EasyML get() {
300319
return this.build();
301320
}
321+
302322
}

easyml/src/net/sourceforge/easyml/XMLReader.java

Lines changed: 57 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
* shared configuration can be created, via constructors.
6666
*
6767
* @author Victor Cordis ( cordis.victor at gmail.com)
68-
* @version 1.7.2
68+
* @version 1.7.3
6969
* @see XMLWriter
7070
* @since 1.0
7171
*/
@@ -366,7 +366,14 @@ public final Object readArray(Class componentType) {
366366
}
367367

368368
/**
369-
* Consumes the XML until the entire subgraph is read.
369+
* Consumes the current XML element, stopping {@linkplain #atElementEnd()}.
370+
* Must be {@linkplain #atElementStart()}.
371+
*/
372+
@Override
373+
public abstract void consume();
374+
375+
/**
376+
* Consumes the XML until the entire content is read.
370377
*/
371378
protected abstract void consumeFully();
372379

@@ -407,10 +414,11 @@ interface AliasingReflectionCacheSupplier {
407414
private boolean sharedConfiguration;
408415
private UnmarshalContextImpl context;
409416
/* default*/ Map<String, Object> cachedAliasingReflection;
417+
private Set<String> maybeExclusions;
410418
private Map<String, SimpleStrategy> simpleStrategies;
411419
private Map<String, CompositeStrategy> compositeStrategies;
412420
/* default*/ SimpleDateFormat dateFormat;
413-
/* default*/ SecurityPolicy securityPolicy;
421+
/* default*/ SecurityPolicy maybeSecurityPolicy;
414422

415423
/**
416424
* Creates a new configuration prototype instance.
@@ -428,10 +436,11 @@ private void init(AliasingReflectionCacheSupplier getAliasingReflectionCache) {
428436
this.sharedConfiguration = false;
429437
this.context = new UnmarshalContextImpl();
430438
this.cachedAliasingReflection = getAliasingReflectionCache.get();
439+
this.maybeExclusions = null; // lazy.
431440
this.simpleStrategies = new StrategyHashMap<>();
432441
this.compositeStrategies = new StrategyHashMap<>();
433442
this.dateFormat = new SimpleDateFormat(DTD.FORMAT_DATE);
434-
this.securityPolicy = null; // lazy.
443+
this.maybeSecurityPolicy = null; // lazy.
435444
// add DTD strategies by default:
436445
this.simpleStrategies.put(DTD.TYPE_BASE64, Base64Strategy.INSTANCE);
437446
this.simpleStrategies.put(DTD.TYPE_BOOLEAN, BooleanStrategy.INSTANCE);
@@ -463,10 +472,11 @@ private void initIdentically(XMLReader other) {
463472
this.sharedConfiguration = true;
464473
this.context = new UnmarshalContextImpl();
465474
this.cachedAliasingReflection = other.cachedAliasingReflection;
475+
this.maybeExclusions = other.maybeExclusions;
466476
this.simpleStrategies = other.simpleStrategies;
467477
this.compositeStrategies = other.compositeStrategies;
468478
this.dateFormat = new SimpleDateFormat(other.dateFormat.toPattern());
469-
this.securityPolicy = other.securityPolicy;
479+
this.maybeSecurityPolicy = other.maybeSecurityPolicy;
470480
}
471481

472482
/**
@@ -591,7 +601,7 @@ public Map<String, CompositeStrategy> getCompositeStrategies() {
591601
}
592602

593603
/**
594-
* Gets the {@linkplain #securityPolicy} property, which is used to create,
604+
* Gets the SecurityPolicy property, which is used to create,
595605
* for security reasons, black- or whitelists of classes to be checked when
596606
* reading objects. If a security policy is defined and an illegal class is
597607
* found at read time then the read will halt and throw a
@@ -613,10 +623,10 @@ public Map<String, CompositeStrategy> getCompositeStrategies() {
613623
*/
614624
public SecurityPolicy getSecurityPolicy() {
615625
this.checkNotSharedConfiguration();
616-
if (this.securityPolicy == null) { // lazy:
617-
this.securityPolicy = new SecurityPolicy();
626+
if (this.maybeSecurityPolicy == null) {
627+
this.maybeSecurityPolicy = new SecurityPolicy();
618628
}
619-
return this.securityPolicy;
629+
return this.maybeSecurityPolicy;
620630
}
621631

622632
/**
@@ -689,6 +699,21 @@ private static String qualifiedFieldKey(Class declaring, String field) {
689699
return declaring.getName() + FIELD_FQN_SEPARATOR + field;
690700
}
691701

702+
/**
703+
* Excludes the given field, by name or alias.
704+
*
705+
* @param declaring class
706+
* @param field name or alias to exclude
707+
* @throws IllegalStateException if shared configuration
708+
*/
709+
public void exclude(Class declaring, String field) {
710+
this.checkNotSharedConfiguration();
711+
if (maybeExclusions == null) {
712+
maybeExclusions = new HashSet<>();
713+
}
714+
this.maybeExclusions.add(qualifiedFieldKey(declaring, field));
715+
}
716+
692717
/**
693718
* Returns {@code true} if there is more to be read from the current input
694719
* or {@code false} if the document end tag was reached.
@@ -860,7 +885,7 @@ public Object readArray(Class componentType) {
860885
try {
861886
final Object ret = this.read0(componentType);
862887
return ret;
863-
} catch (IllegalClassException | InvalidFormatException ex) {
888+
} catch (RuntimeException ex) {
864889
this.driver.consumeFully();
865890
throw ex;
866891
} finally {
@@ -954,20 +979,32 @@ private Object readObject()
954979
final String localPartName = this.driver.elementName();
955980
// search the class for the specified property:
956981
Field f = null;
982+
boolean skipF = false;
957983
while (cls != Object.class) {
984+
985+
if (this.context.excluded(cls, localPartName)) {
986+
skipF = true;
987+
break; // skip excluded field search.
988+
}
989+
958990
try {
959991
f = this.context.fieldFor(cls, localPartName);
960992
if (!Modifier.isStatic(f.getModifiers())) {
961993
break; // field found.
962994
}
963995
} catch (NoSuchFieldException searchInSuperclass) {
964996
}
997+
965998
cls = cls.getSuperclass();
966999
}
967-
// check if field is indeed an instance property:
1000+
if (skipF) {
1001+
this.driver.consume();
1002+
continue; // skip excluded field.
1003+
}
9681004
if (f == null) {
9691005
throw new InvalidFormatException(this.driver.positionDescriptor(), "undefined property: " + cls.getName() + '.' + localPartName);
9701006
}
1007+
// check if field is indeed an instance property:
9711008
final ReflectionUtil.FieldInfo fi = ReflectionUtil.fieldInfoForWrite(f);
9721009
if (!fi.isProperty) {
9731010
throw new InvalidFormatException(this.driver.positionDescriptor(), "not a property: " + cls.getName() + '.' + localPartName);
@@ -1023,8 +1060,8 @@ private Object readArray0(Class componentType) {
10231060
}
10241061

10251062
private void ensureSecurityPolicy(Object o) {
1026-
if (this.securityPolicy != null) {
1027-
this.securityPolicy.check(o.getClass());
1063+
if (this.maybeSecurityPolicy != null) {
1064+
this.maybeSecurityPolicy.check(o.getClass());
10281065
}
10291066
}
10301067

@@ -1157,9 +1194,10 @@ public void close() {
11571194
this.decoded.clear();
11581195
this.context = null;
11591196
this.cachedAliasingReflection = null;
1197+
this.maybeExclusions = null;
11601198
this.compositeStrategies = null;
11611199
this.simpleStrategies = null;
1162-
this.securityPolicy = null;
1200+
this.maybeSecurityPolicy = null;
11631201
}
11641202

11651203
/**
@@ -1287,6 +1325,11 @@ public Field fieldFor(Class declaring, String aliasOrName) throws NoSuchFieldExc
12871325
return ret;
12881326
}
12891327

1328+
@Override
1329+
public boolean excluded(Class declaring, String aliasOrName) {
1330+
return maybeExclusions != null && maybeExclusions.contains(qualifiedFieldKey(declaring, aliasOrName));
1331+
}
1332+
12901333
@Override
12911334
public Date parseDate(String date) throws ParseException {
12921335
return dateFormat.parse(date);

0 commit comments

Comments
 (0)