Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Repository files navigation

copperfield

A flexible, lightweight library to do ORMs using annotations (and magic).

just copperfield things

[[TOC]]

Setup

Import the copperfield-core module at the very least.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-core</artifactId>
<version>2.0.0</version>
</dependency>

Bson

If you want to convert data from and to bson documents, import the copperfield-bson module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-bson</artifactId>
<version>2.0.0</version>
</dependency>

Proto

If you want to convert data from and to proto messages, import the copperfield-proto module as well.

<dependency>
<groupId>dev.volix.rewinside.odyssey.common</groupId>
<artifactId>copperfield-proto</artifactId>
<version>2.0.0</version>
</dependency>

Usage

Value Conversion

At it's core, copperfield is a library to convert values of one datatype to values of another datatype defined in a so-called Converter and vice versa.

Converter<UUID, String> converter = newUuidToStringConverter();
converter.toTheirs(/* ... */); // Converts UUIDs to Stringsconverter.toOurs(/* ... */); // Converts Strings to UUIDs

However, Converters require a bunch of parameters in order to be able to convert the values correctly in all circumstances. Because they are quite delicate, they should not be set manually. This is where the CopperfieldAgent comes in. The agent provides a bunch of accessibility methods to convert values.

CopperfieldAgentagent = newCopperfieldAgent();
StringuuidAsString = (String) agent.toTheirs(UUID.randomUUID());
UUIDstringAsUuid = agent.toOurs(uuidAsString, UUID.class); 

The agent internally selects the converter matching the type, or the classes supertype, of the given value. The following converters are available by default. All converters support conversion in both directions by default, however there are some exceptions.

Our TypeTheir TypeConverterComment
UUIDStringUuidToStringConverterConverts uuids to strings using toString().
EnumStringEnumToStringConverterConverts enums to their corresponding names.
OffsetDateTimeStringOffsetDateTimeToStringConverterConverts datetime instances to ISO 8601 strings.
NumberNumberNumberConverterMakes sure that the number type does not get lost when converting toOurs().
IterableIterableIterableConverterConverts all values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.
MapMapMapConverterConverts all keys and values using the matching converters defined for the CopperfieldAgent. This will only work when converting toTheirs by default. See POJO Conversion.

Converter Registries

Registries contain the required mappings and instantiated converters available to the agent. Every instantiated agent uses mappings defined in the BaseRegistry by default.

You can pass additional Registries to the agent at construction time. This will combine the registries and discard the original instances afterwards.

Registryregistry1 = newCustomRegistry();
Registryregistry2 = newAnotherCustomRegistry();
// ...CopperfieldAgentagent = newCopperfieldAgent(registry, anotherCustomRegistry);

Add Converters

You may want to add additional converters. This can be achieved through calling the with() method on the registry.

Registryregistry = newBaseRegistry();
registry.with(UUID.class, UuidToUpperCaseStringConverter.class); // The registry will create a new instance on demand.// orregistry.with(UUID.class, UuidToUpperCaseStringConverter()); // Injects a specific converter instance.

When working with the agent and you want to add converters dynamically, you can retrieve the agent's registry and call with() on that too.

CopperfieldAgentagent = // ...agent.getRegistry().with(UUID.class, UuidToUpperCaseStringConverter.class);

Note: If another converter already exists for the given type (e.g. UUID), the previous one will be overridden.

Remove Converters

To remove converters, you can use the without() method.

Registryregistry = // ...registry.without(UUID.class); // Removes the converter currently assigned to UUIDs.

Combine Registries

For convenience, multiple Registries can be merged into a single one. This can be achieved using the corresponding with() method.

RegistrybaseRegistry = newBaseRegistry();
RegistryprotoRegistry = newProtoRegistry();
baseRegistry.with(protoRegistry);

In this example, all converter mappings and instances defined in the protoRegistry will be copied to the baseRegistry. This will override existing mappings in the baseRegistry if the same types are defined in both registries.

Accordingly, there is a without() method as well.

CopperfieldAgentagent = newCopperfieldAgent(newProtoRegistry());
agent.getRegistry().without(newBaseRegistry());

In this example, all converter mappings and instances defined in the BaseRegistry will be removed from the agent's registry if both types and mapped converter classes are exactly the same.

Additional Converters

These converters exist but are not registered by default.

Our TypeTheir TypeConverterComment
ObjectObjectNoOperationConverterCan be used to explicitly mark a type to not be converted.

Conversion Context

In the examples above, only one Converter could be defined for every given input type (e.g. every UUID will be converted to a String). However, there might be instances where you do not want the UUIDs to be converted to String but rather to their binary representation. This is where context comes into play.

For example:

agent.toTheirs(uuid); // "Convert the UUID without context" (default converter)agent.toTheirs(uuid, byte[].class); // "Convert the UUID in the context of a byte array."agent.toOurs(string, UUID.class); // "Convert the value to a UUID without context" (default converter)agent.toOurs(bytes, UUID.class, byte[].class); // "Convert the value to a UUID in the context of a byte array".

As you can see, the context basically defines "their" value type (i.e. the output type/format) or a matching supertype.

When adding converters to a Registry, you can optionally define the context for which the converters should be available.

Registryregistry = // ...registry.with(UUID.class, UuidToByteArrayConverter.class, byte[].class);

The agent will use this converter for this context (in this case byte[]) or any of it's derived classes only. Otherwise, the agent will fall back to any converter registered without any context.

Removing converters while defining a context will remove this converter from the given context only. If there is no converter registered specifically for this context, nothing will happen.

POJO Conversion

