All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.javers.core.JaversBuilder Maven / Gradle / Ivy

There is a newer version: 7.6.1
Show newest version
package org.javers.core;

import com.google.gson.TypeAdapter;
import org.javers.common.collections.Lists;
import org.javers.common.date.DateProvider;
import org.javers.common.date.DefaultDateProvider;
import org.javers.common.validation.Validate;
import org.javers.core.JaversCoreProperties.PrettyPrintDateFormats;
import org.javers.core.commit.Commit;
import org.javers.core.commit.CommitFactoryModule;
import org.javers.core.commit.CommitId;
import org.javers.core.diff.Diff;
import org.javers.core.diff.DiffFactoryModule;
import org.javers.core.diff.ListCompareAlgorithm;
import org.javers.core.diff.appenders.DiffAppendersModule;
import org.javers.core.diff.custom.*;
import org.javers.core.graph.GraphFactoryModule;
import org.javers.core.graph.ObjectAccessHook;
import org.javers.core.graph.TailoredJaversMemberFactoryModule;
import org.javers.core.json.JsonAdvancedTypeAdapter;
import org.javers.core.json.JsonConverter;
import org.javers.core.json.JsonConverterBuilder;
import org.javers.core.json.JsonTypeAdapter;
import org.javers.core.json.typeadapter.change.ChangeTypeAdaptersModule;
import org.javers.core.json.typeadapter.commit.CommitTypeAdaptersModule;
import org.javers.core.json.typeadapter.commit.DiffTypeDeserializer;
import org.javers.core.metamodel.annotation.*;
import org.javers.core.metamodel.clazz.*;
import org.javers.core.metamodel.scanner.ScannerModule;
import org.javers.core.metamodel.type.*;
import org.javers.core.pico.AddOnsModule;
import org.javers.core.snapshot.SnapshotModule;
import org.javers.groovysupport.GroovyAddOns;
import org.javers.guava.GuavaAddOns;
import org.javers.jodasupport.JodaAddOns;
import org.javers.mongosupport.MongoLong64JsonDeserializer;
import org.javers.mongosupport.RequiredMongoSupportPredicate;
import org.javers.repository.api.ConfigurationAware;
import org.javers.repository.api.JaversExtendedRepository;
import org.javers.repository.api.JaversRepository;
import org.javers.repository.inmemory.InMemoryRepository;
import org.javers.repository.jql.JqlModule;
import org.javers.shadow.ShadowModule;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

import static org.javers.common.reflection.ReflectionUtil.findClasses;
import static org.javers.common.reflection.ReflectionUtil.isClassPresent;
import static org.javers.common.validation.Validate.argumentIsNotNull;
import static org.javers.common.validation.Validate.argumentsAreNotNull;

