package sushi.event.collection;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EntityTransaction;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinTable;
import javax.persistence.OneToMany;
import javax.persistence.Query;
import javax.persistence.Table;
import sushi.persistence.Persistable;
import sushi.persistence.Persistor;
/**
* TreeStructure used for structured key value mapping. Each node can have childes. Its possible to have many root nodes.
* @param <K>
* @param <V>
*/
@Entity
@Table(name = "SushiMapTree")
public class SushiMapTree<K, V> extends Persistable implements Map<K,V> {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name="SushiMapID")
protected int ID;
// splitted root elements and hierachical for more convinience
@OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
@JoinTable(name="SushiMapTree_SushiMapTreeElements")
private List<SushiMapElement<K,V>> treeElements = new ArrayList<SushiMapElement<K,V>>();
@OneToMany(cascade={CascadeType.PERSIST, CascadeType.REMOVE})
@JoinTable(name="SushiMapTree_SushiMapTreeRootElements")
private List<SushiMapElement<K,V>> treeRootElements = new ArrayList<SushiMapElement<K,V>>();
// JPA cannot save empty objects. this element is carrying
@Column(name="Test")
private String test = "Test";
public SushiMapTree(){
this.ID = 0;
}
public SushiMapTree(K rootElementKey, V rootElementValue){
this.ID = 0;
SushiMapElement<K, V> element = new SushiMapElement<K, V>(rootElementKey, rootElementValue);
treeElements.add(element);
treeRootElements.add(element);
}
public boolean isHierarchical() {
return (! (treeRootElements.size() == treeElements.size()));
}
public V getValueOfAttribute(String attribute) {
for (SushiMapElement<K, V> element : treeElements) {
if (element.getKey().equals(attribute)) {
return element.getValue();
}
}
return null;
}
/**
* @return Values of the Elements in the first hierarchy
*/
public List<V> getRootElementValues(){
List<V> rootElementValues = new ArrayList<V>();
for(SushiMapElement<K, V> currentTreeElement : treeRootElements){
rootElementValues.add(currentTreeElement.getValue());
}
return rootElementValues;
}
/**
* @param treeElementKey
* @return value of the partent of the node of the given key
*/
public V getParentValue(K treeElementKey){
for(SushiMapElement<K, V> currentTreeElement : treeElements){
if(currentTreeElement.getKey() == treeElementKey){
return currentTreeElement.getParent().getValue();
}
}
return null;
}
/**
*
* @param treeElementKey
* @return key of the node of the given key
*/
public K getParentKey(K treeElementKey){
for(SushiMapElement<K, V> currentTreeElement : treeElements){
if(currentTreeElement.getKey() == treeElementKey){
return currentTreeElement.getParent().getKey();
}
}
return null;
}
/**
* @param treeElementKey
* @return all children keys for a specific element in the tree with the key treeElementKey.
*/
public List<K> getChildrenKeys(K treeElementKey){
List<K> childrenKeys = new ArrayList<K>();
SushiMapElement<K,V> currentMapElement = findMapElementByKey(treeElementKey);
for(SushiMapElement<K,V> childElement : currentMapElement.getChildren()){
childrenKeys.add(childElement.getKey());
}
return childrenKeys;
}
/**
* @param treeElementKey
* @return all children values for a specific element in the tree with the key treeElementKey.
*/
public List<V> getChildrenValues(K treeElementKey){
List<V> childrenValues = new ArrayList<V>();
SushiMapElement<K,V> currentMapElement = findMapElementByKey(treeElementKey);
for(SushiMapElement<K,V> childElement : currentMapElement.getChildren()){
childrenValues.add(childElement.getValue());
}
return childrenValues;
}
/**
* checks if the node of the given key has children
* @param treeElementKey
* @return
*/
public boolean hasChildren(K treeElementKey){
SushiMapElement<K,V> currentMapElement = findMapElementByKey(treeElementKey);
return currentMapElement.hasChildren();
}
/**
* Adds a child to the specified parent.
* @param parentKey
* @param child
*/
public void addChild(K parentKey, K childKey, V childValue){
SushiMapElement<K,V> parentElement = findMapElementByKey(parentKey);
SushiMapElement<K,V> childMapElement = new SushiMapElement<K,V>(parentElement, childKey, childValue);
treeElements.add(childMapElement);
if(parentElement == null){
treeRootElements.add(childMapElement);
}
}
/**
* adds root node with the given key and value
* @param childKey
* @param childValue
* @return
*/
public boolean addRootElement(K childKey, V childValue){
SushiMapElement<K,V> element = new SushiMapElement<K,V>(childKey, childValue);
return (treeRootElements.add(element) && treeElements.add(element));
}
/**
* Removes all children from the specified map element.
* @param treeElement
* @return
*/
public void removeChildren(K mapElementKey){
SushiMapElement<K,V> currentMapElement = findMapElementByKey(mapElementKey);
for(SushiMapElement<K,V> child : currentMapElement.getChildren()){
remove(child.getKey());
}
}
@Override
public int size() {
return treeElements.size();
}
@Override
public boolean isEmpty() {
return treeElements.isEmpty();
}
@Override
public boolean containsKey(Object key) {
return keySet().contains(key);
}
@Override
public boolean containsValue(Object value) {
return values().contains(value);
}
@Override
public V get(Object key) {
SushiMapElement<K, V> element = findMapElementByKey((K) key);
if(element != null){
return element.getValue();
} else {
return null;
}
}
@Override
public V put(K key, V value) {
treeElements.add(new SushiMapElement<K, V>(key, value));
treeRootElements.add(new SushiMapElement<K, V>(key, value));
return value;
}
@Override
public V remove(Object key) {
SushiMapElement<K, V> removeTreeElement = findMapElementByKey((K) key);
V removeTreeElementValue = removeTreeElement(removeTreeElement);
return removeTreeElementValue;
}
/**
* removes the given node from the tree
* @param removeTreeElement
* @return
*/
private V removeTreeElement(SushiMapElement<K, V> removeTreeElement) {
V removeTreeElementValue = null;
if(removeTreeElement != null){
removeTreeElementValue = removeTreeElement.getValue();
}
if(removeTreeElement != null){
if(removeTreeElement.hasChildren()){
List<SushiMapElement<K, V>> children = new ArrayList<SushiMapElement<K, V>>(removeTreeElement.getChildren());
for(SushiMapElement<K, V> child : children){
remove(child.getKey());
}
}
if (removeTreeElement.getParent() != null){
removeTreeElement.getParent().getChildren().remove(removeTreeElement);
}
treeElements.remove(removeTreeElement);
treeRootElements.remove(removeTreeElement);
}
return removeTreeElementValue;
}
@Override
public void putAll(Map<? extends K, ? extends V> m) {
for(java.util.Map.Entry<? extends K, ? extends V> element : m.entrySet()){
treeRootElements.add(new SushiMapElement<K, V>(element.getKey(), element.getValue()));
}
}
@Override
public void clear() {
treeElements.clear();
treeRootElements.clear();
}
@Override
public Set<K> keySet() {
Set<K> keySet = new HashSet<K>();
for(SushiMapElement mapElement : treeElements){
keySet.add((K) mapElement.getKey());
}
return keySet;
}
@Override
public Collection<V> values() {
Collection<V> valueCollection = new ArrayList<V>();
for(SushiMapElement mapElement : treeElements){
valueCollection.add((V) mapElement.getValue());
}
return valueCollection;
}
@Override
public Set<java.util.Map.Entry<K, V>> entrySet() {
Set<java.util.Map.Entry<K, V>> elementSet = new HashSet<Map.Entry<K,V>>();
for(SushiMapElement<K, V> element : treeElements){
elementSet.add(new AbstractMap.SimpleEntry<K, V>(element.getKey(), element.getValue()));
}
return elementSet;
}
/**
* return value of the node of the given key
* @param elementKey
* @return
*/
public V findElement(K elementKey){
SushiMapElement<K, V> element = findMapElementByKey(elementKey);
if(element != null){
return element.getValue();
} else {
return null;
}
}
/**
* return node with the given key
* @param treeElementKey
* @return
*/
private SushiMapElement<K, V> findMapElementByKey(K treeElementKey){
if(treeElementKey == null){
return null;
}
for(SushiMapElement<K, V> currentMapElement : treeElements){
if(currentMapElement.getKey().equals(treeElementKey)){
return currentMapElement;
}
}
return null;
}
/**
* @return all maptrees
*/
public static List<SushiMapTree> findAll() {
Query q = Persistor.getEntityManager().createQuery("SELECT t from SushiMapTree t");
return q.getResultList();
}
/**
* removes all maptrees from DB
*/
public static void removeAll() {
try {
EntityTransaction entr = Persistor.getEntityManager().getTransaction();
entr.begin();
Query query = Persistor.getEntityManager().createQuery("DELETE FROM SushiMapTree");
int deleteRecords = query.executeUpdate();
entr.commit();
System.out.println(deleteRecords + " records are deleted.");
} catch (Exception ex) {
System.out.println(ex.getMessage());
}
}
@Override
public String toString(){
return printMapLevel(treeRootElements, 0);
}
private String printMapLevel(List<SushiMapElement<K, V>> treeElements, int count) {
String tree = "";
for(SushiMapElement<K, V> element : treeElements){
for(int i = 0; i < count; i++){
tree += "\t";
}
tree += element.getKey() + " : " + element.getValue() + "\r\n";
if(element.hasChildren()){
tree += printMapLevel(element.getChildren(), ++count);
}
}
return tree;
}
/**
* deletes all elements except the given
* @param collection
* @return
*/
public boolean retainAll(Collection collection){
boolean retainSuccess = true;
List<SushiMapElement<K, V>> copyTreeList = new ArrayList<SushiMapElement<K, V>>(treeElements);
for(SushiMapElement<K, V> element : copyTreeList){
if(!collection.contains(element.getValue())){
remove(element.getValue());
}
}
return retainSuccess;
}
/**
* use this only if the SushiMapTree is used as attribute name/value mapping
*
*/
public void retainAllByAttributeExpression(ArrayList<String> retainableAttributeExpressions) {
List<SushiMapElement<K, V>> treeElementsToBeRemoved = new ArrayList<SushiMapElement<K, V>>();
for (SushiMapElement<K, V> element : treeElements) {
if(!retainableAttributeExpressions.contains(element.getAttributeExpression())){
treeElementsToBeRemoved.add(element);
}
}
for (SushiMapElement<K, V> element : treeElementsToBeRemoved) {
this.removeTreeElement(element);
}
}
/**
* delete all node except those which keys are mentioned
* @param retainableKeys
*/
public void retainAllKeys(ArrayList<String> retainableKeys) {
for(K key : keySet()){
if(!retainableKeys.contains(key)){
this.remove(key);
}
}
}
public List<SushiMapElement<K, V>> getTreeRootElements() {
return treeRootElements;
}
public void setTreeRootElements(List<SushiMapElement<K, V>> treeRootElements) {
this.treeRootElements = treeRootElements;
}
@Override
public Object clone() throws CloneNotSupportedException {
return deepClone();
}
private SushiMapTree<K, V> deepClone() {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(this);
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
return (SushiMapTree<K, V>) ois.readObject();
} catch (IOException | ClassNotFoundException e) {
return null;
}
}
@Override
public int getID() {
return ID;
}
}