Java tutorial
/* * Copyright (C) 2010-2015, Danilo Pianini and contributors * listed in the project's pom.xml file. * * This file is part of Alchemist, and is distributed under the terms of * the GNU General Public License, with a linking exception, as described * in the file LICENSE in the Alchemist distribution's top directory. */ package it.unibo.alchemist.model; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.concurrent.ExecutionException; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.math3.random.MersenneTwister; import org.apache.commons.math3.random.RandomGenerator; import org.danilopianini.lang.LangUtils; import org.protelis.lang.ProtelisLoader; import org.protelis.lang.datatype.DeviceUID; import org.protelis.vm.ExecutionContext; import org.protelis.vm.ExecutionEnvironment; import org.protelis.vm.NetworkManager; import org.protelis.vm.ProtelisVM; import org.protelis.vm.impl.AbstractExecutionContext; import org.protelis.vm.impl.SimpleExecutionEnvironment; import org.protelis.vm.util.CodePath; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Lists; import it.unibo.alchemist.model.implementations.actions.RunProtelisProgram; import it.unibo.alchemist.model.implementations.actions.SendToNeighbor; import it.unibo.alchemist.model.implementations.conditions.ComputationalRoundComplete; import it.unibo.alchemist.model.implementations.molecules.SimpleMolecule; import it.unibo.alchemist.model.implementations.nodes.ProtelisNode; import it.unibo.alchemist.model.implementations.reactions.ChemicalReaction; import it.unibo.alchemist.model.implementations.reactions.Event; import it.unibo.alchemist.model.implementations.timedistributions.DiracComb; import it.unibo.alchemist.model.implementations.timedistributions.ExponentialTime; import it.unibo.alchemist.model.implementations.times.DoubleTime; import it.unibo.alchemist.model.interfaces.Action; import it.unibo.alchemist.model.interfaces.Condition; import it.unibo.alchemist.model.interfaces.Environment; import it.unibo.alchemist.model.interfaces.Incarnation; import it.unibo.alchemist.model.interfaces.Molecule; import it.unibo.alchemist.model.interfaces.Node; import it.unibo.alchemist.model.interfaces.Reaction; import it.unibo.alchemist.model.interfaces.Time; import it.unibo.alchemist.model.interfaces.TimeDistribution; /** */ public final class ProtelisIncarnation implements Incarnation<Object> { private static final Logger L = LoggerFactory.getLogger(ProtelisIncarnation.class); /** * The name that can be used in a property to refer to the extracted value. */ public static final String VALUE_TOKEN = "<value>"; private final LoadingCache<CacheKey, SynchronizedVM> cache = CacheBuilder.newBuilder() .expireAfterAccess(10, TimeUnit.MINUTES).build(new CacheLoader<CacheKey, SynchronizedVM>() { @Override public SynchronizedVM load(final CacheKey key) { return new SynchronizedVM(key); } }); @Override public Action<Object> createAction(final RandomGenerator rand, final Environment<Object> env, final Node<Object> node, final TimeDistribution<Object> time, final Reaction<Object> reaction, final String param) { Objects.requireNonNull(param); if (node instanceof ProtelisNode) { final ProtelisNode pNode = (ProtelisNode) node; if (param.equalsIgnoreCase("send")) { final List<RunProtelisProgram> alreadyDone = pNode.getReactions().parallelStream() .flatMap(r -> r.getActions().parallelStream()).filter(a -> a instanceof SendToNeighbor) .map(c -> (SendToNeighbor) c).map(crc -> crc.getProtelisProgram()) .collect(Collectors.toList()); final List<RunProtelisProgram> pList = getIncomplete(pNode, alreadyDone); if (pList.isEmpty()) { throw new IllegalStateException( "There is no program requiring a " + SendToNeighbor.class.getSimpleName() + " action"); } if (pList.size() > 1) { throw new IllegalStateException("There are too many programs requiring a " + SendToNeighbor.class.getName() + " action: " + pList); } return new SendToNeighbor(pNode, reaction, pList.get(0)); } else { try { return new RunProtelisProgram(env, pNode, reaction, rand, param); } catch (ClassNotFoundException | RuntimeException e) { throw new IllegalArgumentException("Could not create the requested Protelis program: " + param, e); } } } throw new IllegalArgumentException("The node must be an instance of " + ProtelisNode.class.getSimpleName() + ", it is a " + node.getClass().getName() + " instead"); } @Override public Object createConcentration(final String s) { try { final SynchronizedVM vm = new SynchronizedVM(new CacheKey(NoNode.INSTANCE, createMolecule(s), s)); return vm.runCycle(); } catch (IllegalArgumentException e) { /* * Not a valid program: inject the String itself */ return s; } } @Override public Condition<Object> createCondition(final RandomGenerator rand, final Environment<Object> env, final Node<Object> node, final TimeDistribution<Object> time, final Reaction<Object> reaction, final String param) { if (node instanceof ProtelisNode) { final ProtelisNode pNode = (ProtelisNode) node; /* * The list of ProtelisPrograms that have already been completed with a ComputationalRoundComplete condition */ final List<RunProtelisProgram> alreadyDone = pNode.getReactions().parallelStream() .flatMap(r -> r.getConditions().parallelStream()) .filter(c -> c instanceof ComputationalRoundComplete).map(c -> (ComputationalRoundComplete) c) .flatMap(crc -> crc.getInfluencingMolecules().parallelStream()) .filter(mol -> mol instanceof RunProtelisProgram).map(mol -> (RunProtelisProgram) mol) .collect(Collectors.toList()); final List<RunProtelisProgram> pList = getIncomplete(pNode, alreadyDone); if (pList.isEmpty()) { throw new IllegalStateException("There is no program requiring a " + ComputationalRoundComplete.class.getSimpleName() + " condition"); } if (pList.size() > 1) { throw new IllegalStateException("There are too many programs requiring a " + ComputationalRoundComplete.class.getName() + " condition: " + pList); } return new ComputationalRoundComplete(pNode, pList.get(0)); } throw new IllegalArgumentException("The node must be an instance of " + ProtelisNode.class.getSimpleName() + ", it is a " + node.getClass().getName() + " instead"); } @Override public Molecule createMolecule(final String s) { return new SimpleMolecule(Objects.requireNonNull(s)); } @Override public Node<Object> createNode(final RandomGenerator rand, final Environment<Object> env, final String param) { return new ProtelisNode(env); } @Override public Reaction<Object> createReaction(final RandomGenerator rand, final Environment<Object> env, final Node<Object> node, final TimeDistribution<Object> time, final String param) { LangUtils.requireNonNull(node, time); final boolean isSend = "send".equalsIgnoreCase(param); final Reaction<Object> result = isSend ? new ChemicalReaction<>(node, time) : new Event<>(node, time); if (param != null) { result.setActions(Lists.newArrayList(createAction(rand, env, node, time, result, param))); } if (isSend) { result.setConditions(Lists.newArrayList(createCondition(rand, env, node, time, result, null))); } return result; } @Override public TimeDistribution<Object> createTimeDistribution(final RandomGenerator rand, final Environment<Object> env, final Node<Object> node, final String param) { if (param == null) { return new ExponentialTime<>(Double.POSITIVE_INFINITY, rand); } double frequency; try { frequency = Double.parseDouble(param); } catch (final NumberFormatException e) { frequency = 1; } return new DiracComb<>(new DoubleTime(rand.nextDouble() / frequency), frequency); } @Override public double getProperty(final Node<Object> node, final Molecule mol, final String prop) { try { final SynchronizedVM vm = cache.get(new CacheKey(Objects.requireNonNull(node), Objects.requireNonNull(mol), Objects.requireNonNull(prop))); final Object val = vm.runCycle(); if (val instanceof Number) { return ((Number) val).doubleValue(); } else if (val instanceof String) { try { return Double.parseDouble(val.toString()); } catch (final NumberFormatException e) { if (val.equals(prop)) { return 1; } return 0; } } else if (val instanceof Boolean) { final Boolean cond = (Boolean) val; if (cond) { return 1d; } else { return 0d; } } } catch (ExecutionException e) { L.error("Bug in " + getClass().getName() + ": getProperty should never fail.", e); } return Double.NaN; } @Override public String toString() { return getClass().getSimpleName(); } private static List<RunProtelisProgram> getIncomplete(final ProtelisNode pNode, final List<RunProtelisProgram> alreadyDone) { return pNode.getReactions().parallelStream() /* * Get the actions */ .flatMap(r -> r.getActions().parallelStream()) /* * Get only the ProtelisPrograms */ .filter(a -> a instanceof RunProtelisProgram).map(a -> (RunProtelisProgram) a) /* * Retain only those ProtelisPrograms that have no associated ComputationalRoundComplete. * * Only one should be available. */ .filter(prog -> !alreadyDone.contains(prog)).collect(Collectors.toList()); } private static final class CacheKey { private final Molecule molecule; private final WeakReference<Node<Object>> node; private final String property; private final int hash; private CacheKey(final Node<Object> node, final Molecule mol, final String prop) { this.node = new WeakReference<>(node); molecule = mol; property = prop; hash = molecule.hashCode() ^ property.hashCode() ^ (node == null ? 0 : node.hashCode()); } @Override public boolean equals(final Object obj) { return obj instanceof CacheKey && ((CacheKey) obj).node.get() == node.get() && ((CacheKey) obj).molecule.equals(molecule) && ((CacheKey) obj).property.equals(property); } @Override public int hashCode() { return hash; } } /** * An {@link ExecutionContext} that operates over a node, but does not * modify it. */ public static class DummyContext extends AbstractExecutionContext { private static final Semaphore MUTEX = new Semaphore(1); private static final int SEED = -241837578; private static final RandomGenerator RNG = new MersenneTwister(SEED); private final Node<?> node; DummyContext(final Node<?> node) { super(new ProtectedExecutionEnvironment(node), new NetworkManager() { @Override public Map<DeviceUID, Map<CodePath, Object>> getNeighborState() { return Collections.emptyMap(); } @Override public void shareState(final Map<CodePath, Object> toSend) { } }); this.node = node; } @Override public Number getCurrentTime() { return 0; } @Override public DeviceUID getDeviceUID() { if (node instanceof ProtelisNode) { return (ProtelisNode) node; } throw new IllegalStateException("You tried to compute a Protelis device UID, on a non-Protelis node"); } @Override protected AbstractExecutionContext instance() { return this; } @Override public double nextRandomDouble() { final double result; MUTEX.acquireUninterruptibly(); result = RNG.nextDouble(); MUTEX.release(); return result; } } /** * An {@link ExecutionEnvironment} that can read and shadow the content of a * {@link Node}, but cannot modify it. This is used to prevent badly written * properties to interact with the simulation flow. */ public static class ProtectedExecutionEnvironment implements ExecutionEnvironment { private final Node<?> node; private final ExecutionEnvironment shadow = new SimpleExecutionEnvironment(); /** * @param node the {@link Node} */ public ProtectedExecutionEnvironment(final Node<?> node) { this.node = node; } @Override public void commit() { } @Override public Object get(final String id) { return shadow.get(id, node.getConcentration(new SimpleMolecule(id))); } @Override public Object get(final String id, final Object defaultValue) { return Optional.<Object>ofNullable(get(id)).orElse(defaultValue); } @Override public boolean has(final String id) { return shadow.has(id) || node.contains(new SimpleMolecule(id)); } @Override public boolean put(final String id, final Object v) { return shadow.put(id, v); } @Override public Object remove(final String id) { return shadow.remove(id); } @Override public void setup() { } } private static final class SynchronizedVM { private final CacheKey key; private final Semaphore mutex = new Semaphore(1); private final Optional<ProtelisVM> vm; private SynchronizedVM(final CacheKey key) { this.key = key; ProtelisVM myVM = null; if (!StringUtils.isBlank(key.property)) { try { final String baseProgram = "env.get(\"" + key.molecule.getName() + "\")"; myVM = new ProtelisVM(ProtelisLoader.parse(key.property.replace(VALUE_TOKEN, baseProgram)), new DummyContext(key.node.get())); } catch (RuntimeException ex) { L.warn("Program ignored as invalid: \n" + key.property); L.debug("Debug information", ex); } } vm = Optional.ofNullable(myVM); } public Object runCycle() { final Node<Object> node = key.node.get(); if (node == null) { throw new IllegalStateException("The node should never be null"); } if (vm.isPresent()) { final ProtelisVM myVM = vm.get(); mutex.acquireUninterruptibly(); myVM.runCycle(); mutex.release(); return myVM.getCurrentValue(); } if (node instanceof NoNode) { return key.property; } return node.getConcentration(key.molecule); } } private static final class NoNode implements Node<Object> { private static final long serialVersionUID = 1L; public static final NoNode INSTANCE = new NoNode(); private NoNode() { } private <A> A notImplemented() { throw new UnsupportedOperationException("Method can't be invoked in this context."); } @Override public Iterator<Reaction<Object>> iterator() { return notImplemented(); } @Override public int compareTo(final Node<Object> o) { return notImplemented(); } @Override public void addReaction(final Reaction<Object> r) { notImplemented(); } @Override public boolean contains(final Molecule mol) { return notImplemented(); } @Override public int getChemicalSpecies() { return notImplemented(); } @Override public Object getConcentration(final Molecule mol) { return notImplemented(); } @Override public Map<Molecule, Object> getContents() { return notImplemented(); } @Override public int getId() { return notImplemented(); } @Override public List<Reaction<Object>> getReactions() { return null; } @Override public void removeConcentration(final Molecule mol) { notImplemented(); } @Override public void removeReaction(final Reaction<Object> r) { notImplemented(); } @Override public void setConcentration(final Molecule mol, final Object c) { notImplemented(); } @Override public Node<Object> cloneNode(final Time t) { return notImplemented(); } @Override public boolean equals(final Object obj) { return obj instanceof NoNode; } @Override public int hashCode() { return -1; } } }