package com.framsticks.structure; import java.util.Comparator; import java.util.Iterator; import java.util.Map; import java.util.PriorityQueue; import javax.annotation.Nonnull; import org.apache.commons.collections.map.ReferenceIdentityMap; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.LogManager; import com.framsticks.communication.queries.NeedFile; import com.framsticks.communication.queries.NeedFileAcceptor; import com.framsticks.params.Access; import com.framsticks.params.CompositeParam; import com.framsticks.params.FramsClass; import com.framsticks.params.ParamFlags; import com.framsticks.params.ParamsPackage; import com.framsticks.params.Registry; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.annotations.ParamAnnotation; import com.framsticks.structure.messages.ListChange; import com.framsticks.structure.messages.Message; import com.framsticks.structure.messages.Result; import com.framsticks.structure.messages.ValueChange; import com.framsticks.util.ExceptionHandler; import com.framsticks.util.FramsticksException; import com.framsticks.util.Misc; import com.framsticks.util.dispatching.AbstractJoinable; import com.framsticks.util.dispatching.BufferedDispatcher; import com.framsticks.util.dispatching.Dispatcher; import com.framsticks.util.dispatching.Dispatching; import com.framsticks.util.dispatching.Joinable; import com.framsticks.util.dispatching.JoinableParent; import com.framsticks.util.dispatching.JoinableState; import com.framsticks.util.dispatching.RunAt; import com.framsticks.util.dispatching.ThrowExceptionHandler; import com.framsticks.util.lang.Pair; /** * @author Piotr Sniegowski */ @FramsClassAnnotation public abstract class AbstractTree extends AbstractJoinable implements Tree, JoinableParent, NeedFileAcceptor { private static final Logger log = LogManager.getLogger(AbstractTree.class); private Node root = null; private ExceptionHandler handler = ThrowExceptionHandler.getInstance(); protected final BufferedDispatcher bufferedDispatcher = new BufferedDispatcher<>(this); protected final Registry registry = new Registry(); protected final PriorityQueue> needFileAcceptors = new PriorityQueue<>(32, new Comparator>() { @Override public int compare(Pair arg0, Pair arg1) { if (arg0.first < arg1.first) { return -1; } if (arg0.first > arg1.first) { return 1; } return 0; } }); @Override public void assignRootParam(CompositeParam param) { if (root != null) { throw new FramsticksException().msg("root has already specified type"); } root = new Node(this, param, null); log.debug("assigned root type: {}", root); } @Override public void assignRootObject(Object object) { if (root == null) { throw new FramsticksException().msg("root has no type specified"); } if (root.getObject() != null) { throw new FramsticksException().msg("root has already object assigned").arg("current", root.getObject()).arg("candidate", object); } root = new Node(this, root.getParam(), object); log.debug("assigned root object: {}", root); } @Override public @Nonnull Node getAssignedRoot() { if (root == null) { throw new FramsticksException().msg("root has no type specified yet").arg("in", this); } return root; } public boolean isRootAssigned() { // assert isActive(); return root != null; } protected String name; public AbstractTree() { setName("tree"); registry.registerAndBuild(Result.class); registry.registerAndBuild(ValueChange.class); registry.registerAndBuild(ListChange.class); registry.registerAndBuild(Message.class); } protected void tryRegisterOnChangeEvents(Path path) { } @Override public final FramsClass getInfoFromCache(String id) { assert isActive(); return registry.getFramsClass(id); } @Override public @Nonnull Access prepareAccess(CompositeParam param) { return registry.prepareAccess(param, false); } @Override public void takeAllFrom(Registry source) { registry.takeAllFrom(source); } @AutoAppendAnnotation public void usePackage(ParamsPackage paramsPackage) { log.debug("using package {} in tree {}", paramsPackage, this); paramsPackage.register(registry); } @AutoAppendAnnotation public void takeFromRegistry(Registry registry) { log.debug("taking from registry {} in tree {}", registry, this); this.registry.takeAllFrom(registry); } @Override public void putInfoIntoCache(FramsClass framclass) { registry.putFramsClass(framclass); } /** * @return the handler */ @Override public ExceptionHandler getExceptionHandler() { return handler; } /** * @param handler the handler to set */ @Override public void setExceptionHandler(ExceptionHandler handler) { this.handler = handler; } @Override public void handle(FramsticksException exception) { handler.handle(exception); } /** * @return the dispatcher */ @Override public Dispatcher getDispatcher() { return bufferedDispatcher; } /** * @param dispatcher the dispatcher to set */ @Override public void setDispatcher(Dispatcher dispatcher) { bufferedDispatcher.setTargetDispatcher(dispatcher); } /** * @return the name */ @ParamAnnotation(flags = ParamFlags.USERREADONLY) public String getName() { return name; } /** * @param name the name to set */ @ParamAnnotation public void setName(String name) { this.name = name; } /** * @return the registry */ @Override public Registry getRegistry() { return registry; } @Override protected void joinableStart() { bufferedDispatcher.createThreadIfNeeded(); Dispatching.use(bufferedDispatcher, this); } @Override protected void joinableInterrupt() { Dispatching.drop(bufferedDispatcher, this); } @Override protected void joinableFinish() { } @Override protected void joinableJoin() throws InterruptedException { Dispatching.join(bufferedDispatcher); } @Override public void childChangedState(Joinable joinable, JoinableState state) { if (joinable == bufferedDispatcher) { proceedToState(state); } } @Override public boolean isActive() { return bufferedDispatcher.isActive(); } @Override public void dispatch(RunAt runnable) { bufferedDispatcher.dispatch(runnable); } @SuppressWarnings("unchecked") protected final Map sideNotes = (Map) new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD); @Override public void putSideNote(Object object, SideNoteKey key, T value) { assert isActive(); Misc.throwIfNull(object); Misc.throwIfNull(key); Misc.throwIfNull(value); Object sideNote = sideNotes.get(object); if (sideNote == null) { sideNote = new ReferenceIdentityMap(ReferenceIdentityMap.WEAK, ReferenceIdentityMap.HARD); sideNotes.put(object, sideNote); } @SuppressWarnings("unchecked") Map, Object> sideNotesMap = (Map, Object>) sideNote; sideNotesMap.put(key, value); } @SuppressWarnings("unchecked") @Override public T getSideNote(Object object, SideNoteKey key) { assert isActive(); Misc.throwIfNull(object); Misc.throwIfNull(key); Object sideNote = sideNotes.get(object); if (sideNote == null) { return null; } Object value = ((Map, Object>) sideNote).get(key); if (value == null) { return null; } return (T) value; } @Override public boolean removeSideNote(Object object, SideNoteKey key) { assert isActive(); Object sideNote = sideNotes.get(object); if (sideNote == null) { return false; } @SuppressWarnings("unchecked") Map, Object> sideNotesMap = (Map, Object>) sideNote; boolean result = (sideNotesMap.remove(key) != null); if (sideNotesMap.isEmpty()) { sideNotes.remove(object); } return result; } @Override public void addNeedFileAcceptor(int priority, NeedFileAcceptor acceptor) { assert isActive(); needFileAcceptors.add(Pair.make(priority, acceptor)); } @Override public void removeNeedFileAcceptor(NeedFileAcceptor acceptor) { assert isActive(); Iterator> i = needFileAcceptors.iterator(); while (i.hasNext()) { if (i.next().second == acceptor) { i.remove(); break; } } } @Override public boolean acceptNeed(final NeedFile needFile) { Dispatching.dispatchIfNotActive(this, new RunAt(needFile.getFuture()) { @Override protected void runAt() { for (Pair acceptor : needFileAcceptors) { if (acceptor.second.acceptNeed(needFile)) { return; } } throw new FramsticksException().msg("failed to find need file acceptor in tree").arg("tree", AbstractTree.this).arg("needfile", needFile); } }); return true; } }