diff --git a/bellatrix.core/pom.xml b/bellatrix.core/pom.xml
index 23e2f014..478c5657 100644
--- a/bellatrix.core/pom.xml
+++ b/bellatrix.core/pom.xml
@@ -153,5 +153,20 @@
${rest.assured.version}
compile
+
+ com.azure
+ azure-security-keyvault-secrets
+ 4.2.3
+
+
+ com.azure
+ azure-identity
+ 1.2.0
+
+
+ org.jsoup
+ jsoup
+ 1.17.2
+
\ No newline at end of file
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ConverterService.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ConverterService.java
index 466746b8..7023c17f 100644
--- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ConverterService.java
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/ConverterService.java
@@ -1,14 +1,33 @@
+/*
+ * Copyright 2024 Automate The Planet Ltd.
+ * Author: Miriam Kyoseva
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
package solutions.bellatrix.core.utilities;
import lombok.SneakyThrows;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
public class ConverterService {
public static ResultT convert(SourceT source, int index) {
ResultT object = InstanceFactory.createByTypeParameter(source.getClass(), index);
+ assert object != null;
return convert(source, object);
}
+ @SuppressWarnings("unchecked")
public static ResultT convert(SourceT source, ResultT result) {
var object = (ResultT) InstanceFactory.create(result.getClass());
@@ -38,8 +57,8 @@ public static ResultT convert(SourceT source, ResultT result)
public static ResultT convertToClass(SourceT source, Class result) {
var object = (ResultT) InstanceFactory.create(result);
- var sourceFields = source.getClass().getDeclaredFields();
- var objectFields = object.getClass().getDeclaredFields();
+ List sourceFields = getAllFields(source.getClass());
+ List objectFields = getAllFields(object.getClass());
for (var sourceField : sourceFields) {
for (var objectField : objectFields) {
@@ -55,4 +74,19 @@ public static ResultT convertToClass(SourceT source, Class getAllFields(Class> clazz) {
+ List fields = new ArrayList<>();
+ Class> currentClass = clazz;
+
+ while (currentClass != null) {
+ var declaredFields = currentClass.getDeclaredFields();
+ for (Field field : declaredFields) {
+ fields.add(field);
+ }
+ currentClass = currentClass.getSuperclass();
+ }
+
+ return fields;
+ }
}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/HtmlService.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/HtmlService.java
new file mode 100644
index 00000000..306e62cc
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/HtmlService.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2024 Automate The Planet Ltd.
+ * Author: Miriam Kyoseva
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package solutions.bellatrix.core.utilities;
+
+import lombok.experimental.UtilityClass;
+import org.jsoup.nodes.Document;
+import org.jsoup.nodes.Element;
+import org.jsoup.nodes.Node;
+import solutions.bellatrix.core.utilities.parsing.TypeParser;
+
+import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Web Utility Class
+ */
+@UtilityClass
+public class HtmlService {
+ private static final String CHILD_COMBINATOR = " > ";
+ private static final String NODE = "/";
+ private static final String NODE_OR_SELF = "//";
+ private static final String ROOT_ELEMENT_TAG = "bellatrix-root";
+
+ public static Document addRootElementIfNeeded(Document doc) {
+ boolean hasMultipleTopLevelElements = doc.childNodes().stream()
+ .filter(node -> node instanceof Element).count() > 1;
+
+ if (hasMultipleTopLevelElements) {
+ var root = new Element(ROOT_ELEMENT_TAG);
+
+ for (Node node : doc.childNodes()) {
+ root.appendChild(node.clone());
+ }
+
+ doc.empty();
+ doc.appendChild(root);
+ }
+
+ return doc;
+ }
+
+ public static String getAbsoluteXpath(Element element) {
+ StringBuilder xpath = new StringBuilder();
+
+ Element currentElement = element;
+ while (currentElement != null) {
+ if (currentElement.tagName().equals("html") || currentElement.tagName().equals("body") || currentElement.tagName().startsWith("#") || currentElement.tagName().equals(ROOT_ELEMENT_TAG)) {
+ // ignore the and , because jsoup added them to the html fragment
+ // ignore added bellatrix root element
+ // ignore invalid element tags
+ break;
+ }
+
+ xpath.insert(0, indexElement(currentElement));
+
+ currentElement = currentElement.parent();
+ }
+
+ return xpath.toString();
+ }
+
+ private String indexElement(Element element) {
+ int index = 1;
+
+ Node previousSibling = element.previousSibling();
+ while (previousSibling != null) {
+ if (previousSibling.nodeName().equals(element.nodeName())) {
+ index++;
+ }
+ previousSibling = previousSibling.previousSibling();
+ }
+
+ return NODE + element.tagName() + "[" + index + "]";
+ }
+
+ public T getAttribute(Element element, String attributeName, Class clazz) {
+ if (element.attribute(attributeName) == null || element.attribute(attributeName).getValue() == null || element.attribute(attributeName).getValue().isBlank()) {
+ return null;
+ } else {
+ return TypeParser.parse(element.attribute(attributeName).getValue(), clazz);
+ }
+ }
+
+ public static String convertAbsoluteXpathToCss(String xpath) {
+ String cssSelector = xpath.replace(NODE, CHILD_COMBINATOR);
+
+ // Use regular expression to replace [number] with :nth-of-type(number)
+ Pattern pattern = Pattern.compile("\\[(\\d+)\\]");
+ Matcher matcher = pattern.matcher(cssSelector);
+ StringBuilder builder = new StringBuilder();
+
+ while (matcher.find()) {
+ matcher.appendReplacement(builder, ":nth-of-type(" + matcher.group(1) + ")");
+ }
+ matcher.appendTail(builder);
+
+ var semiFinalLocator = builder.toString();
+
+ if (semiFinalLocator.startsWith(CHILD_COMBINATOR)) {
+ semiFinalLocator = semiFinalLocator.substring(2);
+ }
+
+ return semiFinalLocator;
+ }
+
+ public static String removeDanglingChildCombinatorsFromCss(String css) {
+ // convert to array by splitting the css by the child combinator
+ // and remove from that array empty steps
+ var steps = Arrays.stream(css.split(CHILD_COMBINATOR))
+ .filter(x -> !x.isBlank())
+ .toArray(String[]::new);
+
+ // join the remaining steps with child combinator operators
+ return String.join(CHILD_COMBINATOR, steps);
+ }
+}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/InstanceFactory.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/InstanceFactory.java
index fc630195..dd8e5360 100644
--- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/InstanceFactory.java
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/InstanceFactory.java
@@ -16,6 +16,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
+@SuppressWarnings("unchecked")
public final class InstanceFactory {
public static T create(Class classOf) {
T obj = null;
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReference.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReference.java
new file mode 100644
index 00000000..494f510b
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReference.java
@@ -0,0 +1,8 @@
+package solutions.bellatrix.core.utilities;
+
+import java.io.Serializable;
+
+@FunctionalInterface
+public interface PropertyReference extends Serializable {
+ Object apply(T t);
+}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReferenceNameResolver.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReferenceNameResolver.java
new file mode 100644
index 00000000..839e0282
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/PropertyReferenceNameResolver.java
@@ -0,0 +1,33 @@
+package solutions.bellatrix.core.utilities;
+
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+
+import java.lang.invoke.SerializedLambda;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+@UtilityClass
+public class PropertyReferenceNameResolver {
+ public static Field getMember(Class clazz, PropertyReference propertyReference) {
+ var methodName = getFunctionalMethodName(propertyReference);
+ var fields = clazz.getDeclaredFields();
+
+ for (var field : fields) {
+ if (methodName.toLowerCase().contains(field.getName().toLowerCase())) {
+ return field;
+ }
+ }
+
+ return null;
+ }
+
+ @SneakyThrows
+ public static String getFunctionalMethodName(PropertyReference> functionalInterface) {
+ Method writeReplaceMethod = functionalInterface.getClass().getDeclaredMethod("writeReplace");
+ writeReplaceMethod.setAccessible(true);
+ SerializedLambda serializedLambda = (SerializedLambda) writeReplaceMethod.invoke(functionalInterface);
+
+ return serializedLambda.getImplMethodName();
+ }
+}
diff --git a/bellatrix.playwright/src/main/java/solutions/bellatrix/playwright/utilities/functionalinterfaces/AssertionMethod.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Ref.java
similarity index 75%
rename from bellatrix.playwright/src/main/java/solutions/bellatrix/playwright/utilities/functionalinterfaces/AssertionMethod.java
rename to bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Ref.java
index 66018e0c..fe74b7a6 100644
--- a/bellatrix.playwright/src/main/java/solutions/bellatrix/playwright/utilities/functionalinterfaces/AssertionMethod.java
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Ref.java
@@ -11,9 +11,15 @@
* limitations under the License.
*/
-package solutions.bellatrix.playwright.utilities.functionalinterfaces;
+package solutions.bellatrix.core.utilities;
-@FunctionalInterface
-public interface AssertionMethod {
- void perform();
-}
+/**
+ * Wrapper for passing primitives by reference.
+ */
+public class Ref {
+ public T value;
+
+ public Ref(T value) {
+ this.value = value;
+ }
+}
\ No newline at end of file
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java
index 1988bff3..dd7d93b8 100644
--- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/SingletonFactory.java
@@ -13,31 +13,42 @@
package solutions.bellatrix.core.utilities;
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+
import java.util.HashMap;
import java.util.Map;
// Based on http://neutrofoton.github.io/blog/2013/08/29/generic-singleton-pattern-in-java/
// Can be used inside App design pattern.
+@SuppressWarnings("unchecked")
public class SingletonFactory {
private static final SingletonFactory SINGLETON_FACTORY = new SingletonFactory();
- private final Map mapHolder = new HashMap<>();
+ private final Map, Object> mapHolder = new HashMap<>();
private SingletonFactory() {
}
+ @SneakyThrows
public static T getInstance(Class classOf, Object... initargs) {
try {
- if (!SINGLETON_FACTORY.mapHolder.containsKey(classOf.getName())) {
-
+ if (!SINGLETON_FACTORY.mapHolder.containsKey(classOf)) {
T obj = (T)classOf.getConstructors()[0].newInstance(initargs);
- SINGLETON_FACTORY.mapHolder.put(classOf.getName(), obj);
+ SINGLETON_FACTORY.mapHolder.put(classOf, obj);
}
-
- return (T)SINGLETON_FACTORY.mapHolder.get(classOf.getName());
+ return (T)SINGLETON_FACTORY.mapHolder.get(classOf);
} catch (Exception e) {
Log.error("Failed to create instance of the object. Exception was: " + e);
return null;
}
}
+
+ public static void register(T instance) {
+ SINGLETON_FACTORY.mapHolder.put(instance.getClass(), instance);
+ }
+
+ public static void register(Class> classKey, T instance) {
+ SINGLETON_FACTORY.mapHolder.put(classKey, instance);
+ }
}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java
index 136a1d80..873f8fb9 100644
--- a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/Wait.java
@@ -77,4 +77,28 @@ public static boolean retry(Runnable action, Duration timeout, Duration sleepInt
public static void retry(Runnable action, Duration timeout, Duration sleepInterval, Class extends Throwable> ... exceptionsToIgnore) {
Wait.retry(action, timeout, sleepInterval, true, exceptionsToIgnore);
}
+
+ public static boolean forConditionUntilTimeout(Comparator condition, long timeoutInMilliseconds, long pollingIntervalInMilliseconds) {
+ boolean isConditionMet = false;
+
+ long startTime = System.currentTimeMillis();
+
+ while (!isConditionMet && (System.currentTimeMillis() - startTime) <= (timeoutInMilliseconds)) {
+ try {
+ if (condition.evaluate()) {
+ isConditionMet = true;
+ } else {
+ Thread.sleep(pollingIntervalInMilliseconds);
+ }
+ } catch (Exception ignored) {
+ }
+ }
+
+ return isConditionMet;
+ }
+
+ @FunctionalInterface
+ public interface Comparator {
+ boolean evaluate();
+ }
}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/GenericDateTimeParser.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/GenericDateTimeParser.java
new file mode 100644
index 00000000..532731f7
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/GenericDateTimeParser.java
@@ -0,0 +1,127 @@
+package solutions.bellatrix.core.utilities.parsing;
+
+import lombok.experimental.UtilityClass;
+import solutions.bellatrix.core.utilities.Ref;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.List;
+
+@UtilityClass
+public class GenericDateTimeParser {
+ public static List FORMATTERS = new ArrayList<>();
+ public static List PRIORITIZED_FORMATTERS = new ArrayList<>();
+
+ public static void addPattern(String pattern) {
+ FORMATTERS.add(DateTimeFormatter.ofPattern(pattern));
+ }
+
+ public static void prioritizePattern(String pattern) {
+ PRIORITIZED_FORMATTERS.add(DateTimeFormatter.ofPattern(pattern));
+ }
+
+ static {
+ FORMATTERS.add(DateTimeFormatter.ISO_DATE_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_DATE);
+ FORMATTERS.add(DateTimeFormatter.BASIC_ISO_DATE);
+ FORMATTERS.add(DateTimeFormatter.ISO_LOCAL_DATE);
+ FORMATTERS.add(DateTimeFormatter.ISO_LOCAL_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_OFFSET_DATE);
+ FORMATTERS.add(DateTimeFormatter.ISO_OFFSET_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_OFFSET_DATE_TIME);
+ FORMATTERS.add(DateTimeFormatter.ISO_ZONED_DATE_TIME);
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSS'Z'"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("dd MMM yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("dd MMMM yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("E, MMM dd yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("EEE, MMM dd, yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy.MM.dd G 'at' HH:mm:ss z"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy.MM.dd GGGGG 'at' HH:mm:ss z"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy.MM.dd GGGGG 'at' HH:mm:ss z"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZ"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSSSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd'T'HHmmss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss,SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd HHmmss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd HHmmssSSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd HH:mm"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd HHmm"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd HH"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMdd"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy/MM/dd'T'HH:mm:ss.SSSXXX"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSXXX"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssXXX"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS"));
+ FORMATTERS.add(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss"));
+ }
+
+ public static LocalDateTime parse(String data) {
+ if (!PRIORITIZED_FORMATTERS.isEmpty()) {
+ for (var formatter : PRIORITIZED_FORMATTERS) {
+ try {
+ return LocalDateTime.parse(data, formatter);
+ } catch (DateTimeParseException ignored){
+ }
+ }
+ }
+ for (var formatter : FORMATTERS) {
+ try {
+ return LocalDateTime.parse(data, formatter);
+ } catch (DateTimeParseException ignored){
+ }
+ }
+
+ throw new IllegalArgumentException(String.format("There was no proper format found for input string '%s'." +
+ "Please register your custom format in %s by using the add method!", data, GenericDateTimeParser.class.getName()));
+ }
+
+ public static boolean tryParse(String data, Ref returnObject) {
+ if (!PRIORITIZED_FORMATTERS.isEmpty()) {
+ for (var formatter : PRIORITIZED_FORMATTERS) {
+ try {
+ returnObject.value = LocalDateTime.parse(data, formatter);
+ return true;
+ } catch (DateTimeParseException ignored){
+ }
+ }
+ }
+ for (var formatter : FORMATTERS) {
+ try {
+ returnObject.value = LocalDateTime.parse(data, formatter);
+ return true;
+ } catch (DateTimeParseException ignored){
+ }
+ }
+ returnObject.value = null;
+ return false;
+ }
+}
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/ParsingInstructions.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/ParsingInstructions.java
new file mode 100644
index 00000000..7da08d7a
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/ParsingInstructions.java
@@ -0,0 +1,31 @@
+package solutions.bellatrix.core.utilities.parsing;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Function;
+
+@SuppressWarnings({"unchecked", "rawtypes"})
+// TODO: RENAME ME
+public class ParsingInstructions extends HashMap, Function> {
+ public ParsingInstructions() {
+ super();
+ }
+
+ public ParsingInstructions(Map, Function> map) {
+ super(map);
+ }
+
+ public ParsingInstructions add(Class toClass, Function parsingInstructions) {
+ // TODO: EventHandler<> for changing existing instructions
+ super.put(toClass, parsingInstructions);
+ return this;
+ }
+
+ public Function get(Class clazz) {
+ return super.get(clazz);
+ }
+
+ public static ParsingInstructions ofEntries(Entry, Function, ?>>... entries) {
+ return new ParsingInstructions(Map.ofEntries(entries));
+ }
+}
\ No newline at end of file
diff --git a/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/TypeParser.java b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/TypeParser.java
new file mode 100644
index 00000000..b3f975f0
--- /dev/null
+++ b/bellatrix.core/src/main/java/solutions/bellatrix/core/utilities/parsing/TypeParser.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2024 Automate The Planet Ltd.
+ * Author: Miriam Kyoseva
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * You may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package solutions.bellatrix.core.utilities.parsing;
+
+import lombok.SneakyThrows;
+import lombok.experimental.UtilityClass;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.time.LocalDateTime;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Utility class that contains instructions how to convert from one type to another.
+ */
+@UtilityClass
+@SuppressWarnings({"unchecked", "rawtypes"})
+public class TypeParser {
+ /**
+ * Checks for enum fields containing the specified value and prioritizes which enum value to return by the field,
+ * instead of the name of the enum value.
+ */
+ @SneakyThrows
+ public static T parseEnumValue(Object value, Class enumClass) {
+ var fields = enumClass.getDeclaredFields();
+
+ for (T enumConstant : enumClass.getEnumConstants()) {
+ for (var field : fields) {
+ if (field.getClass() == value.getClass()) {
+ field.setAccessible(true);
+ Object fieldValue = field.get(enumConstant);
+ if (fieldValue != null && fieldValue.equals(value)) {
+ return enumConstant;
+ }
+ }
+ }
+ }
+ if (value.getClass() == String.class) {
+ return (T) Enum.valueOf((Class)enumClass, (String)value);
+ } else {
+ // TODO: EventHandler<> for failing to parse enum.
+ return null;
+ }
+ }
+
+ /**
+ * Checks if instructions for parsing the value to the given class exist and uses them.
+ * In case instructions aren't provided, you can provide them dynamically in your code through {@link #instruct(Class, Class, Function)}
+ */
+ public static T parse(Object value, Class clazz) {
+ var originalClass = value.getClass();
+
+ if (clazz == null) {
+ throw new IllegalArgumentException("Class cannot be null.");
+ }
+
+ Function