io.github.devbpm.linneo.classify.Classifier — public final class, @since 0.1.0
Reads the classification annotations of classes and checks them against a Taxonomy. It works on
plain classes, records and JPA entities alike and needs no persistence library. It only reads annotations, never
field values, so it needs no access to the classes beyond what Class.getDeclaredFields() requires. Instances are
immutable and thread-safe.
| Member | Returns | Description |
|---|---|---|
Classifier(Taxonomy taxonomy) |
— | Creates a classifier for a taxonomy; keys must belong to it. NullPointerException if taxonomy is null. |
ClassClassification classify(Class<?> type) |
ClassClassification |
Classifies one class: its field classifications and every violation found. Never throws for classification problems. NullPointerException if type is null. |
List<ClassClassification> classifyAll(Collection<? extends Class<?>> types) |
immutable list | One classification per class, in the given order. |
List<ClassClassification> verify(Collection<? extends Class<?>> types) |
immutable list | Classifies the classes and throws ClassificationException listing every error of every class if there is any. Warnings do not fail. Returns the classifications when there is no error. |
@Test
void everyEntityIsClassified() {
new Classifier(Taxonomy.load()).verify(List.of(Customer.class, Invoice.class, MenuGroup.class));
}
With JPA, take the classes from the metamodel:
List<Class<?>> entities = entityManagerFactory.getMetamodel().getEntities().stream()
.<Class<?>>map(EntityType::getJavaType)
.toList();
new Classifier(Taxonomy.load()).verify(entities);
Object, Record and Enum; interfaces are not visited.getDeclaredFields(). The resulting fields() list holds superclass fields
first, in declaration order.static, transient and synthetic fields (such as the reference to the
enclosing instance in an inner class), and fields whose name starts with $$_ (added by bytecode tools, for
example Hibernate’s enhancement: $$_hibernate_interceptor).$HibernateProxy$ is classified as the entity it extends: the
type() of its ClassClassification is the entity, not the proxy.@Embedded
address is one field, classified by its own annotations, and the embeddable class must be verified as well, for
example by passing it to verify together with the entities.@Exempt are skipped after their annotation is checked.getDeclaredAnnotation), so on a record they must target the
component’s field, which @Category, @CategoryByRow and @Exempt do.Each remaining field is resolved by the resolution order: its own
@Category or @CategoryByRow, then the @DefaultCategory of the declaring class, then the @DefaultCategory of
the classified class, otherwise UNCLASSIFIED_FIELD.
Order of violations in a ClassClassification: first the checks of each @DefaultCategory in the hierarchy (top
down), then the field checks in field order.
Nothing is classified by default. These are errors (they fail verify and make isValid() false):
UNCLASSIFIED_FIELD);UNKNOWN_CATEGORY);PERSONAL_DEFAULT);declaredBy (MISSING_REASON);CONFLICTING_ANNOTATIONS);LOWERED_LEVEL);@Category (AMBIGUOUS_VALUE);IDENTIFIABILITY_NOT_APPLICABLE).A deprecated key is the only warning (DEPRECATED_CATEGORY): the field is classified with the current
replacement. A field with an error is usually missing from fields(); see Violations for the exact
effect of each code.
io.github.devbpm.linneo.classify.ClassificationException — public class ClassificationException extends
RuntimeException, @since 0.1.0
Thrown by Classifier.verify when a model is not fully and correctly classified.
| Member | Description |
|---|---|
ClassificationException(List<Violation> errors) |
Creates the exception from the errors found (at least one). The list is copied. |
List<Violation> errors() |
The errors, all of severity ERROR, from every verified class. |
The message is the number of errors followed by one line per error, each in the format of
Violation.toString():
3 classification error(s):
- ERROR UNCLASSIFIED_FIELD com.example.Person.undecided: no @Category on the field and no @DefaultCategory on Person
- ERROR MISSING_REASON com.example.WrongDefault: @DefaultCategory of WrongDefault needs a reason
- ERROR PERSONAL_DEFAULT com.example.WrongDefault: @DefaultCategory of WrongDefault names 'user.contact', which is personal data: declare personal fields one by one with @Category
The errors list is held in a transient field: it is not preserved if the exception is serialized.
See also: Results · Violations · Annotations · course: Verifying in tests