Up until now we covered how to convert single values. Let's say you want to convert a POJO to an external data format (e.g. Bson Documents). You could write a custom Converter which handles conversion in both directions and register it in the Registry. But then you might want to convert the same POJO to another data format as well. You would have to write new converters for every single data format, for every POJO you might want to convert to and from.

This is where CopperConvertables come into play. This functionality allows you to define all class members you want to convert to/from once while using the same definition for multiple different target formats.

Let's start with an example:

publicclassTimedPartyEvent {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

In this example we want the TimedPartyEvent to be converted into arbitrary target formats (contexts). The first step would be to implement the CopperConvertable interface and annotate all fields we want to be included with the @CopperField annotation.

publicclassTimedPartyEventimplementsCopperConvertable {
@CopperFieldprivateOffsetDateTimeat;
@CopperFieldprivatePartyEventTypetype;
privatebooleansomeInternalFlag;
}

Done, that's basically all there is to the basic POJO definition.

The @CopperField Annotation

The @CopperField annotation provides some properties to override or extend the functionality for each field.

NameTypeDescription
nameStringThe name to use for the external formats. Defaults to the snake_cased field name.
converterConverter.classThe converter to use for this field specifically. Defaults to the closest relative converter defined in the agent's registry for the context used.
typeMapperCopperTypeMapper.classThe type mapper to use. Defaults to none. See Advanced Usage / Type Mappers.

The @CopperFields Annotation

Instead of annotating all class members with @CopperField, you can annotate the class itself (or any of it's supertypes or implemented interfaces). Individual fields can additionally be annotated with @CopperField, if functionality needs to be altered for those specifically.

The @CopperIgnore Annotation

To exclude a field from @CopperFields you can use the @CopperIgnore annotation without arguments. If you want to exclude this field for one or more specific contexts, you can provide a list of matching context classes.

Iterables / Maps

Because generic types are only hints for the compiler, they are not available at runtime. This will cause values of Iterables to not be converted back to their original types when converting toOurs. To fix this behavior, you may annotate the iterable field with @CopperValueType, defining the same type defined in the generic parameter.

@CopperField@CopperValueType(UUID.class)
privatefinalList<UUID> uuids = newArrayList<>();

The @CopperKeyType annotation provides the same functionality for keys of Map fields.

Bson Conversion

The copperfield-bson module implements provides the BsonRegistry containing these default converters for the Document context.

Our TypeTheir TypeConverterComment
CopperConvertableDocumentCopperToBsonConverterConverts classes implementing CopperConvertable to bson Documents.
byte[]BinaryByteArrayToBsonBinaryConverterConverts byte arrays to the bson Binary format.
ObjectIdObjectIdNoOperationConverterDoes not convert ObjectIds at all in the Document context.

As well as this default converter when not converting in the Document context.

Our TypeTheir TypeConverterComment
ObjectIdStringBsonObjectIdToStringConverterConverts ObjectIds to the hex string representation.

If you want to override the converter used for a specific field for the Document context only, you can annotate the @CopperBsonField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Proto Conversion

The copperfield-proto module implements provides the ProtoRegistry containing these default converters for the MessageLiteOrBuilder context.

Our TypeTheir TypeConverterComment
CopperConvertableMessageLiteOrBuilderCopperToProtoConverterConverts classes implementing CopperConvertable to MessageLiteOrBuilders.
byte[]ByteStringByteArrayToProtoByteStringConverterConverts byte arrays to the proto ByteString format.
MapStructMapToProtoStructConverterConverts Maps to proto Structs.

This converter exists but is not registered by default.

Our TypeTheir TypeConverterComment
OffsetDateTimeTimestampOffsetDateTimeToProtoTimestampConverterConverts OffsetDateTimes to proto Timestamps using the given timezone.

If you want to override the converter used for a specific field for the MessageLiteOrBuilder context only, you can annotate the @CopperProtoField annotation in addition to @CopperFields or @CopperField. The annotation shares the signature with the @CopperField annotation and will fall back to the values defined in the @CopperField annotation.

Note: To convert CopperConvertables to and from proto messages, you have to annotate the class with @CopperProtoClass. The given type should be a type diverging from MessageLiteOrBuilder which this POJO represents.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
}

Advanced Usage

Type Mappers

The explanation will be based on the following example.

@CopperFields@CopperProtoField(type = PartyProtos.PartyEvent)
publicclassTimedPartyEventimplementsCopperConvertable {
privateOffsetDateTimeat;
privatePartyEventTypetype;
privatePartyEventevent;
}

Let's assume PartyEvent is an interface. Converting the event toTheirs will work no problem because the agent will use the value's concrete type. However, converting it back toOurs will lose the concrete PartyEvent type resulting in an exception, because the PartyEvent interface can not be instantiated.

To map the interface to a concrete class, copperfield provides CopperTypeMappers.

publicclassPartyEventTypeMapperextendsCopperTypeMapper<TimedPartyEvent, PartyEvent> {
publicPartyEventCopperTypeMapper() {
super("type");
}
@NotNull@OverridepublicClass<? extendsPartyEvent> mapType(finalTimedPartyEventinstance, @NotNullfinalClass<?> valueType) {
returninstance.type.type;
}
}

The type mapper declares, which fields are essential to decide on the concrete class. Copperfield makes sure that the required fields will be populated on the instance if their corresponding values are not null and as long as there are no cylcic field requirements.

About

A flexible, lightweight library to do ORMs using annotations (and magic).

Resources

Stars

0 stars

Watchers

3 watching

Forks

Releases

Packages

Contributors

Languages