Class FactProperties
Map key.
Rules everywhere are written applicant.creditScore, where the fact may be a record, a bean or a map. The
engine hands a language the fact objects as they are, so making that work is each language's job, and every
language that does it by hand does it slightly differently: one reads a record's component as a method and
silently evaluates the condition to false, another fails only for records, a third converts every fact to
a map on every evaluation. A language may use this instead.
Writing a property is the other direction, and the engine already has it:
OutputWriter.beansAndMaps() puts into a map or calls a bean setter. There
is deliberately no write here, so there's only one implementation to keep correct. Records can't be
written to at all.
Accessors are looked up once for each class and shared by every thread.
-
Method Summary
Modifier and TypeMethodDescriptionstatic booleanSays whether a fact has a property, without reading it: whetherread(java.lang.Object, java.lang.String)would find the property rather than throwIllegalArgumentException.propertyNames(Object target) Lists the propertiesread(java.lang.Object, java.lang.String)can read on a fact, without reading any of them, for a language that enumerates an object's properties, such as JavaScript'sObject.keysor Python'sdir.static @Nullable ObjectReads a property of a fact.Converts a record, a bean or a map into a map of its properties, for a language that reads only maps.
-
Method Details
-
read
Reads a property of a fact.- A
Map: the value under that exact key. A key that isn't there is an error, and a key whose value isnullisn't. A map whose keys aren't strings has no property a rule can name, even thoughtoData(java.lang.Object, int)gives those keys a string form. - A record: the component of that name, or one of its own getters when no component matches.
- Anything else: the public no-argument
getProperty(), orisProperty()when it returns abooleanor aBoolean. The getter's first letter is lower-cased byCharacter.toLowerCase(char), unless its second letter is upper case:getURL()is read asURL, andgetİsim()asisim. Where two getters are read as one property, agetProperty()beats anisProperty(), and between two of the same kind the one whose name is greatest inString.compareTo(java.lang.String)order wins, sogetİsim()beatsgetIsim(). Only the tie between twogetProperty()is broken asjava.beansbreaks it; the other two rules are this class's own. The choice never depends on the order reflection reports methods in. Public fields aren't read. A getter whose name aftergetorishas a letter above U+00FF where its case decides the property's name, such as theİofgetİsim(), is read with the JDK's table of the first plane,java.lang.CharacterData00, which the JVM builds on its first use, and the first read of any property of a class reads all its getters, maybe deep in a stack with no engine built. So until the table is known to be built, by such a read, by a bean write throughOutputWriter.beansAndMaps()or by the JVM's first engine build, a read of such a getter first checks that the stack has room for building it, as the engine's first build does, and throwsStackOverflowErrorif it hasn't, before the table is touched, rather than leave it unusable for the life of the JVM (#1297). After that, no read checks.
A fact whose own class isn't public is read through a public type above it that declares the accessor, which is what makes a fact from a factory, or an anonymous implementation of a public interface, readable. When nothing public declares it, the accessor is called directly if the class's package is open to this module: every package on the class path is, and on the module path the application opens it with
opens. Exporting the package isn't enough, because calling a method of a class that isn't public is deep reflection.Rules can't read their way into the JVM through a fact. An enum's
getDeclaringClass()isn't a property, asgetClass()isn't one. And aClass, aClassLoader, aModule, aModuleLayer, aPackage, aProtectionDomain, aCodeSource, aThread, an application's own subclass of one of them, or an object of a class injava.lang.reflect, such as aMethod, has no properties here at all, sotoData(java.lang.Object, int)leaves one as it is too. A fact's own property may still return one of them as its value; only reading a property of that value is refused.- Parameters:
target- The fact, which must not benullproperty- The property's name, as the rule wrote it- Returns:
- The property's value, which may be
null - Throws:
NullPointerException- iftargetorpropertyisnullIllegalArgumentException- if the fact has no such property, or is one of the JVM's own objects whose properties aren't read. A language should let this reach the engine, which fails the rule with it: a missing property is a mistake in the rule, and evaluating it tofalseor to undefined hides itIllegalStateException- if the property exists but can't be read, because nothing public declares its accessor and its package isn't open to this module; or if the accessor threw, with what it threw as the cause. The message, such asReading 'price' on a com.example.Item failed: price service down, ends with a colon and the message of what the accessor threw, whole, however long, for the engine to shorten when it logs it. That is left out when what it threw has no message or reading it throws, and when it's a failure the engine has already logged and names in the rule's failure asa nested run() failed:and its text, or as its text and(already logged)when no run the code around the read started logged it: a nested run's failure, whichever thread the read is on, or a fatalErrora run the code around the read started logged, in either case with nothing of its own around it. The cause still holds it. What a getter throws is always wrapped, so that one throwingIllegalArgumentExceptionisn't read as a missing property. A map's owncontainsKeyandgetare its accessors, and an exception from them is wrapped too, with one exception: aClassCastExceptionorNullPointerExceptionfromcontainsKeyis taken for the map refusing a key of that type, as a sorted map refuses a string key, and reported as no such property, though a map that fails that way for a reason of its own reads the same. OncecontainsKeyhas found the key, whatevergetthrows is the map's own failure. When the accessor was interrupted, the thread is left interrupted, unless the getter threw aVirtualMachineError, so a caller that catches this doesn't lose the interrupt
- A
-
has
Says whether a fact has a property, without reading it: whetherread(java.lang.Object, java.lang.String)would find the property rather than throwIllegalArgumentException. No getter is called, and of a map only its owncontainsKey, so a getter that is slow, has side effects or throws doesn't change the answer, which is what a language needs for its own "is it there" questions, such as JavaScript'sinor Python'shasattr.A map has the property when its
containsKeysays so, a record or a bean when it has an accessor of that name, by the same rules asread(java.lang.Object, java.lang.String), a fact whose class isn't public included. A property whose accessor throws, or can't be called, is still there:read(java.lang.Object, java.lang.String)fails for it withIllegalStateException, not as a missing property. AString, a number or a collection has the properties its public getters give it, asread(java.lang.Object, java.lang.String)reads them, such as a string'sempty; one of the JVM's own objects whose propertiesread(java.lang.Object, java.lang.String)refuses has none.- Parameters:
target- The fact, which must not benullproperty- The property's name, as the rule wrote it- Returns:
trueifread(java.lang.Object, java.lang.String)would read the property,falseif it would report it missing- Throws:
NullPointerException- iftargetorpropertyisnullIllegalStateException- iftargetis a map whosecontainsKeythrows, with what it threw as the cause, asread(java.lang.Object, java.lang.String)wraps it. AClassCastExceptionorNullPointerExceptionfrom it is taken for the map refusing a key of that type, asread(java.lang.Object, java.lang.String)takes it, and answersfalse. WhencontainsKeywas interrupted, the thread is left interrupted, asread(java.lang.Object, java.lang.String)leaves it
-
propertyNames
Lists the propertiesread(java.lang.Object, java.lang.String)can read on a fact, without reading any of them, for a language that enumerates an object's properties, such as JavaScript'sObject.keysor Python'sdir. No getter is called, and of a map only its ownkeySet, through itsforEach, so a synchronized map lists its keys under its lock.A map's properties are its
Stringkeys, in its iteration order. A key of another type, ornull, isn't oneread(java.lang.Object, java.lang.String)can reach, so it's left out, thoughtoData(java.lang.Object, int)gives it a string form. A record's are its components in the order it declares them, then any other getters it has, and a bean's are sorted by name: the names, in the order, oftoData(target, 1).keySet(), with no getter called.Unlike
toData(java.lang.Object, int), which takes apart only a record, a bean or a map, this answers for any factread(java.lang.Object, java.lang.String)reads: aString, a number or a collection has the properties its public getters give it, such as a string'sempty. One of the JVM's own objects whose propertiesread(java.lang.Object, java.lang.String)refuses has none.has(java.lang.Object, java.lang.String)istruefor every name listed; a map whosecontainsKeyfinds keys it doesn't list, such as one that ignores case, has more.- Parameters:
target- The fact, which must not benull- Returns:
- The names, unmodifiable, in the same order every time for the same class, or for a map, in its iteration order
- Throws:
NullPointerException- iftargetisnullIllegalStateException- iftargetis a map whosekeySetor itsforEachthrows, with what it threw as the cause, asReading the keys of a ... failed. When it was interrupted, the thread is left interrupted, asread(java.lang.Object, java.lang.String)leaves it
-
toData
Converts a record, a bean or a map into a map of its properties, for a language that reads only maps.Every step down costs one level, whether it's into a property, into a map's value or into a collection's element. At
1the values are whatever the properties hold; at2a value that is itself convertible becomes a map, and a list becomes a list of the elements as they are; at3the elements of that list are converted too.A value already being converted higher up the same path is left as it is rather than converted again, so a fact that holds itself, or two facts that hold each other, cost no more than a tree of the same depth. That stops a value repeating down one path; it doesn't make every graph cheap. A value reachable by several different paths is converted once for each of them, so an object graph that fans out and rejoins can still cost far more than it holds.
depthis what bounds that, and it's chosen by the caller, not by the facts: convert as few levels as the language needs.A map's values keep their keys, with a key that isn't a
Stringconverted byString.valueOf(java.lang.Object). Two keys that read the same then collapse into one entry and the later wins, and anullkey becomes the entry"null", which a real"null"key can't be told apart from.read(java.lang.Object, java.lang.String)takes the key as it is, so a converted map can show a property that reading the fact directly can't reach. A map's entries are copied through its entry set'sforEach, so a synchronized map's are copied under its lock, each entry'sgetKey()andgetValue()and each key'stoString()included. Its values are converted after the lock is released, so their getters don't run under it.A value is converted when it's a map, a collection, an array of objects, a record, or an application class with at least one getter. Everything the JVM itself provides is left as it is, which is what keeps a
String, aPath, aLocalDate, anOptional, an enum, a lambda or an array of primitives from being taken apart into the properties of its implementation. A proxy over an interface is data, because that's a shape facts take: a projection, a lazy association, a test double. A lambda or an anonymous class is converted when it's thetarget, because then the caller asked for its properties, and left alone when it's met as a value. An object whose propertiesread(java.lang.Object, java.lang.String)doesn't read, such as an application's own class loader or thread, is left as it is too, and can't be thetarget.The rule is "has getters", so it can't tell data from a library type that merely looks like it. A JSON tree node or a multimap from a third-party library has
isEmpty(),isArray()and the like, so converting a fact that holds one returns those flags instead of its contents. Read such a fact withread(java.lang.Object, java.lang.String), or have the language convert it itself.- Parameters:
target- The record, bean or map to convert, which must not benulldepth- How many levels to convert, from1to 20- Returns:
- The properties by name: a record's components in the order it declares them, then any other getters it has, and a bean's sorted by name, because reflection reports a class's methods in no particular order. A new modifiable map
- Throws:
NullPointerException- iftargetisnullIllegalArgumentException- ifdepthis outside1to 20, ortargetis a value this doesn't take apart, such as aString, a number or a collectionIllegalStateException- if an accessor can't be called, with what it threw as the cause, and its message after a colon asread(java.lang.Object, java.lang.String)appends it. Reading a map's entries (itsentrySet(), the entry set'sforEach, each entry'sgetKey()andgetValue()and each key'stoString()) and a collection'ssize()andforEachcount as accessors, so what they throw is wrapped too, asReading the entries of a ... failedorReading the elements of a ... failed. When an accessor was interrupted, the thread is left interrupted, asread(java.lang.Object, java.lang.String)leaves it, unless a getter threw aVirtualMachineError
-