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

net.intelie.pipes.MapPropertySource Maven / Gradle / Ivy

There is a newer version: 0.25.5
Show newest version
package net.intelie.pipes;

import net.intelie.pipes.filters.ObjectSink;
import net.intelie.pipes.types.*;
import net.intelie.pipes.util.GetUtils;
import net.intelie.pipes.util.Iterables;
import net.intelie.pipes.util.Levenshtein;

import java.util.*;

public class MapPropertySource implements PropertySource {
    public static final String TIMESTAMP = "timestamp";
    private static final long serialVersionUID = 1L;
    private final List defaultProperties;
    private final String timestampKey;
    private final Map types;
    private final boolean strict;
    private final MapAdapter adapter;
    private final Metadata metadata;
    private final Type defaultType;

    public MapPropertySource() {
        this(TIMESTAMP, Collections.emptyList(), Collections.emptyMap(), false, null, Metadata.RAW);
    }

    private MapPropertySource(String timestampKey, List defaultProperties, Map types, boolean strict, MapAdapter adapter, Metadata metadata) {
        this.timestampKey = timestampKey;
        this.adapter = adapter != null ? adapter : new DefaultMapAdapter();
        this.metadata = metadata;
        this.defaultProperties = defaultProperties;
        this.types = types;
        this.strict = strict;

        Type valueType = MapType.getValueType(metadata.type());
        this.defaultType = valueType != null ? valueType : Type.STRING;
    }

    private List resolve(List defaultProperties) throws PipeException {
        List props = new ArrayList<>(defaultProperties.size());
        for (String name : defaultProperties) {
            props.add(new PropertyGroup(property(name)));
        }
        return props;
    }

    public MapPropertySource asStrict() {
        return asStrict(true);
    }

