/* * SSSync, a Simple and Stupid Synchronizer for data with multi-valued attributes * Copyright (C) 2014 Ludovic Pouzenc * * This file is part of SSSync. * * SSSync is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * SSSync 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with SSSync. If not, see */ package data; import java.util.HashSet; import java.util.Map.Entry; import java.util.Set; import com.google.common.collect.HashMultimap; /** * Generic Multi-Valued data type. Each object store a particular entry. * Semantics are like in LDAP directories : an entry = a key + a set of multi-valued attributes. * Relational data like in RDMS are more constrained : columns are fixed for an entire table. * Null and empty string attribute value are silently discarded. * * @author lpouzenc */ public class MVDataEntry implements Comparable { /** * The key part that identify this particular entry. */ private final String key; /** * The data part of this particular entry. */ private HashMultimap attrValPairs; //FIXME : memory inefficient //XXX Consider https://commons.apache.org/proper/commons-collections/javadocs/api-3.2.1/org/apache/commons/collections/map/MultiValueMap.html // Constructors /** * Build a fresh empty MVDataEntry. * @param key Unique key identifying this entry */ public MVDataEntry(String key) { if ( key == null ) { throw new IllegalArgumentException("key must be non-null"); } this.key = key; this.attrValPairs = HashMultimap.create(); } /** * Build a fresh empty MVDataEntry with hints about expected attr/values count. * @param key Unique key identifying this entry */ public MVDataEntry(String key, int expectedAttribs, int expectedValuesPerAttrib) { if ( key == null ) { throw new IllegalArgumentException("key must be non-null"); } this.key = key; this.attrValPairs = HashMultimap.create(expectedAttribs, expectedValuesPerAttrib); } /** * Deep copy of an existing MVDataEntry. * @param key Unique key identifying this entry */ public MVDataEntry(final MVDataEntry copyFrom) { this.key=copyFrom.key; // String is immutable, so ref copy is okay this.attrValPairs = HashMultimap.create(copyFrom.attrValPairs); } /** * Proxy function to return all attribute/value pairs. * One can use read a MVDataEntry without depending on non-standard HashMultimap. * @return */ public Set> getAllEntries() { return this.attrValPairs.entries(); } /** * Proxy function to add an attribute/value pair in attrValPairs. * One can use MVDataEntry without depending on non-standard HashMultimap. * * @param attr * @param value */ public void put(String attr, String... values) { for (String value: values) { if ( value != null && !value.isEmpty() ) { this.attrValPairs.put(attr, value); } } } /** * Proxy function to get all values from a particular attribute. * One can use MVDataEntry without depending on non-standard HashMultimap. * @param attr * @return */ public Set getValues(String attr) { return this.attrValPairs.get(attr); } /** * Helper function to insert multiple values from a single string. * * @param attr * @param value * @param splitRegex */ public void splitAndPut(String attr, String value, String splitRegex) { if ( value != null ) { for (String v : value.split(splitRegex)) { put(attr, v); } } } /** * Helper function to return list of changed attributes. * Note : this don't keep track of deleted attributes. * @param original * @return */ public Set getChangedAttributes(MVDataEntry original) { HashSet result = new HashSet(); for (String attr: this.attrValPairs.keySet()) { Set thisValue = this.attrValPairs.get(attr); Set originalValue = original.attrValPairs.get(attr); if ( ! thisValue.equals(originalValue) ) { result.add(attr); } } return result; } /** * Augment this entry with attr/values from other entries. * @param appendMode Select behavior on an existing attribute : append values or replace them * @param entries Entries to merge with current entry */ public void mergeValues(boolean appendMode, MVDataEntry... entries) { for(MVDataEntry entry : entries) { if ( ! appendMode ) { for (String attr : entry.attrValPairs.keySet()) { this.attrValPairs.removeAll(attr); } } this.attrValPairs.putAll(entry.attrValPairs); } } /** * Check if this entry seems contains useful data. * @return true if this entry seems contains useful data */ public boolean isValid() { boolean validKey=(this.key != null && this.key.length() > 0 ); boolean validVal=(this.attrValPairs != null && ! this.attrValPairs.isEmpty()); return (validKey && validVal); } /** * {@inheritDoc} */ @Override public boolean equals(Object obj) { // Check for self-comparison (compare object references) if ( this == obj ) { return true; } // Check non-nullity and type if ( !( obj instanceof MVDataEntry) ) { return false; } // Cast safely MVDataEntry other = (MVDataEntry) obj; // Check all fields (known to be always non null) return ( this.key.equals(other.key) && this.attrValPairs.equals(other.attrValPairs) ); } /** * Compares entries. Ordering of entries is the ordering of their keys. * (java.lang.String default ordering : lexicographical ascending order) */ @Override public int compareTo(MVDataEntry other) { return this.key.compareTo(other.key); } /** * {@inheritDoc} */ @Override public String toString() { return "{key=" + key + ", attrValPairs=" + attrValPairs.toString() + "}"; } // Boring accessors /** * @return the attrValPairs */ public HashMultimap getAttrValPairs() { return attrValPairs; } /** * @param attrValPairs the attrValPairs to set */ public void setAttrValPairs(HashMultimap attrValPairs) { this.attrValPairs = attrValPairs; } /** * @return the key (guaranteed to be non-null) */ public String getKey() { return key; } }