Added javadocs and separated trivial implementations into distinct packages.
diff --git a/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkManager.java b/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkManager.java
new file mode 100644
index 0000000..a817bb6
--- /dev/null
+++ b/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkManager.java
@@ -0,0 +1,213 @@
+package org.onlab.onos.net.trivial.link.impl;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.util.Set;
+
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onlab.onos.event.AbstractListenerRegistry;
+import org.onlab.onos.event.EventDeliveryService;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkAdminService;
+import org.onlab.onos.net.link.LinkDescription;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.link.LinkListener;
+import org.onlab.onos.net.link.LinkProvider;
+import org.onlab.onos.net.link.LinkProviderRegistry;
+import org.onlab.onos.net.link.LinkProviderService;
+import org.onlab.onos.net.link.LinkService;
+import org.onlab.onos.net.provider.AbstractProviderRegistry;
+import org.onlab.onos.net.provider.AbstractProviderService;
+import org.slf4j.Logger;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Provides basic implementation of the link SB & NB APIs.
+ */
+@Component(immediate = true)
+@Service
+public class SimpleLinkManager
+ extends AbstractProviderRegistry<LinkProvider, LinkProviderService>
+ implements LinkService, LinkAdminService, LinkProviderRegistry {
+
+ private static final String DEVICE_ID_NULL = "Device ID cannot be null";
+ private static final String LINK_DESC_NULL = "Link description cannot be null";
+ private static final String CONNECT_POINT_NULL = "Connection point cannot be null";
+
+ private final Logger log = getLogger(getClass());
+
+ private final AbstractListenerRegistry<LinkEvent, LinkListener>
+ listenerRegistry = new AbstractListenerRegistry<>();
+
+ private final SimpleLinkStore store = new SimpleLinkStore();
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EventDeliveryService eventDispatcher;
+
+ @Activate
+ public void activate() {
+ eventDispatcher.addSink(LinkEvent.class, listenerRegistry);
+ log.info("Started");
+ }
+
+ @Deactivate
+ public void deactivate() {
+ eventDispatcher.removeSink(LinkEvent.class);
+ log.info("Stopped");
+ }
+
+ @Override
+ public int getLinkCount() {
+ return store.getLinkCount();
+ }
+
+ @Override
+ public Iterable<Link> getLinks() {
+ return store.getLinks();
+ }
+
+ @Override
+ public Set<Link> getDeviceLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return Sets.union(store.getDeviceEgressLinks(deviceId),
+ store.getDeviceIngressLinks(deviceId));
+ }
+
+ @Override
+ public Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getDeviceEgressLinks(deviceId);
+ }
+
+ @Override
+ public Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ return store.getDeviceIngressLinks(deviceId);
+ }
+
+ @Override
+ public Set<Link> getLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return Sets.union(store.getEgressLinks(connectPoint),
+ store.getIngressLinks(connectPoint));
+ }
+
+ @Override
+ public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return store.getEgressLinks(connectPoint);
+ }
+
+ @Override
+ public Set<Link> getIngressLinks(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, CONNECT_POINT_NULL);
+ return store.getIngressLinks(connectPoint);
+ }
+
+ @Override
+ public Link getLink(ConnectPoint src, ConnectPoint dst) {
+ checkNotNull(src, CONNECT_POINT_NULL);
+ checkNotNull(dst, CONNECT_POINT_NULL);
+ return store.getLink(src, dst);
+ }
+
+ @Override
+ public void removeLinks(ConnectPoint connectPoint) {
+ removeLinks(getLinks(connectPoint));
+ }
+
+ @Override
+ public void removeLinks(DeviceId deviceId) {
+ removeLinks(getDeviceLinks(deviceId));
+ }
+
+ @Override
+ public void addListener(LinkListener listener) {
+ listenerRegistry.addListener(listener);
+ }
+
+ @Override
+ public void removeListener(LinkListener listener) {
+ listenerRegistry.removeListener(listener);
+ }
+
+ @Override
+ protected LinkProviderService createProviderService(LinkProvider provider) {
+ return new InternalLinkProviderService(provider);
+ }
+
+ // Personalized link provider service issued to the supplied provider.
+ private class InternalLinkProviderService
+ extends AbstractProviderService<LinkProvider>
+ implements LinkProviderService {
+
+ InternalLinkProviderService(LinkProvider provider) {
+ super(provider);
+ }
+
+ @Override
+ public void linkDetected(LinkDescription linkDescription) {
+ checkNotNull(linkDescription, LINK_DESC_NULL);
+ checkValidity();
+ LinkEvent event = store.createOrUpdateLink(provider().id(),
+ linkDescription);
+ if (event != null) {
+ log.debug("Link {} detected", linkDescription);
+ post(event);
+ }
+ }
+
+ @Override
+ public void linkVanished(LinkDescription linkDescription) {
+ checkNotNull(linkDescription, LINK_DESC_NULL);
+ checkValidity();
+ LinkEvent event = store.removeLink(linkDescription.src(),
+ linkDescription.dst());
+ if (event != null) {
+ log.info("Link {} vanished", linkDescription);
+ post(event);
+ }
+ }
+
+ @Override
+ public void linksVanished(ConnectPoint connectPoint) {
+ checkNotNull(connectPoint, "Connect point cannot be null");
+ checkValidity();
+ log.info("Links for connection point {} vanished", connectPoint);
+ removeLinks(getLinks(connectPoint));
+ }
+
+ @Override
+ public void linksVanished(DeviceId deviceId) {
+ checkNotNull(deviceId, DEVICE_ID_NULL);
+ checkValidity();
+ log.info("Links for device {} vanished", deviceId);
+ removeLinks(getDeviceLinks(deviceId));
+ }
+ }
+
+ // Removes all links in the specified set and emits appropriate events.
+ private void removeLinks(Set<Link> links) {
+ for (Link link : links) {
+ LinkEvent event = store.removeLink(link.src(), link.dst());
+ post(event);
+ }
+ }
+
+ // Posts the specified event to the local event dispatcher.
+ private void post(LinkEvent event) {
+ if (event != null) {
+ eventDispatcher.post(event);
+ }
+ }
+
+}
diff --git a/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkStore.java b/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkStore.java
new file mode 100644
index 0000000..2ba7a30
--- /dev/null
+++ b/core/trivial/src/main/java/org/onlab/onos/net/trivial/link/impl/SimpleLinkStore.java
@@ -0,0 +1,218 @@
+package org.onlab.onos.net.trivial.link.impl;
+
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Multimap;
+import org.onlab.onos.net.ConnectPoint;
+import org.onlab.onos.net.DefaultLink;
+import org.onlab.onos.net.DeviceId;
+import org.onlab.onos.net.Link;
+import org.onlab.onos.net.link.LinkDescription;
+import org.onlab.onos.net.link.LinkEvent;
+import org.onlab.onos.net.provider.ProviderId;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.onlab.onos.net.Link.Type.DIRECT;
+import static org.onlab.onos.net.Link.Type.INDIRECT;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_REMOVED;
+import static org.onlab.onos.net.link.LinkEvent.Type.LINK_UPDATED;
+
+/**
+ * Manages inventory of infrastructure links using trivial in-memory structures
+ * implementation.
+ */
+class SimpleLinkStore {
+
+ // Link inventory
+ private final Map<LinkKey, DefaultLink> links = new ConcurrentHashMap<>();
+
+ // Egress and ingress link sets
+ private final Multimap<DeviceId, Link> srcLinks = HashMultimap.create();
+ private final Multimap<DeviceId, Link> dstLinks = HashMultimap.create();
+
+ private static final Set<Link> EMPTY = ImmutableSet.copyOf(new Link[]{});
+
+ /**
+ * Returns the number of links in the store.
+ *
+ * @return number of links
+ */
+ int getLinkCount() {
+ return links.size();
+ }
+
+ /**
+ * Returns an iterable collection of all links in the inventory.
+ *
+ * @return collection of all links
+ */
+ Iterable<Link> getLinks() {
+ return Collections.unmodifiableSet(new HashSet<Link>(links.values()));
+ }
+
+ /**
+ * Returns all links egressing from the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device links
+ */
+ Set<Link> getDeviceEgressLinks(DeviceId deviceId) {
+ return ImmutableSet.copyOf(srcLinks.get(deviceId));
+ }
+
+ /**
+ * Returns all links ingressing from the specified device.
+ *
+ * @param deviceId device identifier
+ * @return set of device links
+ */
+ Set<Link> getDeviceIngressLinks(DeviceId deviceId) {
+ return ImmutableSet.copyOf(dstLinks.get(deviceId));
+ }
+
+ /**
+ * Returns the link between the two end-points.
+ *
+ * @param src source connection point
+ * @param dst destination connection point
+ * @return link or null if one not found between the end-points
+ */
+ Link getLink(ConnectPoint src, ConnectPoint dst) {
+ return links.get(new LinkKey(src, dst));
+ }
+
+ /**
+ * Returns all links egressing from the specified connection point.
+ *
+ * @param src source connection point
+ * @return set of connection point links
+ */
+ Set<Link> getEgressLinks(ConnectPoint src) {
+ Set<Link> egress = new HashSet<>();
+ for (Link link : srcLinks.get(src.deviceId())) {
+ if (link.src().equals(src)) {
+ egress.add(link);
+ }
+ }
+ return egress;
+ }
+
+ /**
+ * Returns all links ingressing to the specified connection point.
+ *
+ * @param dst destination connection point
+ * @return set of connection point links
+ */
+ Set<Link> getIngressLinks(ConnectPoint dst) {
+ Set<Link> ingress = new HashSet<>();
+ for (Link link : dstLinks.get(dst.deviceId())) {
+ if (link.dst().equals(dst)) {
+ ingress.add(link);
+ }
+ }
+ return ingress;
+ }
+
+ /**
+ * Creates a new link, or updates an existing one, based on the given
+ * information.
+ *
+ * @param providerId provider identity
+ * @param linkDescription link description
+ * @return create or update link event, or null if no change resulted
+ */
+ public LinkEvent createOrUpdateLink(ProviderId providerId,
+ LinkDescription linkDescription) {
+ LinkKey key = new LinkKey(linkDescription.src(), linkDescription.dst());
+ DefaultLink link = links.get(key);
+ if (link == null) {
+ return createLink(providerId, key, linkDescription);
+ }
+ return updateLink(providerId, link, key, linkDescription);
+ }
+
+ // Creates and stores the link and returns the appropriate event.
+ private LinkEvent createLink(ProviderId providerId, LinkKey key,
+ LinkDescription linkDescription) {
+ DefaultLink link = new DefaultLink(providerId, key.src, key.dst,
+ linkDescription.type());
+ synchronized (this) {
+ links.put(key, link);
+ srcLinks.put(link.src().deviceId(), link);
+ dstLinks.put(link.dst().deviceId(), link);
+ }
+ return new LinkEvent(LINK_ADDED, link);
+ }
+
+ // Updates, if necessary the specified link and returns the appropriate event.
+ private LinkEvent updateLink(ProviderId providerId, DefaultLink link,
+ LinkKey key, LinkDescription linkDescription) {
+ if (link.type() == INDIRECT && linkDescription.type() == DIRECT) {
+ synchronized (this) {
+ srcLinks.remove(link.src().deviceId(), link);
+ dstLinks.remove(link.dst().deviceId(), link);
+
+ DefaultLink updated =
+ new DefaultLink(providerId, link.src(), link.dst(),
+ linkDescription.type());
+ links.put(key, updated);
+ srcLinks.put(link.src().deviceId(), updated);
+ dstLinks.put(link.dst().deviceId(), updated);
+ return new LinkEvent(LINK_UPDATED, updated);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Removes the link based on the specified information.
+ *
+ * @param src link source
+ * @param dst link destination
+ * @return remove link event, or null if no change resulted
+ */
+ LinkEvent removeLink(ConnectPoint src, ConnectPoint dst) {
+ synchronized (this) {
+ Link link = links.remove(new LinkKey(src, dst));
+ if (link != null) {
+ srcLinks.remove(link.src().deviceId(), link);
+ dstLinks.remove(link.dst().deviceId(), link);
+ return new LinkEvent(LINK_REMOVED, link);
+ }
+ return null;
+ }
+ }
+
+ // Auxiliary key to track links.
+ private class LinkKey {
+ final ConnectPoint src;
+ final ConnectPoint dst;
+
+ LinkKey(ConnectPoint src, ConnectPoint dst) {
+ this.src = src;
+ this.dst = dst;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(src, dst);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (obj instanceof LinkKey) {
+ final LinkKey other = (LinkKey) obj;
+ return Objects.equals(this.src, other.src) &&
+ Objects.equals(this.dst, other.dst);
+ }
+ return false;
+ }
+ }
+}