mirror of
https://github.com/Kingsrook/qqq.git
synced 2025-07-20 06:00:44 +00:00
more entity->QRecord abilities; add TIME & BOOLEAN types
This commit is contained in:
@ -47,7 +47,10 @@ import com.kingsrook.qqq.backend.core.processes.implementations.bulk.edit.BulkEd
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertReceiveFileStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.bulk.insert.BulkInsertStoreRecordsStep;
|
||||
import com.kingsrook.qqq.backend.core.processes.implementations.general.LoadInitialRecordsStep;
|
||||
import com.kingsrook.qqq.backend.core.utils.CollectionUtils;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -57,6 +60,10 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
*******************************************************************************/
|
||||
public class QInstanceEnricher
|
||||
{
|
||||
private static final Logger LOG = LogManager.getLogger(QInstanceEnricher.class);
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -182,6 +189,16 @@ public class QInstanceEnricher
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(name.length() == 0)
|
||||
{
|
||||
return ("");
|
||||
}
|
||||
|
||||
if(name.length() == 1)
|
||||
{
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT));
|
||||
}
|
||||
|
||||
return (name.substring(0, 1).toUpperCase(Locale.ROOT) + name.substring(1).replaceAll("([A-Z])", " $1"));
|
||||
}
|
||||
|
||||
@ -403,4 +420,96 @@ public class QInstanceEnricher
|
||||
)));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** for all fields in a table, set their backendName, using the default "inference" logic
|
||||
** see {@link #inferBackendName(String)}
|
||||
*******************************************************************************/
|
||||
public static void setInferredFieldBackendNames(QTableMetaData tableMetaData)
|
||||
{
|
||||
if(tableMetaData == null)
|
||||
{
|
||||
LOG.warn("Requested to infer field backend names with a null table as input. Returning with noop.");
|
||||
return;
|
||||
}
|
||||
|
||||
if(CollectionUtils.nullSafeIsEmpty(tableMetaData.getFields()))
|
||||
{
|
||||
LOG.warn("Requested to infer field backend names on a table [" + tableMetaData.getName() + "] with no fields. Returning with noop.");
|
||||
return;
|
||||
}
|
||||
|
||||
for(QFieldMetaData field : tableMetaData.getFields().values())
|
||||
{
|
||||
String fieldName = field.getName();
|
||||
String fieldBackendName = field.getBackendName();
|
||||
if(!StringUtils.hasContent(fieldBackendName))
|
||||
{
|
||||
String backendName = inferBackendName(fieldName);
|
||||
field.setBackendName(backendName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Do a default mapping from a camelCase field name to an underscore_style
|
||||
** name for a backend.
|
||||
*******************************************************************************/
|
||||
static String inferBackendName(String fieldName)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
// build a list of words in the name, then join them with _ and lower-case the result //
|
||||
////////////////////////////////////////////////////////////////////////////////////////
|
||||
List<String> words = new ArrayList<>();
|
||||
StringBuilder currentWord = new StringBuilder();
|
||||
for(int i = 0; i < fieldName.length(); i++)
|
||||
{
|
||||
Character thisChar = fieldName.charAt(i);
|
||||
Character nextChar = i < (fieldName.length() - 1) ? fieldName.charAt(i + 1) : null;
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// if we're at the end of the whole string, then we're at the end of the last word //
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
if(nextChar == null)
|
||||
{
|
||||
currentWord.append(thisChar);
|
||||
words.add(currentWord.toString());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////
|
||||
// transitioning from a lower to an upper starts a word. //
|
||||
///////////////////////////////////////////////////////////
|
||||
else if(Character.isLowerCase(thisChar) && Character.isUpperCase(nextChar))
|
||||
{
|
||||
currentWord.append(thisChar);
|
||||
words.add(currentWord.toString());
|
||||
currentWord = new StringBuilder();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// transitioning from an upper to a lower - it starts a word, as long as there were already letters in the current word //
|
||||
// e.g., on wordThenTLAInMiddle, when thisChar=I and nextChar=n. currentWord will be "TLA". So finish that word, and start a new one with the 'I' //
|
||||
// but the normal single-upper condition, e.g., firstName, when thisChar=N and nextChar=a, current word will be empty string, so just append the 'a' to it //
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
else if(Character.isUpperCase(thisChar) && Character.isLowerCase(nextChar) && currentWord.length() > 0)
|
||||
{
|
||||
words.add(currentWord.toString());
|
||||
currentWord = new StringBuilder();
|
||||
currentWord.append(thisChar);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////
|
||||
// by default, just add this character to the current word //
|
||||
/////////////////////////////////////////////////////////////
|
||||
else
|
||||
{
|
||||
currentWord.append(thisChar);
|
||||
}
|
||||
}
|
||||
|
||||
return (String.join("_", words).toLowerCase(Locale.ROOT));
|
||||
}
|
||||
}
|
||||
|
@ -32,12 +32,33 @@ import java.util.List;
|
||||
*******************************************************************************/
|
||||
public class QFilterCriteria implements Serializable
|
||||
{
|
||||
private String fieldName;
|
||||
private QCriteriaOperator operator;
|
||||
private String fieldName;
|
||||
private QCriteriaOperator operator;
|
||||
private List<Serializable> values;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QFilterCriteria(String fieldName, QCriteriaOperator operator, List<Serializable> values)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.operator = operator;
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldName
|
||||
**
|
||||
@ -127,6 +148,7 @@ public class QFilterCriteria implements Serializable
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Setter for values
|
||||
**
|
||||
|
@ -0,0 +1,54 @@
|
||||
/*
|
||||
* QQQ - Low-code Application Framework for Engineers.
|
||||
* Copyright (C) 2021-2022. Kingsrook, LLC
|
||||
* 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States
|
||||
* contact@kingsrook.com
|
||||
* https://github.com/Kingsrook/
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldType;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@Target(ElementType.FIELD)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface QField
|
||||
{
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
String backendName() default "";
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
boolean isRequired() default false;
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
boolean isEditable() default true;
|
||||
}
|
@ -23,8 +23,12 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
@ -104,7 +108,7 @@ public abstract class QRecordEntity
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static List<QRecordEntityField> getFieldList(Class<? extends QRecordEntity> c)
|
||||
public static List<QRecordEntityField> getFieldList(Class<? extends QRecordEntity> c)
|
||||
{
|
||||
if(!fieldMapping.containsKey(c))
|
||||
{
|
||||
@ -114,10 +118,12 @@ public abstract class QRecordEntity
|
||||
if(isGetter(possibleGetter))
|
||||
{
|
||||
Optional<Method> setter = getSetterForGetter(c, possibleGetter);
|
||||
|
||||
if(setter.isPresent())
|
||||
{
|
||||
String name = getFieldNameFromGetter(possibleGetter);
|
||||
fieldList.add(new QRecordEntityField(name, possibleGetter, setter.get(), possibleGetter.getReturnType()));
|
||||
String fieldName = getFieldNameFromGetter(possibleGetter);
|
||||
Optional<QField> fieldAnnotation = getQFieldAnnotation(c, fieldName);
|
||||
fieldList.add(new QRecordEntityField(fieldName, possibleGetter, setter.get(), possibleGetter.getReturnType(), fieldAnnotation.orElse(null)));
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -132,6 +138,27 @@ public abstract class QRecordEntity
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public static Optional<QField> getQFieldAnnotation(Class<? extends QRecordEntity> c, String fieldName)
|
||||
{
|
||||
try
|
||||
{
|
||||
Field field = c.getDeclaredField(fieldName);
|
||||
return (Optional.ofNullable(field.getAnnotation(QField.class)));
|
||||
}
|
||||
catch(NoSuchFieldException e)
|
||||
{
|
||||
//////////////////////////////////////////
|
||||
// ok, we just won't have an annotation //
|
||||
//////////////////////////////////////////
|
||||
}
|
||||
return (Optional.empty());
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -207,7 +234,15 @@ public abstract class QRecordEntity
|
||||
|| returnType.equals(int.class)
|
||||
|| returnType.equals(Boolean.class)
|
||||
|| returnType.equals(boolean.class)
|
||||
|| returnType.equals(BigDecimal.class));
|
||||
|| returnType.equals(BigDecimal.class)
|
||||
|| returnType.equals(Instant.class)
|
||||
|| returnType.equals(LocalDate.class)
|
||||
|| returnType.equals(LocalTime.class));
|
||||
/////////////////////////////////////////////
|
||||
// note - this list has implications upon: //
|
||||
// - QFieldType.fromClass //
|
||||
// - QRecordEntityField.convertValueType //
|
||||
/////////////////////////////////////////////
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -25,6 +25,8 @@ package com.kingsrook.qqq.backend.core.model.data;
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
import com.kingsrook.qqq.backend.core.utils.ValueUtils;
|
||||
|
||||
@ -38,18 +40,20 @@ public class QRecordEntityField
|
||||
private final Method getter;
|
||||
private final Method setter;
|
||||
private final Class<?> type;
|
||||
private final QField fieldAnnotation;
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor.
|
||||
*******************************************************************************/
|
||||
public QRecordEntityField(String fieldName, Method getter, Method setter, Class<?> type)
|
||||
public QRecordEntityField(String fieldName, Method getter, Method setter, Class<?> type, QField fieldAnnotation)
|
||||
{
|
||||
this.fieldName = fieldName;
|
||||
this.getter = getter;
|
||||
this.setter = setter;
|
||||
this.type = type;
|
||||
this.fieldAnnotation = fieldAnnotation;
|
||||
}
|
||||
|
||||
|
||||
@ -98,39 +102,67 @@ public class QRecordEntityField
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Getter for fieldAnnotation
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QField getFieldAnnotation()
|
||||
{
|
||||
return fieldAnnotation;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public Object convertValueType(Serializable value)
|
||||
{
|
||||
if(value == null)
|
||||
try
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
if(value.getClass().equals(type))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
if(value.getClass().equals(type))
|
||||
{
|
||||
return (value);
|
||||
}
|
||||
|
||||
if(type.equals(String.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsString(value));
|
||||
}
|
||||
if(type.equals(String.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsString(value));
|
||||
}
|
||||
|
||||
if(type.equals(Integer.class) || type.equals(int.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsInteger(value));
|
||||
}
|
||||
if(type.equals(Integer.class) || type.equals(int.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsInteger(value));
|
||||
}
|
||||
|
||||
if(type.equals(Boolean.class) || type.equals(boolean.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsBoolean(value));
|
||||
}
|
||||
if(type.equals(Boolean.class) || type.equals(boolean.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsBoolean(value));
|
||||
}
|
||||
|
||||
if(type.equals(BigDecimal.class))
|
||||
if(type.equals(BigDecimal.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsBigDecimal(value));
|
||||
}
|
||||
|
||||
if(type.equals(LocalDate.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsLocalDate(value));
|
||||
}
|
||||
|
||||
if(type.equals(Instant.class))
|
||||
{
|
||||
return (ValueUtils.getValueAsInstant(value));
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
return (ValueUtils.getValueAsBigDecimal(value));
|
||||
throw (new QValueException("Exception converting value [" + value + "] for field [" + fieldName + "]", e));
|
||||
}
|
||||
|
||||
throw (new QValueException("Unhandled value type [" + type + "] for field [" + fieldName + "]"));
|
||||
|
@ -24,9 +24,13 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Optional;
|
||||
import com.github.hervian.reflection.Fun;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QField;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecord;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.utils.StringUtils;
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
@ -77,12 +81,49 @@ public class QFieldMetaData
|
||||
** e.g., new QFieldMetaData(Order::getOrderNo).
|
||||
*******************************************************************************/
|
||||
public <T> QFieldMetaData(Fun.With1ParamAndVoid<T> getterRef) throws QException
|
||||
{
|
||||
Method getter = Fun.toMethod(getterRef);
|
||||
constructFromGetter(getter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Initialize a fieldMetaData from a getter method from an entity
|
||||
**
|
||||
*******************************************************************************/
|
||||
public <T> QFieldMetaData(Method getter) throws QException
|
||||
{
|
||||
constructFromGetter(getter);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** From a getter method, populate attributes in this field meta-data, including
|
||||
** those from the @QField annotation on the field in the class, if present.
|
||||
*******************************************************************************/
|
||||
private void constructFromGetter(Method getter) throws QException
|
||||
{
|
||||
try
|
||||
{
|
||||
Method getter = Fun.toMethod(getterRef);
|
||||
this.name = QRecordEntity.getFieldNameFromGetter(getter);
|
||||
this.type = QFieldType.fromClass(getter.getReturnType());
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Optional<QField> optionalFieldAnnotation = QRecordEntity.getQFieldAnnotation((Class<? extends QRecordEntity>) getter.getDeclaringClass(), this.name);
|
||||
|
||||
if(optionalFieldAnnotation.isPresent())
|
||||
{
|
||||
QField fieldAnnotation = optionalFieldAnnotation.get();
|
||||
setIsRequired(fieldAnnotation.isRequired());
|
||||
setIsEditable(fieldAnnotation.isEditable());
|
||||
|
||||
if(StringUtils.hasContent(fieldAnnotation.backendName()))
|
||||
{
|
||||
setBackendName(fieldAnnotation.backendName());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(QException qe)
|
||||
{
|
||||
@ -90,7 +131,7 @@ public class QFieldMetaData
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QException("Error constructing field from getterRef: " + getterRef, e));
|
||||
throw (new QException("Error constructing field from getter method: " + getter.getName(), e));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,9 @@ package com.kingsrook.qqq.backend.core.model.metadata.fields;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
|
||||
|
||||
@ -35,8 +38,9 @@ public enum QFieldType
|
||||
STRING,
|
||||
INTEGER,
|
||||
DECIMAL,
|
||||
BOOLEAN,
|
||||
DATE,
|
||||
// TIME,
|
||||
TIME,
|
||||
DATE_TIME,
|
||||
TEXT,
|
||||
HTML,
|
||||
@ -65,6 +69,22 @@ public enum QFieldType
|
||||
{
|
||||
return (DECIMAL);
|
||||
}
|
||||
if(c.equals(Instant.class))
|
||||
{
|
||||
return (DATE_TIME);
|
||||
}
|
||||
if(c.equals(LocalDate.class))
|
||||
{
|
||||
return (DATE);
|
||||
}
|
||||
if(c.equals(LocalTime.class))
|
||||
{
|
||||
return (TIME);
|
||||
}
|
||||
if(c.equals(Boolean.class))
|
||||
{
|
||||
return (BOOLEAN);
|
||||
}
|
||||
|
||||
throw (new QException("Unrecognized class [" + c + "]"));
|
||||
}
|
||||
|
@ -28,6 +28,10 @@ import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QException;
|
||||
import com.kingsrook.qqq.backend.core.instances.QInstanceEnricher;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntity;
|
||||
import com.kingsrook.qqq.backend.core.model.data.QRecordEntityField;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.code.QCodeReference;
|
||||
import com.kingsrook.qqq.backend.core.model.metadata.fields.QFieldMetaData;
|
||||
|
||||
@ -91,6 +95,22 @@ public class QTableMetaData implements Serializable
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withFieldsFromEntity(Class<? extends QRecordEntity> entityClass) throws QException
|
||||
{
|
||||
List<QRecordEntityField> recordEntityFieldList = QRecordEntity.getFieldList(entityClass);
|
||||
for(QRecordEntityField recordEntityField : recordEntityFieldList)
|
||||
{
|
||||
QFieldMetaData field = new QFieldMetaData(recordEntityField.getGetter());
|
||||
addField(field);
|
||||
}
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
@ -434,4 +454,15 @@ public class QTableMetaData implements Serializable
|
||||
return (this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
public QTableMetaData withInferredFieldBackendNames()
|
||||
{
|
||||
QInstanceEnricher.setInferredFieldBackendNames(this);
|
||||
return (this);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -41,7 +41,7 @@ public class ListingHash<K, V> implements Map<K, List<V>>, Serializable
|
||||
{
|
||||
public static final long serialVersionUID = 0L;
|
||||
|
||||
private HashMap<K, List<V>> hashMap = null;
|
||||
private Map<K, List<V>> hashMap = null;
|
||||
|
||||
|
||||
|
||||
@ -51,7 +51,19 @@ public class ListingHash<K, V> implements Map<K, List<V>>, Serializable
|
||||
*******************************************************************************/
|
||||
public ListingHash()
|
||||
{
|
||||
this.hashMap = new HashMap<K, List<V>>();
|
||||
this.hashMap = new HashMap<>();
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Constructor where you can supply a source map (e.g., if you want a specific
|
||||
** Map type (like LinkedHashMap), or with pre-values
|
||||
**
|
||||
*******************************************************************************/
|
||||
public ListingHash(Map<K, List<V>> sourceMap)
|
||||
{
|
||||
this.hashMap = sourceMap;
|
||||
}
|
||||
|
||||
|
||||
|
@ -23,11 +23,14 @@ package com.kingsrook.qqq.backend.core.utils;
|
||||
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.format.DateTimeParseException;
|
||||
import java.util.Calendar;
|
||||
import java.util.List;
|
||||
import java.util.TimeZone;
|
||||
import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
|
||||
@ -37,7 +40,8 @@ import com.kingsrook.qqq.backend.core.exceptions.QValueException;
|
||||
*******************************************************************************/
|
||||
public class ValueUtils
|
||||
{
|
||||
private static final DateTimeFormatter localDateDefaultFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter yyyyMMddWithDashesFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter MdyyyyWithSlashesFormatter = DateTimeFormatter.ofPattern("M/d/yyyy");
|
||||
|
||||
|
||||
|
||||
@ -174,9 +178,13 @@ public class ValueUtils
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to Integer."));
|
||||
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Integer."));
|
||||
}
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
throw (qve);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to an Integer.", e));
|
||||
@ -212,8 +220,8 @@ public class ValueUtils
|
||||
}
|
||||
else if(value instanceof Calendar c)
|
||||
{
|
||||
TimeZone tz = c.getTimeZone();
|
||||
ZoneId zid = (tz == null) ? ZoneId.systemDefault() : tz.toZoneId();
|
||||
TimeZone tz = c.getTimeZone();
|
||||
ZoneId zid = (tz == null) ? ZoneId.systemDefault() : tz.toZoneId();
|
||||
return LocalDateTime.ofInstant(c.toInstant(), zid).toLocalDate();
|
||||
}
|
||||
else if(value instanceof LocalDateTime ldt)
|
||||
@ -227,21 +235,47 @@ public class ValueUtils
|
||||
return (null);
|
||||
}
|
||||
|
||||
return LocalDate.parse(s, localDateDefaultFormatter);
|
||||
return tryLocalDateParsers(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to LocalDate."));
|
||||
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to LocalDate."));
|
||||
}
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
throw (qve);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to an LocalDate.", e));
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to a LocalDate.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
**
|
||||
*******************************************************************************/
|
||||
private static LocalDate tryLocalDateParsers(String s)
|
||||
{
|
||||
DateTimeParseException lastException = null;
|
||||
for(DateTimeFormatter dateTimeFormatter : List.of(yyyyMMddWithDashesFormatter, MdyyyyWithSlashesFormatter))
|
||||
{
|
||||
try
|
||||
{
|
||||
return LocalDate.parse(s, dateTimeFormatter);
|
||||
}
|
||||
catch(DateTimeParseException dtpe)
|
||||
{
|
||||
lastException = dtpe;
|
||||
}
|
||||
}
|
||||
throw (new QValueException("Could not parse value [" + s + "] to a local date", lastException));
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Type-safely make a BigDecimal from any Object.
|
||||
** null and empty-string inputs return null.
|
||||
@ -305,13 +339,80 @@ public class ValueUtils
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new IllegalArgumentException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal."));
|
||||
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to BigDecimal."));
|
||||
}
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
throw (qve);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to an BigDecimal.", e));
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to a BigDecimal.", e));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/*******************************************************************************
|
||||
** Type-safely make an Instant from any Object.
|
||||
** null and empty-string inputs return null.
|
||||
** We may throw if the input can't be converted to a Instant
|
||||
*******************************************************************************/
|
||||
public static Instant getValueAsInstant(Object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
if(value == null)
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
else if(value instanceof Instant i)
|
||||
{
|
||||
return (i);
|
||||
}
|
||||
else if(value instanceof java.sql.Date d)
|
||||
{
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// note - in the jdk, this method throws UnsupportedOperationException (because of the lack of time in sql Dates) //
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
return d.toInstant();
|
||||
}
|
||||
else if(value instanceof java.util.Date d)
|
||||
{
|
||||
return d.toInstant();
|
||||
}
|
||||
else if(value instanceof Calendar c)
|
||||
{
|
||||
TimeZone tz = c.getTimeZone();
|
||||
return (c.toInstant());
|
||||
}
|
||||
else if(value instanceof LocalDateTime ldt)
|
||||
{
|
||||
ZoneId zoneId = ZoneId.systemDefault();
|
||||
return ldt.toInstant(zoneId.getRules().getOffset(ldt));
|
||||
}
|
||||
else if(value instanceof String s)
|
||||
{
|
||||
if(!StringUtils.hasContent(s))
|
||||
{
|
||||
return (null);
|
||||
}
|
||||
|
||||
return Instant.parse(s);
|
||||
}
|
||||
else
|
||||
{
|
||||
throw (new QValueException("Unsupported class " + value.getClass().getName() + " for converting to Instant."));
|
||||
}
|
||||
}
|
||||
catch(QValueException qve)
|
||||
{
|
||||
throw (qve);
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
throw (new QValueException("Value [" + value + "] could not be converted to a Instant.", e));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user