This is central repository that aggregates various Ayte utility libraries for Java. Because of NIH syndrome, some OCD regarding naming and, finally, some missing functionality in common libraries it has been decided to create our own ones.
- Common boilerplate (
optional -> optional.orElse(null)
,list -> list.stream().anyMatch(predicate)
) - Debug unfriendliness (
MinTest$lambda@2640
- what the hell does this thing do?) - Hard to guess and/or long utility functionality names (
materialize
instead ofemptyIfNull
) - Porting some common functionality (e.g.
anyMatch()
for iterable)
- value - some data interfaces/classes like Pair, Trio and so on
- supplier - common implementations for
java.util.function.Supplier
interface - fabricator - interface identical to
Supplier
, but which may throw (for example, InputStream providers may be exposed as such Fabricators that throw IOExceptions), as well as common implementations - task - Interface that sits between
Runnable
andCallable
- it doesn't return value, but can throw typed exception - action - Interface similar to function with throw support
- predicate - Collection of common predicate implementations, as well as TernaryPredicate support
- function - Collection of common function implementations
- collection - Classic NIH collection-related static helper methods
- string - Same, but for strings
Every component is split into api
and kit
packages. api
package
contains all the interfaces, while kit
contains implementations - this
is done so anyone who needs just interfaces wouldn't have to pull bunch
of unnecessary classes.
Every library is exposed as two artifacts,
io.ayte.utility.<library>:api
and io.ayte.utility.<library>:kit
,
which contain io.ayte.utility.<library>.api
and
io.ayte.utility.<library>.kit
packages, as well as same-named modules,
and kit module requires transitive api module.
Some libraries may miss one of two artifacts, but that's OK.
There are also aggregate artifacts io.ayte.utility:api
and
io.ayte.utility:kit
that simply aggregate all other packages. They
also expose io.ayte.utility.api
and io.ayte.utility.kit
modules that
transitively require all other modules.
Internal functionality is exposed through classes with static methods.
They usually provide static factories for interfaces maintained by
component and thus most often are called Xs
, where X
is interface
name Xs
provides factories for - Pairs
, Fabricators
and so on.
As stated above, interfaces reside in api
components. They can be used
separately (quite often you need just TernaryFunction
interface
without any additional weight).
AugmentedX
names are used to extend X
interface with default
methods overriden so they would use named classes instead of lambdas.
They reside in kit
packages, since they depend on actual
implementations.
AmplifiedX
names are used to resolve possible name collision, for
example AmplifiedCollections
may exist because otherwise it would
collide with java.util.Collections
.
Blah-blah-blah we will put all the effort to preserve it. Deprecated things will stay for one major release, and then they'll be gone. Please note that according to semantic versioning every 0.x release is treated as major release.
- Developer friendliness
- Java 8 compatibility, but Java 9+ readiness (modularization)
- Proper naming
- Proper splitting to reduce unnecessary dependencies and possibility of dependency hell
- Eradication of long and unpleasant but common lambdas
- Backward compatibility
- Code simplicity
Every function, predicate and so on should be exposed as named class.
This is somewhat contradictory to what lambdas were intended to bring
in, but when it comes to lambda world, it becomes hard to debug: all you
see in debugger is <ClassName>$lambda@<hashcode>
and captured
variables, so it is hard to tell what lambda does, even if it's just
x -> x * 2
. Because of that, all that simple stuff - Xor
, Not
,
GreaterThan
, Min
and so on should be exposed as named classes, just
to ease the pain of lambda debugging where small effort makes great
profit. It is much easier to read complex predicate constructions when
they are named, Not(And(GreaterThan(3), Even, ElementOf(6, 9, 12)))
is
way way way easier to understand than lambda composition.
Whole functionality should be exposed through static utility classes, so real constructors and required computations are called in shadows. This allows some simplicity hacks, for example operation verify that all predicates in collection respond with true can be substituted just with first predicate of such collection if it's size is 1, and can be reduced to constant true if collection is empty. Everything but such static classes or explicitly exposed functionality is considered internal even if you are writing non-modularized code.
Just don't break it.
Lots of tests look pretty much redundant and only test contracts of classes. This is done just to prevent any modifications that would break those contracts, even if this is unlikely.
Last & least, wherever possible, try to write performant code, but remember that compiler is smarter than you.
MIT / UPL-1.0
Ayte Labs, 2019
Have fun & do whatever you want, for free.