/**
 * Creates a JaVers instance based on your domain model metadata and custom configuration.
 * 

* * For example, to build a JaVers instance configured with reasonable defaults: *
 * Javers javers = JaversBuilder.javers().build();
 * 
* * To build a JaVers instance with Entity type registered: *
 * Javers javers = JaversBuilder.javers()
 *                              .registerEntity(MyEntity.class)
 *                              .build();
 * 
* * @see http://javers.org/documentation/domain-configuration * @author bartosz walacik */ public class JaversBuilder extends AbstractContainerBuilder { public static final Logger logger = LoggerFactory.getLogger(JaversBuilder.class); private final Map clientsClassDefinitions = new LinkedHashMap<>(); private final Map> mappedToStringFunction = new ConcurrentHashMap<>(); private final Set classesToScan = new HashSet<>(); private final Set conditionalTypesPlugins; private JaversRepository repository; private DateProvider dateProvider; private long bootStart = System.currentTimeMillis(); public static JaversBuilder javers() { return new JaversBuilder(); } /** * use static factory method {@link JaversBuilder#javers()} */ protected JaversBuilder() { logger.debug("starting up JaVers ..."); //conditional plugins conditionalTypesPlugins = new HashSet<>(); if (isClassPresent("groovy.lang.MetaClass")) { conditionalTypesPlugins.add(new GroovyAddOns()); } if (isClassPresent("org.joda.time.LocalDate")){ conditionalTypesPlugins.add(new JodaAddOns()); } if (isClassPresent("com.google.common.collect.Multimap")) { conditionalTypesPlugins.add(new GuavaAddOns()); } // bootstrap pico container & core module bootContainer(); addModule(new CoreJaversModule(getContainer())); } public Javers build() { Javers javers = assembleJaversInstance(); repository.ensureSchema(); long boot = System.currentTimeMillis() - bootStart; logger.info("JaVers instance started in {} ms", boot); return javers; } protected Javers assembleJaversInstance(){ // boot main modules addModule(new DiffFactoryModule()); addModule(new CommitFactoryModule(getContainer())); addModule(new SnapshotModule(getContainer())); addModule(new GraphFactoryModule(getContainer())); addModule(new DiffAppendersModule(coreConfiguration(), getContainer())); addModule(new TailoredJaversMemberFactoryModule(coreConfiguration(), getContainer())); addModule(new ScannerModule(coreConfiguration(), getContainer())); addModule(new ShadowModule(getContainer())); addModule(new JqlModule(getContainer())); // boot add-ons modules Set additionalTypes = bootAddOns(); // boot TypeMapper module addModule(new TypeMapperModule(getContainer())); // boot JSON beans & domain aware typeAdapters additionalTypes.addAll( bootJsonConverter() ); bootDateTimeProvider(); // clases to scan & additionalTypes for (Class c : classesToScan){ typeMapper().getJaversType(c); } typeMapper().addPluginTypes(additionalTypes); mapRegisteredClasses(); bootRepository(); return getContainerComponent(JaversCore.class); } /** * @see http://javers.org/documentation/repository-configuration */ public JaversBuilder registerJaversRepository(JaversRepository repository) { argumentsAreNotNull(repository); this.repository = repository; return this; } /** * Registers an {@link EntityType}.
* Use @Id annotation to mark exactly one Id-property. *

* * Optionally, use @Transient or @{@link DiffIgnore} annotations to mark ignored properties. *

* * For example, Entities are: Person, Document * * @see http://javers.org/documentation/domain-configuration/#entity * @see #registerEntity(EntityDefinition) */ public JaversBuilder registerEntity(Class entityClass) { argumentIsNotNull(entityClass); return registerEntity( new EntityDefinition(entityClass)); } /** * Registers a {@link ValueObjectType}.
* Optionally, use @Transient or @{@link DiffIgnore} annotations to mark ignored properties. *

* * For example, ValueObjects are: Address, Point * * @see http://javers.org/documentation/domain-configuration/#value-object * @see #registerValueObject(ValueObjectDefinition) */ public JaversBuilder registerValueObject(Class valueObjectClass) { argumentIsNotNull(valueObjectClass); registerType(new ValueObjectDefinition(valueObjectClass)); return this; } /** * Registers an {@link EntityType}.
* Use this method if you are not willing to use {@link Entity} annotation. *

* * Recommended way to create {@link EntityDefinition} is {@link EntityDefinitionBuilder}, * for example: *
     * javersBuilder.registerEntity(
     *     EntityDefinitionBuilder.entityDefinition(Person.class)
     *     .withIdPropertyName("id")
     *     .withTypeName("Person")
     *     .withIgnoredProperties("notImportantProperty","transientProperty")
     *     .build());
     * 
* * For simple cases, you can use {@link EntityDefinition} constructors, * for example: *
     * javersBuilder.registerEntity( new EntityDefinition(Person.class, "login") );
     * 
* * @see http://javers.org/documentation/domain-configuration/#entity * @see EntityDefinitionBuilder#entityDefinition(Class) */ public JaversBuilder registerEntity(EntityDefinition entityDefinition){ argumentIsNotNull(entityDefinition); return registerType(entityDefinition); } /** * Generic version of {@link #registerEntity(EntityDefinition)} and * {@link #registerValueObject(ValueObjectDefinition)} */ public JaversBuilder registerType(ClientsClassDefinition clientsClassDefinition) { argumentIsNotNull(clientsClassDefinition); clientsClassDefinitions.put(clientsClassDefinition.getBaseJavaClass(), clientsClassDefinition); return this; } /** * Registers a {@link ValueObjectType}.
* Use this method if you are not willing to use {@link ValueObject} annotations. *

* * Recommended way to create {@link ValueObjectDefinition} is {@link ValueObjectDefinitionBuilder}. * For example: *
     * javersBuilder.registerValueObject(ValueObjectDefinitionBuilder.valueObjectDefinition(Address.class)
     *     .withIgnoredProperties(ignoredProperties)
     *     .withTypeName(typeName)
     *     .build();
     * 
* * For simple cases, you can use {@link ValueObjectDefinition} constructors, * for example: *
     * javersBuilder.registerValueObject( new ValueObjectDefinition(Address.class, "ignored") );
     * 
* * @see http://javers.org/documentation/domain-configuration/#value-object * @see ValueObjectDefinitionBuilder#valueObjectDefinition(Class) */ public JaversBuilder registerValueObject(ValueObjectDefinition valueObjectDefinition) { argumentIsNotNull(valueObjectDefinition); registerType(valueObjectDefinition); return this; } /** * Comma separated list of packages scanned by Javers in search of * your classes with the {@link TypeName} annotation. *

* * It's important to declare here all of your packages containing classes with {@literal @}TypeName,
* because Javers needs live class definitions to properly deserialize Snapshots from {@link JaversRepository}. *

* * For example, consider this class: * *
     * {@literal @}Entity
     * {@literal @}TypeName("Person")
     *  class Person {
     *     {@literal @}Id
     *      private int id;
     *      private String name;
     *  }
     * 
* * In the scenario when Javers reads a Snapshot of type named 'Person' * before having a chance to map the Person class definition, * the 'Person' type will be mapped to generic {@link UnknownType}. *

* * Since 5.8.4, Javers logs WARNING when UnknownType is created * because Snapshots with UnknownType can't be properly deserialized from {@link JaversRepository}. * * @param packagesToScan e.g. "my.company.domain.person, my.company.domain.finance" * @since 2.3 */ public JaversBuilder withPackagesToScan(String packagesToScan) { if (packagesToScan == null || packagesToScan.trim().isEmpty()) { return this; } long start = System.currentTimeMillis(); logger.info("scanning package(s): {}", packagesToScan); List> scan = findClasses(TypeName.class, packagesToScan.replaceAll(" ","").split(",")); for (Class c : scan) { scanTypeName(c); } long delta = System.currentTimeMillis() - start; logger.info(" found {} ManagedClasse(s) with @TypeName in {} ms", scan.size(), delta); return this; } /** * Register your class with @{@link TypeName} annotation * in order to use it in all kinds of JQL queries. *

* * You can also use {@link #withPackagesToScan(String)} * to scan all your classes. *

* * Technically, this method is the convenient alias for {@link Javers#getTypeMapping(Type)} * * @since 1.4 */ public JaversBuilder scanTypeName(Class userType){ classesToScan.add(userType); return this; } /** * Registers a simple value type (see {@link ValueType}). *

* * For example, values are: BigDecimal, LocalDateTime. *

* * Use this method if can't use the {@link Value} annotation. *

* * By default, Values are compared using {@link Object#equals(Object)}. * You can provide external equals() function * by registering a {@link CustomValueComparator}. * See {@link #registerValue(Class, CustomValueComparator)}. * * @see http://javers.org/documentation/domain-configuration/#ValueType */ public JaversBuilder registerValue(Class valueClass) { argumentIsNotNull(valueClass); registerType(new ValueDefinition(valueClass)); return this; } /** * Registers a {@link ValueType} with a custom comparator to be used instead of * {@link Object#equals(Object)}. *

* * For example, by default, BigDecimals are Values * compared using {@link java.math.BigDecimal#equals(Object)}, * sadly it isn't the correct mathematical equality: * *
     *     new BigDecimal("1.000").equals(new BigDecimal("1.00")) == false
     * 
* * If you want to compare them in the right way — ignoring trailing zeros — * register this comparator: * *
     * JaversBuilder.javers()
     *     .registerValue(BigDecimal.class, new BigDecimalComparatorWithFixedEquals())
     *     .build();
     * 
* * @param Value Type * @see http://javers.org/documentation/domain-configuration/#ValueType * @see https://javers.org/documentation/diff-configuration/#custom-comparators * @see BigDecimalComparatorWithFixedEquals * @see CustomBigDecimalComparator * @since 3.3 */ public JaversBuilder registerValue(Class valueClass, CustomValueComparator customValueComparator) { argumentsAreNotNull(valueClass, customValueComparator); if (!clientsClassDefinitions.containsKey(valueClass)){ registerType(new ValueDefinition(valueClass)); } ValueDefinition def = getClassDefinition(valueClass); def.setCustomValueComparator(customValueComparator); return this; } /** * Lambda-style variant of {@link #registerValue(Class, CustomValueComparator)}. *

* * For example, you can register the comparator for BigDecimals with fixed equals: * *
     * Javers javers = JaversBuilder.javers()
     *     .registerValue(BigDecimal.class, (a, b) -> a.compareTo(b) == 0,
     *                                           a -> a.stripTrailingZeros().toString())
     *     .build();
     * 
* * @param Value Type * @see #registerValue(Class, CustomValueComparator) * @since 5.8 */ public JaversBuilder registerValue(Class valueClass, BiFunction equalsFunction, Function toStringFunction) { Validate.argumentsAreNotNull(valueClass, equalsFunction, toStringFunction); return registerValue(valueClass, new CustomValueComparator() { @Override public boolean equals(T a, T b) { return equalsFunction.apply(a,b); } @Override public String toString(@Nonnull T value) { return toStringFunction.apply(value); } }); } /** * Deprecated, use {@link #registerValue(Class, CustomValueComparator)}. * *

* * Since this comparator is not aligned with {@link Object#hashCode()}, * it calculates incorrect results when a given Value is used in hashing context * (when comparing Sets with Values or Maps with Values as keys). * * @see CustomValueComparator */ @Deprecated public JaversBuilder registerValue(Class valueClass, BiFunction equalsFunction) { Validate.argumentsAreNotNull(valueClass, equalsFunction); return registerValue(valueClass, new CustomValueComparator() { @Override public boolean equals(T a, T b) { return equalsFunction.apply(a,b); } @Override public String toString(@Nonnull T value) { return value.toString(); } }); } /** * Deprecated, use {@link #registerValue(Class, CustomValueComparator)}. * * @see CustomValueComparator * @since 3.7.6 */ @Deprecated public JaversBuilder registerValueWithCustomToString(Class valueClass, Function toStringFunction) { Validate.argumentsAreNotNull(valueClass, toStringFunction); return registerValue(valueClass, (a,b) -> Objects.equals(a,b), toStringFunction); } /** * Marks given class as ignored by JaVers. *

* * Use this method if you are not willing to use {@link DiffIgnore} annotation. * * @see DiffIgnore */ public JaversBuilder registerIgnoredClass(Class ignoredClass) { argumentIsNotNull(ignoredClass); registerType(new IgnoredTypeDefinition(ignoredClass)); return this; } /** * Registers a {@link ValueType} and its custom JSON adapter. *

* * Useful for not trivial ValueTypes when Gson's default representation isn't appropriate * * @see http://javers.org/documentation/repository-configuration/#json-type-adapters * @see JsonTypeAdapter */ public JaversBuilder registerValueTypeAdapter(JsonTypeAdapter typeAdapter) { for (Class c : (List)typeAdapter.getValueTypes()){ registerValue(c); } jsonConverterBuilder().registerJsonTypeAdapter(typeAdapter); return this; } /** * INCUBATING
* * For complex structures like Multimap * @since 3.1 */ public JaversBuilder registerJsonAdvancedTypeAdapter(JsonAdvancedTypeAdapter adapter) { jsonConverterBuilder().registerJsonAdvancedTypeAdapter(adapter); return this; } /** * Registers {@link ValueType} and its custom native * Gson adapter. *

* * Useful when you already have Gson {@link TypeAdapter}s implemented. * * @see TypeAdapter */ public JaversBuilder registerValueGsonTypeAdapter(Class valueType, TypeAdapter nativeAdapter) { registerValue(valueType); jsonConverterBuilder().registerNativeTypeAdapter(valueType, nativeAdapter); return this; } /** * Switch on when you need a type safe serialization for * heterogeneous collections like List, List<Object>. *

* * Heterogeneous collections are collections which contains items of different types * (or types unknown at compile time). *

* * This approach is generally discouraged, prefer statically typed collections * with exactly one type of items like List<String>. * * @see org.javers.core.json.JsonConverterBuilder#typeSafeValues(boolean) * @param typeSafeValues default false */ public JaversBuilder withTypeSafeValues(boolean typeSafeValues) { jsonConverterBuilder().typeSafeValues(typeSafeValues); return this; } /** * choose between JSON pretty or concise printing style, i.e. : * *
  • pretty: *
         * {
         *     "value": 5
         * }
         * 
    *
  • concise: *
         * {"value":5}
         * 
    *
* * @param prettyPrint default true */ public JaversBuilder withPrettyPrint(boolean prettyPrint) { jsonConverterBuilder().prettyPrint(prettyPrint); return this; } public JaversBuilder registerEntities(Class... entityClasses) { for(Class clazz : entityClasses) { registerEntity(clazz); } return this; } public JaversBuilder registerValueObjects(Class... valueObjectClasses) { for(Class clazz : valueObjectClasses) { registerValueObject(clazz); } return this; } /** * Default style is {@link MappingStyle#FIELD}. * * @see http://javers.org/documentation/domain-configuration/#property-mapping-style */ public JaversBuilder withMappingStyle(MappingStyle mappingStyle) { argumentIsNotNull(mappingStyle); coreConfiguration().withMappingStyle(mappingStyle); return this; } /** *
    *
  • {@link CommitIdGenerator#SYNCHRONIZED_SEQUENCE} — for non-distributed applications *
  • {@link CommitIdGenerator#RANDOM} — for distributed applications *
* SYNCHRONIZED_SEQUENCE is used by default. */ public JaversBuilder withCommitIdGenerator(CommitIdGenerator commitIdGenerator) { coreConfiguration().withCommitIdGenerator(commitIdGenerator); return this; } JaversBuilder withCustomCommitIdGenerator(Supplier commitIdGenerator) { coreConfiguration().withCustomCommitIdGenerator(commitIdGenerator); return this; } /** * When enabled, {@link Javers#compare(Object oldVersion, Object currentVersion)} * generates additional 'Snapshots' of new objects (objects added in currentVersion graph). *
* For each new object, state of its properties is captured and returned as a Set of PropertyChanges. * These Changes have null at the left side and a current property value at the right side. *

* * Disabled by default. */ public JaversBuilder withNewObjectsSnapshot(boolean newObjectsSnapshot){ coreConfiguration().withNewObjectsSnapshot(newObjectsSnapshot); return this; } public JaversBuilder withObjectAccessHook(ObjectAccessHook objectAccessHook) { removeComponent(ObjectAccessHook.class); bindComponent(ObjectAccessHook.class, objectAccessHook); return this; } /** * Registers a {@link CustomPropertyComparator} for a given class and maps this class * to {@link CustomType}. *

* * * Custom Types are not easy to manage, use it as a last resort,
* only for corner cases like comparing custom Collection types.
*

* * In most cases, it's better to customize the Javers' diff algorithm using * much more simpler {@link CustomValueComparator}, * see {@link #registerValue(Class, CustomValueComparator)}. * * @param Custom Type * @see https://javers.org/documentation/diff-configuration/#custom-comparators */ public JaversBuilder registerCustomType(Class customType, CustomPropertyComparator comparator){ registerType(new CustomDefinition(customType, comparator)); bindComponent(comparator, new CustomToNativeAppenderAdapter(comparator, customType)); return this; } /** * @deprecated Renamed to {@link #registerCustomType(Class, CustomPropertyComparator)} */ @Deprecated public JaversBuilder registerCustomComparator(CustomPropertyComparator comparator, Class customType){ return registerCustomType(customType, comparator); } /** * Choose between two algorithms for comparing list: ListCompareAlgorithm.SIMPLE * or ListCompareAlgorithm.LEVENSHTEIN_DISTANCE. *

* Generally, we recommend using LEVENSHTEIN_DISTANCE, because it's smarter. * However, it can be slow for long lists, so SIMPLE is enabled by default. *

* * Refer to http://javers.org/documentation/diff-configuration/#list-algorithms * for description of both algorithms * * @param algorithm ListCompareAlgorithm.SIMPLE is used by default */ public JaversBuilder withListCompareAlgorithm(ListCompareAlgorithm algorithm) { argumentIsNotNull(algorithm); coreConfiguration().withListCompareAlgorithm(algorithm); return this; } /** * DateProvider providers current util for {@link Commit#getCommitDate()}. *
* By default, now() is used. *
* Overriding default dateProvider probably makes sense only in test environment. */ public JaversBuilder withDateTimeProvider(DateProvider dateProvider) { argumentIsNotNull(dateProvider); this.dateProvider = dateProvider; return this; } public JaversBuilder withPrettyPrintDateFormats(PrettyPrintDateFormats prettyPrintDateFormats) { coreConfiguration().withPrettyPrintDateFormats(prettyPrintDateFormats); return this; } public JaversBuilder withProperties(JaversCoreProperties javersProperties) { this.withListCompareAlgorithm(ListCompareAlgorithm.valueOf(javersProperties.getAlgorithm().toUpperCase())) .withCommitIdGenerator(CommitIdGenerator.valueOf(javersProperties.getCommitIdGenerator().toUpperCase())) .withMappingStyle(MappingStyle.valueOf(javersProperties.getMappingStyle().toUpperCase())) .withNewObjectsSnapshot(javersProperties.isNewObjectSnapshot()) .withPrettyPrint(javersProperties.isPrettyPrint()) .withTypeSafeValues(javersProperties.isTypeSafeValues()) .withPackagesToScan(javersProperties.getPackagesToScan()) .withPrettyPrintDateFormats(javersProperties.getPrettyPrintDateFormats()); return this; } private void mapRegisteredClasses() { TypeMapper typeMapper = typeMapper(); for (ClientsClassDefinition def : clientsClassDefinitions.values()) { typeMapper.registerClientsClass(def); } } private TypeMapper typeMapper() { return getContainerComponent(TypeMapper.class); } private JaversCoreConfiguration coreConfiguration() { return getContainerComponent(JaversCoreConfiguration.class); } private JsonConverterBuilder jsonConverterBuilder(){ return getContainerComponent(JsonConverterBuilder.class); } private Set bootAddOns() { Set additionalTypes = new HashSet<>(); for (ConditionalTypesPlugin plugin : conditionalTypesPlugins) { logger.info("loading "+plugin.getClass().getSimpleName()+" ..."); plugin.beforeAssemble(this); additionalTypes.addAll(plugin.getNewTypes()); AddOnsModule addOnsModule = new AddOnsModule(getContainer(), (Collection)plugin.getPropertyChangeAppenders()); addModule(addOnsModule); } return additionalTypes; } /** * boots JsonConverter and registers domain aware typeAdapters */ private Collection bootJsonConverter() { JsonConverterBuilder jsonConverterBuilder = jsonConverterBuilder(); addModule(new ChangeTypeAdaptersModule(getContainer())); addModule(new CommitTypeAdaptersModule(getContainer())); if (new RequiredMongoSupportPredicate().test(repository)) { jsonConverterBuilder.registerNativeGsonDeserializer(Long.class, new MongoLong64JsonDeserializer()); } jsonConverterBuilder.registerJsonTypeAdapters(getComponents(JsonTypeAdapter.class)); jsonConverterBuilder.registerNativeGsonDeserializer(Diff.class, new DiffTypeDeserializer()); JsonConverter jsonConverter = jsonConverterBuilder.build(); addComponent(jsonConverter); return Lists.transform(jsonConverterBuilder.getBuiltInValueTypes(), c -> new ValueType(c)); } private void bootDateTimeProvider() { if (dateProvider == null) { dateProvider = new DefaultDateProvider(); } addComponent(dateProvider); } private void bootRepository(){ if (repository == null){ logger.info("using fake InMemoryRepository, register actual Repository implementation via JaversBuilder.registerJaversRepository()"); repository = new InMemoryRepository(); } repository.setJsonConverter( getContainerComponent(JsonConverter.class)); if (repository instanceof ConfigurationAware){ ((ConfigurationAware) repository).setConfiguration(coreConfiguration()); } bindComponent(JaversRepository.class, repository); //JaversExtendedRepository can be created after users calls JaversBuilder.registerJaversRepository() addComponent(JaversExtendedRepository.class); } private T getClassDefinition(Class baseJavaClass) { return (T)clientsClassDefinitions.get(baseJavaClass); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy