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

com.baidu.hugegraph.backend.serializer.TextBackendEntry Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2017 HugeGraph Authors
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You 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 com.baidu.hugegraph.backend.serializer;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NavigableMap;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListMap;

import com.baidu.hugegraph.backend.BackendException;
import com.baidu.hugegraph.backend.id.Id;
import com.baidu.hugegraph.backend.store.BackendEntry;
import com.baidu.hugegraph.type.HugeType;
import com.baidu.hugegraph.type.define.HugeKeys;
import com.baidu.hugegraph.util.JsonUtil;
import com.baidu.hugegraph.util.StringEncoding;

public class TextBackendEntry implements BackendEntry, Cloneable {

    public static final String VALUE_SPLITOR = "\u0003";

    private final HugeType type;
    private final Id id;
    private Id subId;
    private NavigableMap columns;

    public TextBackendEntry(HugeType type, Id id) {
        this.type = type;
        this.id = id;
        this.subId = null;
        this.resetColumns();
    }

    @Override
    public HugeType type() {
        return this.type;
    }

    @Override
    public Id id() {
        return this.id;
    }

    @Override
    public Id originId() {
        return this.id;
    }

    @Override
    public Id subId() {
        return this.subId;
    }

    @Override
    public long ttl() {
        return 0L;
    }

    public void subId(Id subId) {
        this.subId = subId;
    }

    public Set columnNames() {
        return this.columns.keySet();
    }

    public void column(HugeKeys column, String value) {
        this.columns.put(column.string(), value);
    }

    public void column(String column, String value) {
        this.columns.put(column, value);
    }

    public String column(HugeKeys column) {
        return this.columns.get(column.string());
    }

    public String column(String column) {
        return this.columns.get(column);
    }

    public BackendColumn columns(String column) {
        String value = this.columns.get(column);
        if (value == null) {
            return null;
        }
        return BackendColumn.of(StringEncoding.encode(column),
                                StringEncoding.encode(value));
    }

    public Collection columnsWithPrefix(String prefix) {
        return this.columnsWithPrefix(prefix, true, prefix);
    }

    public Collection columnsWithPrefix(String start,
                                                       boolean inclusiveStart,
                                                       String prefix) {
        List list = new ArrayList<>();
        Map map = this.columns.tailMap(start, inclusiveStart);
        for (Map.Entry e : map.entrySet()) {
            String key = e.getKey();
            String value = e.getValue();
            if (key.startsWith(prefix)) {
                list.add(BackendColumn.of(StringEncoding.encode(key),
                                          StringEncoding.encode(value)));
            }
        }
        return list;
    }

    public Collection columnsWithRange(String start,
                                                      boolean inclusiveStart,
                                                      String end,
                                                      boolean inclusiveEnd) {
        List list = new ArrayList<>();
        Map map = this.columns.subMap(start, inclusiveStart,
                                                      end, inclusiveEnd);
        for (Map.Entry e : map.entrySet()) {
            String key = e.getKey();
            String value = e.getValue();
            list.add(BackendColumn.of(StringEncoding.encode(key),
                                      StringEncoding.encode(value)));
        }
        return list;
    }

    public boolean contains(String column) {
        return this.columns.containsKey(column);
    }

    public boolean contains(String column, String value) {
        String col = this.columns.get(column);
        return col != null && col.equals(value);
    }

    public boolean containsPrefix(String column) {
        Map map = this.columns.tailMap(column, true);
        for (String c : map.keySet()) {
            if (c.startsWith(column)) {
                return true;
            }
        }
        return false;
    }

    public boolean containsValue(String value) {
        return this.columns.values().contains(value);
    }

    public void append(TextBackendEntry entry) {
        for (Entry col : entry.columns.entrySet()) {
            String newValue = col.getValue();
            String oldValue = this.column(col.getKey());

            // TODO: use more general method
            if (col.getKey().startsWith(HugeType.PROPERTY.string())) {
                this.columns.put(col.getKey(), col.getValue());
                continue;
            }

            // TODO: use more general method
            if (!col.getKey().endsWith(HugeKeys.ELEMENT_IDS.string())) {
                continue;
            }

            // TODO: ensure the old value is a list and json format (for index)
            if (oldValue.equals("[]")) {
                this.column(col.getKey(), newValue);
                continue;
            }
            List values = new ArrayList<>();
            @SuppressWarnings("unchecked")
            List oldValues = JsonUtil.fromJson(oldValue, List.class);
            @SuppressWarnings("unchecked")
            List newValues = JsonUtil.fromJson(newValue, List.class);
            values.addAll(oldValues);
            values.addAll(newValues);
            // Update the old value
            this.column(col.getKey(), JsonUtil.toJson(values));
        }
    }

    public void eliminate(TextBackendEntry entry) {
        for (Entry col : entry.columns.entrySet()) {
            String newValue = col.getValue();
            String oldValue = this.column(col.getKey());

            // TODO: use more general method
            if (col.getKey().startsWith(HugeType.PROPERTY.string()) ||
                col.getKey().startsWith(HugeType.EDGE_OUT.string()) ||
                col.getKey().startsWith(HugeType.EDGE_IN.string())) {
                this.columns.remove(col.getKey());
                continue;
            }

            // TODO: use more general method
            if (!col.getKey().endsWith(HugeKeys.ELEMENT_IDS.string())) {
                continue;
            }

            // TODO: ensure the old value is a list and json format (for index)
            List values = new ArrayList<>();
            @SuppressWarnings("unchecked")
            List oldValues = JsonUtil.fromJson(oldValue, List.class);
            @SuppressWarnings("unchecked")
            List newValues = JsonUtil.fromJson(newValue, List.class);
            values.addAll(oldValues);
            values.removeAll(newValues);
            // Update the old value
            this.column(col.getKey(), JsonUtil.toJson(values));
        }
    }

    @Override
    public String toString() {
        return String.format("%s: %s", this.id, this.columns.toString());
    }

    @Override
    public int columnsSize() {
        return this.columns.size();
    }

    @Override
    public Collection columns() {
        List list = new ArrayList<>(this.columns.size());
        for (Entry column : this.columns.entrySet()) {
            BackendColumn bytesColumn = new BackendColumn();
            bytesColumn.name = StringEncoding.encode(column.getKey());
            bytesColumn.value = StringEncoding.encode(column.getValue());
            list.add(bytesColumn);
        }
        return list;
    }

    @Override
    public void columns(Collection bytesColumns) {
        for (BackendColumn column : bytesColumns) {
            this.columns.put(StringEncoding.decode(column.name),
                             StringEncoding.decode(column.value));
        }
    }

    @Override
    public void columns(BackendColumn... bytesColumns) {
        for (BackendColumn column : bytesColumns) {
            this.columns.put(StringEncoding.decode(column.name),
                             StringEncoding.decode(column.value));
        }
    }

    @Override
    public void merge(BackendEntry other) {
        TextBackendEntry text = (TextBackendEntry) other;
        this.columns.putAll(text.columns);
    }

    @Override
    public void clear() {
        this.columns.clear();
    }

    private void resetColumns() {
        this.columns = new ConcurrentSkipListMap<>();
    }

    public TextBackendEntry copy() {
        try {
            TextBackendEntry clone = (TextBackendEntry) this.clone();
            clone.columns = new ConcurrentSkipListMap<>(this.columns);
            return clone;
        } catch (CloneNotSupportedException e) {
            throw new BackendException(e);
        }
    }

    public TextBackendEntry copyLast(int count) {
        TextBackendEntry clone;
        try {
            clone = (TextBackendEntry) this.clone();
        } catch (CloneNotSupportedException e) {
            throw new BackendException(e);
        }
        clone.resetColumns();

        // Copy the last count columns
        Iterator> it = this.columns.entrySet().iterator();
        final int skip = this.columns.size() - count;
        for (int i = 0; it.hasNext(); i++) {
            Entry entry = it.next();
            if (i < skip) {
                continue;
            }
            clone.columns.put(entry.getKey(), entry.getValue());
        }
        return clone;
    }

    public TextBackendEntry copyHead(int count) {
        TextBackendEntry clone;
        try {
            clone = (TextBackendEntry) this.clone();
        } catch (CloneNotSupportedException e) {
            throw new BackendException(e);
        }
        clone.resetColumns();

        // Copy the head count columns
        Iterator> it = this.columns.entrySet().iterator();
        for (int i = 0; it.hasNext() && i < count; i++) {
            Entry entry = it.next();
            clone.columns.put(entry.getKey(), entry.getValue());
        }
        return clone;
    }

    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof TextBackendEntry)) {
            return false;
        }
        TextBackendEntry other = (TextBackendEntry) obj;
        if (this.id() != other.id() && !this.id().equals(other.id())) {
            return false;
        }
        if (this.columns().size() != other.columns().size()) {
            return false;
        }
        for (Map.Entry e : this.columns.entrySet()) {
            String key = e.getKey();
            String value = e.getValue();
            String otherValue = other.columns.get(key);
            if (otherValue == null) {
                return false;
            }
            if (!value.equals(otherValue)) {
                return false;
            }
        }
        return true;
    }
}