    public MapPropertySource asStrict(boolean strict) {
        return new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata);
    }

    public MapPropertySource safe() {
        return safe(true);
    }

    public MapPropertySource safe(boolean safe) {
        return new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata.withSafe(safe));
    }


    public MapPropertySource withTypes(Map types) {
        return new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata);
    }

    public MapPropertySource withDefaultProperties(String... filterProperties) throws PipeException {
        return withDefaultProperties(Arrays.asList(filterProperties));
    }

    public MapPropertySource withDefaultProperties(List filterProperties) throws PipeException {
        return new MapPropertySource(timestampKey, resolve(filterProperties), types, strict, adapter, metadata);
    }

    public MapPropertySource withTimestamp(String timestampKey) {
        return new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata);
    }

    public MapPropertySource withAdapter(MapAdapter adapter) {
        return new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata);
    }

    @Override
    public Property timestamp() throws PipeException {
        return makeProp(timestampKey, Type.NUMBER);
    }

    @Override
    public Property property() throws PipeException {
        return new MapProperty(metadata.type(), null);
    }

    @Override
    public PropertySource newSource(Metadata metadata) {
        return metadata.hasRowFields() ?
                new PipePropertySource(metadata) :
                new MapPropertySource(timestampKey, defaultProperties, types, strict, adapter, metadata);
    }

    @Override
    @SuppressWarnings("unchecked")
    public Property property(String name) throws PipeException {
        Type type = types.get(name);
        if (type == null && (strict || !acceptsProperties()))
            throw Levenshtein.makeExc(name, types.keySet(), "Cannot resolve property '%s'.%s");
        return makeProp(name, type);
    }

    private boolean acceptsProperties() {
        return Type.OBJECT.equals(metadata.type()) || metadata.type().isAssignableTo(Type.MAP);
    }

    private Property makeProp(String name, Type type) throws PipeException {
        return new MapProperty(type, name);
    }

    @Override
    public List defaultProperties() {
        return defaultProperties;
    }

    @Override
    public PropertyReplacer makeReplacer(ClauseInfo fields) throws PipeException {
        PipeException.check(acceptsProperties(), "Cannot set fields in an expression of type '%s'", metadata.type());

        RowFields rowFields = new RowFields(fields);
        return new MyReplacer(rowFields);
    }


    @Override
    public Metadata metadata() {
        return metadata;
    }

    public class MapProperty implements Property {
        private static final long serialVersionUID = 3847552900381849629L;

        private final Type originalType;
        private final Type hint;
        private final Type type;
        private final String identifier;
        private final boolean single;
        private final Scalar[] nth;
        private final boolean reallySingle;
        private final boolean requiresObjectFix;


        public MapProperty(Type type, String identifier) throws PipeException {
            this(type, null, identifier, true);
        }

        public MapProperty(Type originalType, Type hint, String identifier, boolean single, Scalar... nth) throws PipeException {
            this.originalType = originalType;
            this.hint = hint;
            this.single = single;
            this.reallySingle = single && nth.length == 0;

            Type type = makeType(originalType, hint, identifier, reallySingle, nth);
            this.type = type != null ? type : defaultType;
            this.requiresObjectFix = type == null;
            this.identifier = identifier;
            this.nth = nth;
        }

        @Override
        public boolean requiresObjectFix() {
            return requiresObjectFix;
        }

        private Type makeType(Type originalType, Type hint, String identifier, boolean reallySingle, Scalar[] nth) throws PipeException {
            if (hint != null)
                return hint;
            if (originalType == null)
                originalType = adapter.type(identifier, reallySingle);

            Type answer = GetUtils.inferType(originalType, nth);
            if (answer != null)
                return answer;
            else
                return null;
        }

        @Override
        public Object eval(Scope parent, Object obj) {
            if (identifier == null)
                return type.cast(resolveIndexes(parent, obj, obj));
            return type.cast(resolveValue(parent, obj));
        }

        @Override
        public boolean sameProperty(Property other) {
            if (!(other instanceof MapProperty))
                return false;
            MapProperty that = (MapProperty) other;
            return Objects.equals(this.identifier, that.identifier) &&
                    Objects.equals(this.type, that.type) &&
                    sameScalars(this.nth, that.nth);
        }

        private boolean sameScalars(Scalar[] thisNth, Scalar[] thatNth) {
            if (thisNth.length != thatNth.length)
                return false;
            for (int i = 0; i < thisNth.length; i++) {
                if (!Level.CONSTANT.accepts(thatNth[i]) ||
                        !Level.CONSTANT.accepts(thisNth[i]) ||
                        !Objects.equals(Level.asScalar(thisNth[i]).eval(null, null), Level.asScalar(thatNth[i]).eval(null, null)))
                    return false;
            }
            return true;
        }

        private Object resolveValue(Scope parent, Object obj) {
            Object value = identifier == null ? obj : adapter.get(obj, identifier, reallySingle);

            return resolveIndexes(parent, obj, value);
        }

        private Object resolveIndexes(Scope parent, Object obj, Object value) {
            if (nth.length == 0)
                return value;

            for (Scalar scalar : nth) {
                value = GetUtils.get(value, scalar.eval(parent, obj));
            }
            return value;
        }

        @Override
        public Type type() {
            return type;
        }

        @Override
        public String name() {
            return identifier == null ? "_" : identifier;
        }

        @Override
        public Iterable args() {
            return Arrays.asList(nth);
        }

        @Override
        public Property indexed(Scalar... args) throws PipeException {
            return new MapProperty(originalType, hint, identifier, false, args);
        }

        @Override
        public Property hint(Type type) throws PipeException {
            return new MapProperty(originalType, type, identifier, single, nth);
        }

        @Override
        public void evalRaw(Scope parent, Object obj, ObjectSink sink) {
            if (identifier == null) {
                sink.onSingle(obj);
                return;
            }

            Object o = adapter.get(obj, identifier, false);
            if (o instanceof Iterable) {
                for (Object child : ((Iterable) o))
                    sink.onSingle(child);
            } else {
                sink.onSingle(o);
            }
        }

        @Override
        public PropertyVisitor visit(Scope parent, PropertyVisitor visitor) {
            for (Scalar scalar : nth)
                scalar.visit(parent, visitor);
            return identifier == null ? visitor.anyProperty() : visitor.property(this);
        }

        @Override
        public String toString() {
            return "map:" + name() + "[" + Iterables.join(", ", Arrays.asList(nth)) + "]@" + type;
        }
    }

    private class MyReplacer implements PropertyReplacer {
        private final RowFields rowFields;
        private final Type type;

        public MyReplacer(RowFields rowFields) {
            this.rowFields = rowFields;
            this.type = Type.min(metadata.type(), new MapType(Type.STRING, Type.extract(new RowType(rowFields), SeqType.class).type()));
        }

        @Override
        public Type type() {
            return type;
        }

        @Override
        public Object replaceValues(Object obj, Row row) {
            return adapter.replaceValues(obj, rowFields, row);
        }
    }
}