Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package org.osgl.util;
/*-
* #%L
* Java Tool
* %%
* Copyright (C) 2014 - 2018 OSGL (Open Source General Library)
* %%
* 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.
* #L%
*/
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.osgl.*;
import org.osgl.exception.*;
import org.osgl.util.converter.TypeConverterRegistry;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
/**
* Map data from one structure into another structure. The target data structure
* must be exists and must be mutable to do the data mapping.
*
* ## Error reporting
*
* `DataMapper` consumer can dictate on how `DataMapper` to handle exceptions by
* passing boolean value `ignoreError` to constructor.
*
* If `ignoreError` is set to `true` then `DataMapper` will not raise error for
* any exception raised during mapping process. It will simply skip to the next
* field.
*
* Otherwise it will raise {@link MappingException} when
*
* * Exceptions raised
* * Type conversion failed
*
* ## Mapping rule
*
* Three mapping rule has been defined:
*
* * {@link MappingRule#STRICT_MATCHING}
* - mapping happens between exact same name, and do type conversion if needed
* * {@link MappingRule#KEYWORD_MATCHING}
* - mapping happens if the {@link Keyword} of source field name
* and target field name matches. For example `foo_bar` can be mapped to `fooBar`.
*
* ## Field name based mapping
*
* `DataMapper` do field name based mapping, in other words, it does not rely
* on JavaBean properties to do the mapping. For example if source object
* has a field name `foo`, the value of `source.foo` will be mapped to
* the target object's field `foo`: `target.foo`.
*
* ## Recursive mapping
*
* `DataMapper` do recursive mapping. If target object has embedded structure
* and source object has the corresponding value, then that value will be
* mapped to target object's embedded structure recursively.
*
* ## Type conversion
*
* When mapping semantic is {@link Semantic#MAP}, the mapper will try to
* convert from source type to target type when types doesn't match. Otherwise
* it will raise an {@link MappingException}.
*
* ## Mapping to array
*
* For target field that is an array, tt will first try to do type conversion
* in case special converter has been defined.
*
* If there is not converter defined or when root mapping target is an array,
* then it will check if source component can be converted to an {@link Iterable}.
* If it's okay then it will add elements from the iterable to the target array.
* In case element exists in the target array then it will do recursive mapping
* from the source element to the target element.
*
* ### Handle out of range
*
* If source array/collection has more elements that the target array can hold,
* an new array will be returned when calling to {@link #getTarget()}.
*
* If array is inner property of another structure, it will be replaced with
* the new array that holds all elements from source array/collection
*
* ## Mapping to a List
*
* Determine the list element type. If this is a recursive mapping, list element
* type can be found from the field type of upper level target. Otherwise, it
* will try to determine list type from the existing elements of the list. If
* there is no element in the list then it assume list can be put in any type
* of object.
*
* For target field that is an list, it will first try to do type conversion
* in case special converter has been defined.
*
* If there is no converter defined, then it will check if source component can be
* converted to an {@link Iterable}. If it's okay then it will add elements from
* the iterable to the target list. In case the element in the position is not
* null in the target list, it will do recursive mapping from the source element
* to the target element.
*
* If source array/collection has more elements than the target list, then
* the extra elements will be added to the list
*
* ## Mapping to a Set
*
* Determine the set element type. If this is a recursive mapping, set element
* type can be found from the field type of upper level target. Otherwise, it
* will try to determine list type from the existing elements of the set. If
* there is no element in the list then it assume set can be put in any type
* of object.
*
* For target field that is a set, it will first try to do type conversion in
* case special converter has been defined.
*
* If there is no converter defined, then it will check if source component can be
* converted to an {@link Iterable}. If it's okay then it will add elements from
* the iterable to the target set. Unlike mapping to an array or a list, there is
* no recursive element mapping happening for set, it just add the elements from
* source iterable into the target set, after type conversion if needed.
*
* ## Mapping to a Map
*
* When mapping to a map, it will check if target map has existing non-null value
* for a certain property, if it exists, then it will do recursive mapping from
* property value to the target map entry value.
*
* If there are extra properties in the source object, they will be added into the
* target map if the type can be converted.
*
* When mapping to a map, the {@link MappingRule#KEYWORD_MATCHING} won't effect, it is
* treated as {@link MappingRule#STRICT_MATCHING}
*
* ## Mapping from a Map
*
* Again it will try to do type conversion to allow custom data mapping for certain types
* happening. In case no type conversion is done then
*
* It treats map entry as fields of a class, literally the entry is treated as the field name
* and entry value is treated as field value. And then start from there to do the
* data mapping process.
*
* ## `null` value
*
* If `null` value encountered in source, the corresponding field/map entry will be set to `null`
* in the target.
*/
public class DataMapper {
private static final class IntermediatePlaceHolder {
private Object rootSource;
IntermediatePlaceHolder(Object rootSource) {
this.rootSource = rootSource;
}
}
public enum MappingRule {
/**
* field name must match exactly.
*/
STRICT_MATCHING,
/**
* field name match by {@link Keyword} equality.
*
* For example, `foo_bar` in source can be mapped to `fooBar` in target
*/
KEYWORD_MATCHING;
public boolean keywordMatching() {
return this == KEYWORD_MATCHING;
}
}
/**
* The mapping semantic steering the mapping logic
*/
public enum Semantic {
/**
* Copy the reference from source properties to target properties.
* * require property type be exactly match between source and target data
* * if there are more properties in the target data, the extra properties will be
* set to `null` or default value if they are primitive types
*/
SHALLOW_COPY,
/**
* Recursively copy data from source data structure into target data structure.
* * intermediate data structure can be different between source and target
* * the terminate data type must match exactly
* * if there are more properties in the target data, the extra properties will be
* set to `null` or default value if they are primitive types
* * if there are existing data in collection of target data structure, the existing data must be cleared
* before copy happening
*/
DEEP_COPY,
/**
* Recursively copy data from source data structure into target Map structure.
* The nested data will be flatmap to top level. E.g
*
* ```
* {
* id: 123
* name: foo
* address:
* streetNo: 1
* streetName: George St
* }
* ```
*
* will be flat into
*
* ```
* {
* id: 123
* name: foo
* address.streetNo: 1
* address.streetName: George St
* }
* ```
*
* Note this semantic only applied when target is a Map and key type is String, otherwise
* an IllegalStateException will be thrown out.
*/
FLAT_COPY,
/**
* Recursively merge data from source data structure into target data structure. This
* semantic is same as {@link #DEEP_COPY} except:
* * if there are more properties in the target data, the extra properties will be left
* untouched
* * if there are existing data in collection of target data structure, the data will be
* merged or untouched
*/
MERGE,
/**
* Recursively map data from source data structure into target data structure. This
* semantic is same as {@link #DEEP_COPY} except:
* * the terminate data type can be different, in which case type conversion will be used.
*/
MAP,
/**
* Recursively map data from source data structure into target data structure. This
* semantic is same as {@link #MERGE} except:
* * the terminate data type can be different, in which case type conversion will be used.
*/
MERGE_MAP
;
boolean isShallowCopy() {
return this == SHALLOW_COPY;
}
boolean isDeepCopy() {
return this == DEEP_COPY;
}
boolean isFlatCopy() {
return this == FLAT_COPY;
}
boolean isCopy() {
return isShallowCopy() || isDeepCopy() || isMapping();
}
boolean isMerge() {
return this == MERGE;
}
boolean isMapping() {
return this == MAP;
}
boolean isMergeMapping() {
return this == MERGE_MAP;
}
boolean isAppend() {
return isMerge() || isMergeMapping();
}
boolean allowTypeConvert() {
return isMapping() || isMergeMapping();
}
}
private static class NameList {
private boolean useKeyword;
private Set stringList = C.Set();
private Set keywordList = C.Set();
NameList(boolean useKeywordMatching) {
useKeyword = useKeywordMatching;
if (useKeywordMatching) {
keywordList = new HashSet<>();
} else {
stringList = new HashSet<>();
}
}
void add(String key) {
if (useKeyword) {
keywordList.add(Keyword.of(key));
} else {
stringList.add(key);
}
}
boolean remove(String key) {
if (useKeyword) {
return keywordList.remove(Keyword.of(key));
} else {
return stringList.remove(key);
}
}
boolean isEmpty() {
return stringList.isEmpty() && keywordList.isEmpty();
}
boolean contains(String key) {
return useKeyword ? keywordList.contains(Keyword.of(key)) : stringList.contains(key);
}
}
class PropertyFilter extends $.Predicate {
/**
* Keep a set of properties that can be copied.
*
* Note if both {@link #whiteList} and `blackList` contains
* elements, then `whiteList` is ignored.
*/
private NameList whiteList = new NameList(rule.keywordMatching());
/**
* Keep a set of properties that shall not be copied.
*/
private NameList blackList = new NameList(rule.keywordMatching());
/**
* Keep a set of properties that by default their sub properties shall
* not be copied
*/
private NameList grayList = new NameList(rule.keywordMatching());
/**
* Keep a set of property contexts that by default their sub properties
* shall be copied
*/
private NameList greenList = new NameList(rule.keywordMatching());
private boolean allEmpty = true;
PropertyFilter(String spec) {
if (S.blank(spec)) {
return;
}
List words = C.newList(S.fastSplit(spec, ","));
// make sure the black list go first
Collections.sort(words, new Comparator() {
@Override
public int compare(String o1, String o2) {
if (o1.startsWith("-")) {
return o2.startsWith("-") ? o1.compareTo(o2) : -1;
} else if (o2.startsWith("-")) {
return 1;
}
return o1.compareTo(o2);
}
});
boolean removeDefaultContextFromGreenList = false;
for (String word : words) {
boolean isBlackList = false;
if (word.startsWith("-")) {
isBlackList = true;
word = word.substring(1);
} else if (word.startsWith("+")) {
word = word.substring(1);
}
word = word.trim();
String context = "";
if (word.contains(".")) {
context = S.cut(word).beforeLast(".");
}
if (isBlackList) {
if (!grayList.contains(word)) {
blackList.add(word);
greenList.add(context);
}
} else {
if (!blackList.contains(word)) {
whiteList.add(word);
if (word.contains(".")) {
blackList.remove(context);
grayList.add(context);
} else {
removeDefaultContextFromGreenList = true;
}
}
}
}
if (removeDefaultContextFromGreenList) {
greenList.remove("");
}
allEmpty = blackList.isEmpty() && whiteList.isEmpty();
}
private boolean isContextIn(String s, NameList list) {
if (s.contains(".")) {
String context = S.beforeLast(s, ".");
return list.contains(context) || isContextIn(context, list);
}
return list.contains("");
}
@Override
public boolean test(String s) {
if (allEmpty) {
return true;
}
E.illegalArgumentIf(S.blank(s));
if (whiteList.contains(s) || grayList.contains(s) || isContextIn(s, whiteList)) {
return true;
}
if (blackList.contains(s)) {
return false;
}
if (isContextIn(s, grayList)) {
return false;
}
if (isContextIn(s, greenList)) {
return true;
}
return whiteList.isEmpty();
}
private void addIntoBlackList(String s) {
blackList.add(s);
allEmpty = false;
}
}
private Map specialMapping = C.Map();
private Map specialMappingsReversed = C.Map();
private Set intermediates = C.Set();
private MappingRule rule;
private Semantic semantic;
/**
* Keep track the object hierarchies
*/
private StringBuilder context = new StringBuilder();
/**
* Used to track types walked through and detect
* circular reference
*/
private Set circularReferenceDetector;
/**
* Decide whether copy a field or not
*/
private PropertyFilter filter;
private Object source;
private Class> sourceType;
private Object target;
private Class> targetType;
private ParameterizedType targetGenericType;
private ParameterizedType targetComponentType;
private Class> targetComponentRawType = Object.class;
private Class> targetKeyType;
/**
* convert hints indexed by type
*/
Map conversionHints;
/**
* used to create new instance of certain type
*/
$.Function instanceFactory;
/**
* Allow inject custom type converters
*/
TypeConverterRegistry typeConverterRegistry;
/**
* If set to `false` then it will raise {@link org.osgl.exception.MappingException}
* whenever an error is encountered during mapping process.
*/
private boolean ignoreError;
/**
* If set to `true` then it skip checking field/map key using global filter
*/
private boolean ignoreGlobalFilter;
/**
* If set then it will stop exploring fields
* when it reaches the `rootClass`
*/
private Class rootClass;
/**
* Cache target fields
*/
private List targetFields;
/**
* Cache result of check if target is an array
*/
private boolean targetIsArray;
/**
* Cache result of check if target is a list
*/
private boolean targetIsCollection;
/**
* Cache result of check if target is a collection
*/
private boolean targetIsList;
/**
* Cache result of check if target is a map
*/
private boolean targetIsMap;
private boolean targetIsSimpleType;
private boolean targetIsPojo;
/**
* Cache result of target length if it is array or collection
*/
private int targetLength = -1;
/**
* Cache result of target as collection if it is a collection
*/
private Collection targetCollection;
/**
* Cache result of target as list if it is a list
*/
private List targetList;
/**
* Cache result of target as map if it is a map
*/
private Map targetMap;
private DataMapper root;
private $.Function