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

apoc.load.Mapping Maven / Gradle / Ivy

There is a newer version: 5.25.1
Show newest version
package apoc.load;

import apoc.load.util.LoadCsvConfig;
import apoc.meta.Types;
import apoc.util.Util;
import org.apache.commons.lang3.StringUtils;
import org.neo4j.values.storable.DateTimeValue;
import org.neo4j.values.storable.DateValue;
import org.neo4j.values.storable.DurationValue;
import org.neo4j.values.storable.LocalDateTimeValue;
import org.neo4j.values.storable.LocalTimeValue;
import org.neo4j.values.storable.TimeValue;

import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import static apoc.ApocConfig.apocConfig;
import static apoc.util.Util.parseCharFromConfig;
import static java.util.Collections.emptyList;
import static org.neo4j.configuration.GraphDatabaseSettings.db_temporal_timezone;

public class Mapping {
    public static final Mapping EMPTY = new Mapping("", Collections.emptyMap(), LoadCsvConfig.DEFAULT_ARRAY_SEP, false);
    final String name;
    final Collection nullValues;
    final Types type;
    final boolean array;
    final boolean ignore;
    final char arraySep;
    private final Pattern arrayPattern;
    private final Map optionalData;

    public Mapping(String name, Map mapping, char arraySep, boolean ignore) {
        this.name = mapping.getOrDefault("name", name).toString();
        this.array = (Boolean) mapping.getOrDefault("array", false);
        this.optionalData = (Map) mapping.get("optionalData");
        
        this.ignore = (Boolean) mapping.getOrDefault("ignore", ignore);
        this.nullValues = (Collection) mapping.getOrDefault("nullValues", emptyList());
        this.arraySep = parseCharFromConfig(mapping, "arraySep", arraySep);
        this.type = Types.from(mapping.getOrDefault("type", "STRING").toString());
        this.arrayPattern = Pattern.compile(String.valueOf(this.arraySep), Pattern.LITERAL);

        if (this.type == null) {
            // Call this out to the user explicitly because deep inside of LoadCSV and others you will get
            // NPEs that are hard to spot if this is allowed to go through.
            throw new RuntimeException("In specified mapping, there is no type by the name " +
                    mapping.getOrDefault("type", "STRING").toString());
        }
    }

    public Object convert(String value) {
        return array ? convertArray(value) : convertType(value);
    }

    private Object convertArray(String value) {
        String[] values = arrayPattern.split(value);
        List result = new ArrayList<>(values.length);
        for (String v : values) {
            result.add(convertType(v));
        }
        return result;
    }

    private Object convertType(String value) {
        if (nullValues.contains(value) || (StringUtils.isBlank(value) && !type.equals(Types.STRING))) {
            return null;
        }
        if (type == Types.STRING) return value;

        final Supplier timezone = () -> ZoneId.of((String) optionalData.getOrDefault("timezone", apocConfig().getString(db_temporal_timezone.name())));
        switch (type) {
            case POINT:
                return Util.toPoint(Util.fromJson(value, Map.class), optionalData);
            case LOCAL_DATE_TIME:
                // asObjectCopy() returns LocalDateTime, 
                // because in case of array entity.setProperty() fails with LocalDateTimeValue[]
                return LocalDateTimeValue.parse(value).asObjectCopy();
            case LOCAL_TIME:
                return LocalTimeValue.parse(value).asObjectCopy();
            case DATE_TIME:
                return DateTimeValue.parse(value, timezone).asObjectCopy();
            case TIME:
                return TimeValue.parse(value, timezone).asObjectCopy();
            case DATE:
                return DateValue.parse(value).asObjectCopy();
            case DURATION:
                return DurationValue.parse(value);
            case INTEGER: return Util.toLong(value);
            case FLOAT: return Util.toDouble(value);
            case BOOLEAN: return Util.toBoolean(value);
            case NULL: return null;
            case LIST: return Arrays.stream(arrayPattern.split(value)).map(this::convertType).collect(Collectors.toList());
            default: return value;
        }
    }
}