package com.framsticks.core; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.annotation.Nonnull; import org.apache.log4j.Logger; import com.framsticks.communication.File; import com.framsticks.params.AccessInterface; import com.framsticks.params.CompositeParam; import com.framsticks.params.ConstructionException; import com.framsticks.params.FramsClass; import com.framsticks.params.ListAccess; import com.framsticks.params.Param; import com.framsticks.params.ParamsPackage; import com.framsticks.params.Registry; import com.framsticks.params.ValueParam; import com.framsticks.params.annotations.AutoAppendAnnotation; import com.framsticks.params.annotations.FramsClassAnnotation; import com.framsticks.params.types.ObjectParam; import com.framsticks.params.types.ProcedureParam; import com.framsticks.parsers.Loaders; import com.framsticks.parsers.MultiParamLoader; import com.framsticks.util.FramsticksException; import com.framsticks.util.StateFunctor; import com.framsticks.util.UnsupportedOperationException; import com.framsticks.util.dispatching.Dispatching; import com.framsticks.util.dispatching.Future; import com.framsticks.util.dispatching.RunAt; import com.framsticks.util.dispatching.Thread; import com.framsticks.util.lang.Casting; /** * @author Piotr Sniegowski */ @FramsClassAnnotation public abstract class Instance extends Thread implements Entity { private static final Logger log = Logger.getLogger(Instance.class.getName()); private Node root; protected @Nonnull Node setRoot(CompositeParam param, Object object) { // if (isRootAssigned()) { // throw new FramsticksException().msg("root is already assigned"); // } // assert isActive(); root = new Node(param, object); return root; } protected @Nonnull Node getRoot() { // assert isActive(); assert root != null; return root; } public boolean isRootAssigned() { // assert isActive(); return root != null; } protected Set listeners = new HashSet(); public Instance() { setName("entity"); } protected void fetchInfo(Path path, Future future) { future.result(null, new UnsupportedOperationException()); } public void resolve(Path path, Future future) { assert isActive(); assert path.isOwner(this); if (path.getTop().getObject() != null) { future.result(path, null); return; } AccessInterface access = bindAccess(path.getUnder()); Object object = access.get(path.getTop().getParam(), Object.class); if (object == null) { future.result(path, null); return; } future.result(path.appendResolution(object), null); } /** This is part of the Instance interface. * */ public abstract void fetchValue(Path path, Param param, StateFunctor stateFunctor); /** This is part of the Instance interface. * */ public abstract void fetchValues(Path path, StateFunctor stateFunctor); /** This is part of the Instance interface. * */ public abstract void call(Path path, ProcedureParam param, Object[] arguments, StateFunctor stateFunctor); protected void tryRegisterOnChangeEvents(Path path) { } public void storeValue(Path path, Param param, Object value, final StateFunctor stateFunctor) { assert isActive(); dispatch(new RunAt() { @Override public void run() { stateFunctor.call(new UnsupportedOperationException()); } }); } protected void fireRun(Exception e) { for (InstanceListener l : this.listeners) { l.onRun(e); } } protected void fireStop(Exception e) { for (InstanceListener l : this.listeners) { l.onStop(e); } } public void addListener(final InstanceListener listener) { assert Dispatching.isThreadSafe(); Dispatching.dispatchIfNotActive(this, new RunAt() { @Override public void run() { listeners.add(listener); } }); } public void removeListener(final InstanceListener listener) { assert Dispatching.isThreadSafe(); Dispatching.dispatchIfNotActive(this, new RunAt() { @Override public void run() { listeners.remove(listener); } }); } protected void fireListChange(Path path, ListChange change) { assert isActive(); for (InstanceListener l : this.listeners) { l.onListChange(path, change); } } protected void fireFetch(Path path) { assert isActive(); for (InstanceListener l : this.listeners) { l.onFetch(path); } } public final FramsClass getInfoFromCache(Path path) { return getInfoFromCache(path.getTop().getParam().getContainedTypeName()); } public FramsClass getInfoFromCache(String id) { assert isActive(); return registry.getFramsClass(id); } protected Registry registry = new Registry(); public AccessInterface createAccess(String name) throws ConstructionException { assert isActive(); return registry.createAccess(name); } // TODO: make ValueParam public T get(Node node, Param childParam, Class type) { return bindAccess(node).get((ValueParam) childParam, type); } public void findInfo(final Path path, final Future future) { assert isActive(); final String name = path.getTop().getParam().getContainedTypeName(); final FramsClass framsClass = getInfoFromCache(name); if (framsClass != null) { log.trace("info for " + name + " found in cache"); future.result(framsClass, null); return; } fetchInfo(path, future); } public final AccessInterface bindAccess(String path) { return bindAccess(getPath(path)); } public final AccessInterface bindAccess(Node node) { assert isActive(); assert node.getObject() != null; try { AccessInterface access = registry.prepareAccess(node.getParam()); if (access == null) { throw new FramsticksException().msg("failed to prepare access for param").arg("param", node.getParam()); } return access.select(node.getObject()); } catch (ConstructionException e) { log.error("failed to bind access for " + node.getParam() + ": " + e); } return null; } public final T getParam(Path path, String id, Class type) { return Casting.tryCast(type, registry.prepareAccess(path.getTop().getParam()).getParam(id)); } public final AccessInterface bindAccess(Path path) { path.assureResolved(); return bindAccess(path.getTop()); } public void resolve(final String targetPath, final Future future) { assert isActive(); final Path path = getPath(targetPath); resolve(path, new Future() { @Override public void result(Path result, Exception e) { assert isActive(); if (e != null) { future.result(path, e); return; } if (path.isResolved(targetPath)) { future.result(path, null); return; } if (path.isResolved()) { future.result(path, new Exception("testing")); return; } resolve(targetPath, future); } }); } public void resolveAndFetch(final String targetPath, final Future future) { assert isActive(); resolve(targetPath, new Future() { @Override public void result(final Path path, Exception e) { if (e != null) { future.result(path, e); return; } assert path.isResolved(targetPath); fetchValues(path, new StateFunctor() { @Override public void call(Exception e) { future.result(path, e); } }); } }); } public Path createIfNeeded(String path) { Path p; while (!(p = getPath(path)).isResolved(path)) { create(p); } return p; } public Path createIfNeeded(Path path) { assert isActive(); if (path.isResolved()) { return path; } return create(path); } public Path create(Path path) { assert isActive(); assert !path.isResolved(); Path resolved = path.tryFindResolution(); if (!resolved.isResolved()) { log.debug("creating: " + path); AccessInterface access = registry.prepareAccess(path.getTop().getParam()); assert access != null; Object child = access.createAccessee(); assert child != null; if (path.size() == 1) { setRoot(getRoot().getParam(), child); } else { bindAccess(path.getUnder()).set(path.getTop().getParam(), child); } resolved = path.appendResolution(child); } tryRegisterOnChangeEvents(resolved); return resolved; } public @Nonnull FramsClass processFetchedInfo(File file) { assert isActive(); FramsClass framsClass = Loaders.loadFramsClass(file.getContent()); if ("/".equals(file.getPath())) { if (getRoot().getParam().getContainedTypeName() == null) { setRoot(Param.build().name("Instance").id(getName()).type("o " + framsClass.getId()).finish(CompositeParam.class), getRoot().getObject()); } } registry.putFramsClass(framsClass); return framsClass; } public void processFetchedValues(Path path, List files) { assert isActive(); assert files.size() == 1; assert path.isTheSame(files.get(0).getPath()); Node node = path.getTop(); MultiParamLoader loader = new MultiParamLoader(); loader.setNewSource(files.get(0).getContent()); loader.addBreakCondition(MultiParamLoader.Status.AfterObject); try { if (node.getParam() instanceof ObjectParam) { loader.addAccessInterface(bindAccess(node)); loader.go(); fireFetch(path); return; } ListAccess listAccess = ((ListAccess)bindAccess(node)); assert listAccess != null; listAccess.clearValues(); AccessInterface elementAccess = listAccess.getElementAccess(); loader.addAccessInterface(elementAccess); MultiParamLoader.Status status; while ((status = loader.go()) != MultiParamLoader.Status.Finished) { if (status == MultiParamLoader.Status.AfterObject) { AccessInterface accessInterface = loader.getLastAccessInterface(); String id = listAccess.computeIdentifierFor(accessInterface.getSelected()); //TODO listAccessParam Param param = Param.build().forAccess(accessInterface).id(id).finish(); Object child = accessInterface.getSelected(); accessInterface.select(null); assert child != null; bindAccess(node).set((ValueParam) param, child); } } fireFetch(path); } catch (Exception e) { log.error("exception occurred while loading: " + e); } } public static Iterator splitPath(String path) { List list = new LinkedList(); for (String s : path.split("/")) { if (!s.isEmpty()) { list.add(s); } } return list.iterator(); } public Registry getRegistry() { return registry; } public Path getPath(String textual) { return Path.build().resolve(this, textual).finish(); } public Path getRootPath() { return getPath("/"); } @AutoAppendAnnotation public void usePackage(ParamsPackage paramsPackage) { log.debug("using package " + paramsPackage + " in instance " + this); paramsPackage.register(registry); } @AutoAppendAnnotation public void takeFromRegistry(Registry registry) { log.debug("taking from registry " + registry + " in instance " + this); this.registry.takeAllFrom(registry); } @Override protected void joinableStart() { dispatch(new RunAt() { @Override public void run() { if (!isRootAssigned()) { setRoot(Param.build().name("Instance").id(getName()).type("o").finish(CompositeParam.class), null); } } }); super.joinableStart(); } }