/************************************************************************ * Copyright (c) 2014 IoT-Solutions e.U. * * Licensed 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 iot.jcypher.graph; import iot.jcypher.graph.internal.ChangeListener; import java.util.ArrayList; import java.util.Collections; import java.util.List; public abstract class PersistableItemsContainer<T extends PersistableItem> { private List<T> elements; private List<T> removedElements; private ElementChangeListener elementChangeListener; abstract SyncState getContainerSyncState(); abstract void setContainerSyncState(SyncState syncState); protected abstract void fireContainerChanged(SyncState oldState, SyncState newState); /** * calculate the container's state, check if it is sync * * @return true, if the container's calculated state is sync */ protected abstract boolean checkContainerForSyncState(); /** * Resolve and initialize the appropriate elements from the underlying * QueryResult JsonObject. * * @return */ protected abstract List<T> resolveElements(); /** * @param elems * @param elem * @return true, if list 'elems' contains 'elem' */ protected abstract boolean containsElement(List<T> elems, T elem); /** * @return a list of all modified elements */ List<T> getModifiedElements() { List<T> ret = new ArrayList<T>(); if (this.elements != null) { for (T elem : this.elements) { if (elem.getSyncState() != SyncState.SYNC) ret.add(elem); } } if (this.removedElements != null) ret.addAll(this.removedElements); return ret; } /** * @return an unmodifiable list of elements */ public List<T> getElements() { if (this.elements == null) { this.elements = resolveElements(); if (this.elementChangeListener == null) this.elementChangeListener = new ElementChangeListener(); for (T elem : this.elements) { elem.addChangeListener(this.elementChangeListener); } } // Build a new Array // to allow iterating over the elements with a for loop and to remove // elements. // That is done by calling remove() on the element, // which leads to removing the element from this.elements // That may not break the for loop ArrayList<T> list = new ArrayList<T>(this.elements); return Collections.unmodifiableList(list); } /** * add a new element, throw a RuntimeException if the element already exists * @param element * @return the added element */ public T addElement(T element) { // make sure that elements are initialized getElements(); if (!containsElement(this.elements, element)) { this.elements.add(element); element.addChangeListener(this.elementChangeListener); element.notifyState(); return element; } throw new RuntimeException(element.toString() + " already exists"); } public boolean checkForSyncState() { SyncState st = this.checkForElementStates(SyncState.SYNC); if (st == null) return this.removedElements == null || this.removedElements.isEmpty(); return false; } /** * check all elements, if their state is one of the given states * @param states to check against * @return null, if all elements have a state out of the requested states, * else return the first element state that differs */ private SyncState checkForElementStates(SyncState... states) { if (this.elements != null) { for (T elem : this.elements) { for (SyncState state : states) { if (elem.getSyncState() != state) return elem.getSyncState(); } } } return null; } void setToSynchronized() { this.removedElements = null; if (this.elements != null) { for (T item : this.elements) { item.setToSynchronized(); } } } /********************************************/ private class ElementChangeListener implements ChangeListener { @SuppressWarnings("unchecked") @Override public void changed(Object changedElement, SyncState oldState, SyncState newState) { if (newState == SyncState.REMOVED || newState == SyncState.NEW_REMOVED) { elements.remove(changedElement); if (newState == SyncState.REMOVED) { if (removedElements == null) removedElements = new ArrayList<T>(); if (!containsElement(removedElements, (T)changedElement)) removedElements.add((T) changedElement); } } SyncState myOldContainerState = getContainerSyncState(); if (getContainerSyncState() == SyncState.SYNC) { if (newState != SyncState.SYNC) setContainerSyncState(SyncState.CHANGED); // possibly reverts the CHANGED state } else if (getContainerSyncState() == SyncState.CHANGED && (newState == SyncState.NEW_REMOVED || newState == SyncState.SYNC)) { if (checkContainerForSyncState()) setContainerSyncState(SyncState.SYNC); } // notify if the element container's state has changed if (myOldContainerState != getContainerSyncState()) fireContainerChanged(myOldContainerState, getContainerSyncState()); } } }