Merge remote-tracking branch 'teamforge/master'
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c7ccd3b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Created by https://www.gitignore.io
+
+### Maven ###
+target/
+pom.xml.tag
+pom.xml.releaseBackup
+pom.xml.versionsBackup
+pom.xml.next
+release.properties
+
+
+### Java ###
+*.class
+
+# Mobile Tools for Java (J2ME)
+.mtj.tmp/
+
+# Package Files #
+*.jar
+*.war
+*.ear
+
+# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
+hs_err_pid*
+
+# Linux temporary files
+*~
diff --git a/nb-configuration.xml b/nb-configuration.xml
new file mode 100644
index 0000000..38ca257
--- /dev/null
+++ b/nb-configuration.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project-shared-configuration>
+ <!--
+This file contains additional configuration written by modules in the NetBeans IDE.
+The configuration is intended to be shared among all the users of project and
+therefore it is assumed to be part of version control checkout.
+Without this configuration present, some functionality in the IDE may be limited or fail altogether.
+-->
+ <properties xmlns="http://www.netbeans.org/ns/maven-properties-data/1">
+ <!--
+Properties that influence various parts of the IDE, especially code formatting and the like.
+You can copy and paste the single properties, into the pom.xml file and the IDE will pick them up.
+That way multiple projects can share the same settings (useful for formatting rules for example).
+Any value defined here will override the pom.xml file value but is only applicable to the current project.
+-->
+ <org-netbeans-modules-editor-indent.CodeStyle.usedProfile>project</org-netbeans-modules-editor-indent.CodeStyle.usedProfile>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>4</org-netbeans-modules-editor-indent.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap>
+ <org-netbeans-modules-editor-indent.text.x-fortran.CodeStyle.project.text-limit-width>132</org-netbeans-modules-editor-indent.text.x-fortran.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.spaces-per-tab>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>4</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.tab-size>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>2</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.indent-shift-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>true</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.expand-tabs>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>80</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-limit-width>
+ <org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>none</org-netbeans-modules-editor-indent.text.x-java.CodeStyle.project.text-line-wrap>
+ </properties>
+</project-shared-configuration>
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..1ac549e
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <groupId>com.intel.jndn.mock</groupId>
+ <artifactId>jndn-mock</artifactId>
+ <version>0.9.1</version>
+ <packaging>jar</packaging>
+ <description>Tools for testing NDN Java code without using network IO</description>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.7</maven.compiler.source>
+ <maven.compiler.target>1.7</maven.compiler.target>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>net.named_data.jndn</groupId>
+ <artifactId>jndn</artifactId>
+ <version>RELEASE</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <version>2.1</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>2.1</version>
+ </dependency>
+ <!-- Test dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.10</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <repositories>
+ <repository>
+ <id>ubit-artifactory-or.intel.com</id>
+ <name>IT Artifactory (libs)</name>
+ <url>https://ubit-artifactory-or.intel.com/artifactory/libs-release-local</url>
+ </repository>
+ </repositories>
+ <distributionManagement>
+ <repository>
+ <id>ubit-artifactory-or.intel.com</id>
+ <name>ubit-artifactory-or.intel.com-releases</name>
+ <url>https://ubit-artifactory-or.intel.com/artifactory/libs-release-local</url>
+ </repository>
+ <snapshotRepository>
+ <id>ubit-artifactory-or.intel.com</id>
+ <name>ubit-artifactory-or.intel.com-snapshots</name>
+ <url>https://ubit-artifactory-or.intel.com/artifactory/libs-snapshot-local</url>
+ </snapshotRepository>
+ </distributionManagement>
+</project>
\ No newline at end of file
diff --git a/src/main/java/com/intel/jndn/mock/MockFace.java b/src/main/java/com/intel/jndn/mock/MockFace.java
new file mode 100644
index 0000000..8fb1f39
--- /dev/null
+++ b/src/main/java/com/intel/jndn/mock/MockFace.java
@@ -0,0 +1,572 @@
+/*
+ * File name: MockTransport.java
+ *
+ * Purpose: Use the MockTransport to mock sending data over the network.
+ *
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.mock;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map.Entry;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.ForwardingFlags;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.Node;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnInterest;
+import net.named_data.jndn.OnRegisterFailed;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.encoding.WireFormat;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class MockFace {
+
+ private static final Logger logger = LogManager.getLogger();
+ private final Node node_;
+ HashMap<String, Data> responseMap = new HashMap<>();
+ HashMap<Long, MockOnInterestHandler> handlerMap = new HashMap<>();
+ long lastRegisteredId = 0;
+
+ /**
+ * Create a new Face to mock communication over the network; all packets are
+ * maintained in memory
+ */
+ public MockFace() {
+ node_ = new Node(new MockTransport(), null);
+ }
+
+ /**
+ * Add a response Data packet to send immediately when an Interest with a
+ * matching name is received; will continue to respond with the same packet
+ * over multiple requests. This will preempt any registered OnInterest
+ * handlers.
+ *
+ * @param name
+ * @param data
+ */
+ public void addResponse(Name name, Data data) {
+ logger.debug("Added response for: " + name.toUri());
+ responseMap.put(name.toUri(), data);
+ }
+
+ /**
+ * Stop sending a response for the given name.
+ *
+ * @param name
+ */
+ public void removeResponse(Name name) {
+ logger.debug("Removed response for: " + name.toUri());
+ responseMap.remove(name);
+ }
+
+ /**
+ * Handle incoming Interest packets; when an Interest is expressed through
+ * expressInterest(), this will run to determine if: 1) any responses have
+ * been registered or 2) if any OnInterest handlers have been registered. If
+ * one of these two succeeds, this method then re-directs the Interest from
+ * traveling down the network stack and returns data.
+ *
+ * @param interest
+ */
+ protected void handleIncomingRequests(Interest interest) {
+ String interestName = interest.getName().toUri();
+ long registeredPrefixId = findRegisteredHandler(interest);
+ // check if response registered
+ if (responseMap.containsKey(interestName)) {
+ logger.debug("Found response for: " + interestName);
+ Data data = responseMap.get(interestName);
+ ((MockTransport) node_.getTransport()).respondWith(data);
+ } // check if handler registered
+ else if (registeredPrefixId != -1) {
+ logger.debug("Found handler for: " + interestName);
+ MockOnInterestHandler handler = handlerMap.get(findRegisteredHandler(interest));
+ handler.onInterest.onInterest(handler.prefix, interest, node_.getTransport(), registeredPrefixId);
+ } // log failure
+ else {
+ logger.warn("No response found for interest (aborting): " + interestName);
+ }
+ }
+
+ /**
+ * Find a handler that matches the incoming interest; currently, the only
+ * flags supported are the ChildInherit flags.
+ *
+ * @param interest
+ * @return
+ */
+ protected long findRegisteredHandler(Interest interest) {
+ for (Entry<Long, MockOnInterestHandler> entry : handlerMap.entrySet()) {
+ MockOnInterestHandler handler = entry.getValue();
+ if (handler.flags.getChildInherit() && handler.prefix.match(interest.getName())) {
+ return entry.getKey();
+ }
+ if (handler.prefix.equals(interest.getName())) {
+ return entry.getKey();
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Helper class for holding references to OnInterest handlers
+ */
+ class MockOnInterestHandler {
+
+ Name prefix;
+ OnInterest onInterest;
+ ForwardingFlags flags;
+
+ public MockOnInterestHandler(Name prefix, OnInterest onInterest, ForwardingFlags flags) {
+ this.prefix = prefix;
+ this.onInterest = onInterest;
+ this.flags = flags;
+ }
+ }
+
+ /**
+ * Send the Interest through the transport, read the entire response and call
+ * onData(interest, data).
+ *
+ * @param interest The Interest to send. This copies the Interest.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Interest interest, OnData onData, OnTimeout onTimeout,
+ WireFormat wireFormat) throws IOException {
+ long id = node_.expressInterest(interest, onData, onTimeout, wireFormat);
+ handleIncomingRequests(interest);
+ return id;
+ }
+
+ /**
+ * Send the Interest through the transport, read the entire response and call
+ * onData(interest, data). This uses the default
+ * WireFormat.getDefaultWireFormat().
+ *
+ * @param interest The Interest to send. This copies the Interest.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Interest interest, OnData onData, OnTimeout onTimeout) throws IOException {
+ return expressInterest(interest, onData, onTimeout, WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Send the Interest through the transport, read the entire response and call
+ * onData(interest, data). Ignore if the interest times out.
+ *
+ * @param interest The Interest to send. This copies the Interest.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Interest interest, OnData onData, WireFormat wireFormat) throws IOException {
+ return expressInterest(interest, onData, null, wireFormat);
+ }
+
+ /**
+ * Send the Interest through the transport, read the entire response and call
+ * onData(interest, data). Ignore if the interest times out. This uses the
+ * default WireFormat.getDefaultWireFormat().
+ *
+ * @param interest The Interest to send. This copies the Interest.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Interest interest, OnData onData) throws IOException {
+ return expressInterest(interest, onData, null, WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data).
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param interestTemplate If not null, copy interest selectors from the
+ * template. This does not keep a pointer to the Interest object.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, Interest interestTemplate, OnData onData, OnTimeout onTimeout,
+ WireFormat wireFormat) throws IOException {
+ Interest interest = new Interest(name);
+ if (interestTemplate != null) {
+ interest.setMinSuffixComponents(interestTemplate.getMinSuffixComponents());
+ interest.setMaxSuffixComponents(interestTemplate.getMaxSuffixComponents());
+ interest.setKeyLocator(interestTemplate.getKeyLocator());
+ interest.setExclude(interestTemplate.getExclude());
+ interest.setChildSelector(interestTemplate.getChildSelector());
+ interest.setMustBeFresh(interestTemplate.getMustBeFresh());
+ interest.setScope(interestTemplate.getScope());
+ interest.setInterestLifetimeMilliseconds(
+ interestTemplate.getInterestLifetimeMilliseconds());
+ // Don't copy the nonce.
+ } else {
+ interest.setInterestLifetimeMilliseconds(4000.0);
+ }
+
+ return expressInterest(interest, onData, onTimeout, wireFormat);
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Use a default interest
+ * lifetime.
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, OnData onData, OnTimeout onTimeout,
+ WireFormat wireFormat) throws IOException {
+ return expressInterest(name, null, onData, onTimeout, wireFormat);
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Ignore if the interest
+ * times out.
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param interestTemplate If not null, copy interest selectors from the
+ * template. This does not keep a pointer to the Interest object.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, Interest interestTemplate, OnData onData,
+ WireFormat wireFormat) throws IOException {
+ return expressInterest(name, interestTemplate, onData, null, wireFormat);
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). This uses the default
+ * WireFormat.getDefaultWireFormat().
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param interestTemplate If not null, copy interest selectors from the
+ * template. This does not keep a pointer to the Interest object.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, Interest interestTemplate, OnData onData,
+ OnTimeout onTimeout) throws IOException {
+ return expressInterest(name, interestTemplate, onData, onTimeout,
+ WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Ignore if the interest
+ * times out. This uses the default WireFormat.getDefaultWireFormat().
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param interestTemplate If not null, copy interest selectors from the
+ * template. This does not keep a pointer to the Interest object.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, Interest interestTemplate, OnData onData) throws IOException {
+ return expressInterest(name, interestTemplate, onData, null, WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Use a default interest
+ * lifetime. This uses the default WireFormat.getDefaultWireFormat().
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param onTimeout If the interest times out according to the interest
+ * lifetime, this calls onTimeout.onTimeout(interest) where interest is the
+ * interest given to expressInterest. If onTimeout is null, this does not use
+ * it.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, OnData onData, OnTimeout onTimeout) throws IOException {
+ return expressInterest(name, null, onData, onTimeout, WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Use a default interest
+ * lifetime. Ignore if the interest times out.
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, OnData onData, WireFormat wireFormat) throws IOException {
+ return expressInterest(name, null, onData, null, wireFormat);
+ }
+
+ /**
+ * Encode name as an Interest. If interestTemplate is not null, use its
+ * interest selectors. Send the interest through the transport, read the
+ * entire response and call onData(interest, data). Use a default interest
+ * lifetime. Ignore if the interest times out. This uses the default
+ * WireFormat.getDefaultWireFormat().
+ *
+ * @param name A Name for the interest. This copies the Name.
+ * @param onData When a matching data packet is received, this calls
+ * onData.onData(interest, data) where interest is the interest given to
+ * expressInterest and data is the received Data object. NOTE: You must not
+ * change the interest object - if you need to change it then make a copy.
+ * @return The pending interest ID which can be used with
+ * removePendingInterest.
+ * @throws IOException For I/O error in sending the interest.
+ */
+ public long expressInterest(Name name, OnData onData) throws IOException {
+ return expressInterest(name, null, onData, null, WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Remove the pending interest entry with the pendingInterestId from the
+ * pending interest table. This does not affect another pending interest with
+ * a different pendingInterestId, even if it has the same interest name. If
+ * there is no entry with the pendingInterestId, do nothing.
+ *
+ * @param pendingInterestId The ID returned from expressInterest.
+ */
+ public void removePendingInterest(long pendingInterestId) {
+ node_.removePendingInterest(pendingInterestId);
+ }
+
+ /**
+ * Register prefix with the connected NDN hub and call onInterest when a
+ * matching interest is received. If you have not called
+ * setCommandSigningInfo, this assumes you are connecting to NDNx. If you have
+ * called setCommandSigningInfo, this first sends an NFD registration request,
+ * and if that times out then this sends an NDNx registration request. If you
+ * need to register a prefix with NFD, you must first call
+ * setCommandSigningInfo.
+ *
+ * @param prefix A Name for the prefix to register. This copies the Name.
+ * @param onInterest When an interest is received which matches the name
+ * prefix, this calls onInterest.onInterest(prefix, interest, transport,
+ * registeredPrefixId). NOTE: You must not change the prefix object - if you
+ * need to change it then make a copy.
+ * @param onRegisterFailed If register prefix fails for any reason, this calls
+ * onRegisterFailed.onRegisterFailed(prefix).
+ * @param flags The flags for finer control of which interests are forwarded
+ * to the application.
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The lastRegisteredId prefix ID which can be used with
+ * removeRegisteredPrefix.
+ * @throws IOException For I/O error in sending the registration request.
+ * @throws SecurityException If signing a command interest for NFD and cannot
+ * find the private key for the certificateName.
+ */
+ public long registerPrefix(Name prefix, OnInterest onInterest, OnRegisterFailed onRegisterFailed,
+ ForwardingFlags flags, WireFormat wireFormat) throws IOException, net.named_data.jndn.security.SecurityException {
+ lastRegisteredId++;
+ handlerMap.put(lastRegisteredId, new MockOnInterestHandler(prefix, onInterest, flags));
+ return lastRegisteredId;
+ }
+
+ /**
+ * Register prefix with the connected NDN hub and call onInterest when a
+ * matching interest is received. This uses the default
+ * WireFormat.getDefaultWireFormat().
+ *
+ * @param prefix A Name for the prefix to register. This copies the Name.
+ * @param onInterest When an interest is received which matches the name
+ * prefix, this calls onInterest.onInterest(prefix, interest, transport,
+ * registeredPrefixId). NOTE: You must not change the prefix object - if you
+ * need to change it then make a copy.
+ * @param onRegisterFailed If register prefix fails for any reason, this calls
+ * onRegisterFailed.onRegisterFailed(prefix).
+ * @param flags The flags for finer control of which interests are forwarded
+ * to the application.
+ * @return The lastRegisteredId prefix ID which can be used with
+ * removeRegisteredPrefix.
+ * @throws IOException For I/O error in sending the registration request.
+ */
+ public long registerPrefix(Name prefix, OnInterest onInterest, OnRegisterFailed onRegisterFailed,
+ ForwardingFlags flags) throws IOException, net.named_data.jndn.security.SecurityException {
+ return registerPrefix(prefix, onInterest, onRegisterFailed, flags,
+ WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Register prefix with the connected NDN hub and call onInterest when a
+ * matching interest is received. Use default ForwardingFlags.
+ *
+ * @param prefix A Name for the prefix to register. This copies the Name.
+ * @param onInterest When an interest is received which matches the name
+ * prefix, this calls onInterest.onInterest(prefix, interest, transport,
+ * registeredPrefixId). NOTE: You must not change the prefix object - if you
+ * need to change it then make a copy.
+ * @param onRegisterFailed If register prefix fails for any reason, this calls
+ * onRegisterFailed.onRegisterFailed(prefix).
+ * @param wireFormat A WireFormat object used to encode the message.
+ * @return The lastRegisteredId prefix ID which can be used with
+ * removeRegisteredPrefix.
+ * @throws IOException For I/O error in sending the registration request.
+ * @throws SecurityException If signing a command interest for NFD and cannot
+ * find the private key for the certificateName.
+ */
+ public long registerPrefix(Name prefix, OnInterest onInterest, OnRegisterFailed onRegisterFailed,
+ WireFormat wireFormat) throws IOException, net.named_data.jndn.security.SecurityException {
+ return registerPrefix(prefix, onInterest, onRegisterFailed, new ForwardingFlags(), wireFormat);
+ }
+
+ /**
+ * Register prefix with the connected NDN hub and call onInterest when a
+ * matching interest is received. This uses the default
+ * WireFormat.getDefaultWireFormat(). Use default ForwardingFlags.
+ *
+ * @param prefix A Name for the prefix to register. This copies the Name.
+ * @param onInterest When an interest is received which matches the name
+ * prefix, this calls onInterest.onInterest(prefix, interest, transport,
+ * registeredPrefixId). NOTE: You must not change the prefix object - if you
+ * need to change it then make a copy.
+ * @param onRegisterFailed If register prefix fails for any reason, this calls
+ * onRegisterFailed.onRegisterFailed(prefix).
+ * @return The lastRegisteredId prefix ID which can be used with
+ * removeRegisteredPrefix.
+ * @throws IOException For I/O error in sending the registration request.
+ * @throws SecurityException If signing a command interest for NFD and cannot
+ * find the private key for the certificateName.
+ */
+ public long registerPrefix(Name prefix, OnInterest onInterest,
+ OnRegisterFailed onRegisterFailed) throws IOException, net.named_data.jndn.security.SecurityException {
+ return registerPrefix(prefix, onInterest, onRegisterFailed, new ForwardingFlags(),
+ WireFormat.getDefaultWireFormat());
+ }
+
+ /**
+ * Remove the lastRegisteredId prefix entry with the registeredPrefixId from
+ * the lastRegisteredId prefix table. This does not affect another
+ * lastRegisteredId prefix with a different registeredPrefixId, even if it has
+ * the same prefix name. If there is no entry with the registeredPrefixId, do
+ * nothing.
+ *
+ * @param registeredPrefixId The ID returned from registerPrefix.
+ */
+ public void removeRegisteredPrefix(long registeredPrefixId) {
+ handlerMap.remove(registeredPrefixId);
+ }
+
+ /**
+ * Process any packets to receive and call callbacks such as onData,
+ * onInterest or onTimeout. This returns immediately if there is no data to
+ * receive. This blocks while calling the callbacks. You should repeatedly
+ * call this from an event loop, with calls to sleep as needed so that the
+ * loop doesn’t use 100% of the CPU. Since processEvents modifies the pending
+ * interest table, your application should make sure that it calls
+ * processEvents in the same thread as expressInterest (which also modifies
+ * the pending interest table). This may throw an exception for reading data
+ * or in the callback for processing the data. If you call this from an main
+ * event loop, you may want to catch and log/disregard all exceptions.
+ */
+ public void processEvents() throws IOException, EncodingException {
+ // Just call Node's processEvents.
+ node_.processEvents();
+ }
+
+ /**
+ * Shut down and disconnect this Face.
+ */
+ public void shutdown() {
+ node_.shutdown();
+ }
+}
diff --git a/src/main/java/com/intel/jndn/mock/MockTransport.java b/src/main/java/com/intel/jndn/mock/MockTransport.java
new file mode 100644
index 0000000..0704f94
--- /dev/null
+++ b/src/main/java/com/intel/jndn/mock/MockTransport.java
@@ -0,0 +1,234 @@
+/*
+ * File name: MockTransport.java
+ *
+ * Purpose: Provide testing functionality for running NDN unit tests without
+ * connecting to the network or requiring an NFD installed.
+ *
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.mock;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.encoding.ElementListener;
+import net.named_data.jndn.encoding.ElementReader;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.transport.Transport;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+/**
+ * Mock the transport class Example: ...
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class MockTransport extends Transport {
+
+ public final static int BUFFER_CAPACITY = 2000;
+ private static final Logger logger = LogManager.getLogger();
+ protected boolean connected;
+ protected ElementReader elementReader;
+ protected ByteBuffer buffer = ByteBuffer.allocate(BUFFER_CAPACITY);
+ protected ByteBuffer sentBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
+ protected List<Data> sentDataPackets = new ArrayList<>();
+ protected List<Interest> sentInterestPackets = new ArrayList<>();
+
+ /**
+ * Place data in the receive queue; when processEvents() is called, the
+ * calling application will receive these bytes.
+ *
+ * @param response
+ */
+ public void respondWith(ByteBuffer response) {
+ buffer.put(response);
+ }
+
+ /**
+ * Place data in the receive queue; when processEvents() is called, the
+ * calling application will receive this packet.
+ *
+ * @param response
+ */
+ public void respondWith(Data response) {
+ respondWith(response.wireEncode().buf());
+ }
+
+ /**
+ * Place data in the receive queue; when processEvents() is called, the
+ * calling application will receive this packet.
+ *
+ * @param request
+ */
+ public void respondWith(Interest request) {
+ respondWith(request.wireEncode().buf());
+ }
+
+ /**
+ * Inspect the bytes sent using this transport.
+ *
+ * @return
+ */
+ public ByteBuffer getSentBuffer() {
+ return sentBuffer;
+ }
+
+ /**
+ * Inspect the list of data packets sent using this transport; the alternative
+ * is to inspect getSentBuffer().
+ *
+ * @return
+ */
+ public List<Data> getSentDataPackets() {
+ return sentDataPackets;
+ }
+
+ /**
+ * Inspect the list of interest packets sent using this transport; the
+ * alternative is to inspect getSentBuffer().
+ *
+ * @return
+ */
+ public List<Interest> getSentInterestPackets() {
+ return sentInterestPackets;
+ }
+
+ /**
+ * Clear all sent and to-be-received data
+ */
+ public void clear() {
+ buffer = ByteBuffer.allocate(BUFFER_CAPACITY);
+ sentBuffer = ByteBuffer.allocate(BUFFER_CAPACITY);
+ sentDataPackets.clear();
+ sentInterestPackets.clear();
+ }
+
+ /**
+ * Mock the connection startup; calls this processEvents().
+ *
+ * @param connectionInfo
+ * @param elementListener
+ * @throws IOException
+ */
+ @Override
+ public void connect(Transport.ConnectionInfo connectionInfo,
+ ElementListener elementListener) throws IOException {
+ logger.debug("Connecting...");
+ connected = true;
+ elementReader = new ElementReader(elementListener);
+ }
+
+ /**
+ * Mock sending data to the host; access the data as bytes using
+ * getSentBuffer() or as packets with getSentDataPackets().
+ *
+ * @param data The buffer of data to send. This reads from position() to
+ * limit(), but does not change the position.
+ * @throws IOException For I/O error.
+ */
+ @Override
+ public void send(ByteBuffer data) throws IOException {
+ logger.debug("Sending " + (data.capacity() - data.position()) + " bytes");
+
+ // add to sent bytes
+ buffer.put(data);
+ data.flip();
+ sentBuffer.put(data);
+ data.flip();
+
+ // add to sent packets
+ byte first = data.get();
+ if (first == 5) {
+ data.position(0);
+ addSentInterest(data);
+ } else if (first == 6) {
+ data.position(0);
+ addSentData(data);
+ } else {
+ logger.warn("Unknown TLV packet type; cannot parse.");
+ }
+ }
+
+ /**
+ * Helper method to parse Data packets.
+ *
+ * @param data
+ */
+ protected void addSentData(ByteBuffer data) {
+ Data packet = new Data();
+ try {
+ packet.wireDecode(data);
+ sentDataPackets.add(packet);
+ } catch (EncodingException e) {
+ logger.warn("Failed to parse bytes into a data packet");
+ }
+ }
+
+ /**
+ * Helper method to parse Interest packets.
+ *
+ * @param data
+ */
+ protected void addSentInterest(ByteBuffer data) {
+ Interest packet = new Interest();
+ try {
+ packet.wireDecode(data);
+ sentInterestPackets.add(packet);
+ } catch (EncodingException e) {
+ logger.warn("Failed to parse bytes into an interest packet");
+ }
+ }
+
+ /**
+ * Process any data to receive and clear the input buffer; to mock incoming
+ * Data packets, add data to the buffer with respondWith().
+ *
+ * @throws IOException For I/O error.
+ * @throws EncodingException For invalid encoding.
+ */
+ @Override
+ public void processEvents() throws IOException, EncodingException {
+ if (!getIsConnected()) {
+ logger.warn("Not connnected, aborting...");
+ return;
+ }
+
+ // trace data sent
+ logger.trace(String.format("Processing buffer (position: %s, limit: %s, capacity: %s): %s", buffer.position(), buffer.limit(), buffer.capacity(), Arrays.toString(buffer.array())));
+
+ // pass data up to face
+ buffer.limit(buffer.position());
+ buffer.position(0);
+ elementReader.onReceivedData(buffer);
+
+ // reset buffer
+ buffer = ByteBuffer.allocate(BUFFER_CAPACITY);
+ }
+
+ /**
+ * Check if the transport is connected.
+ *
+ * @return true if connected.
+ */
+ @Override
+ public boolean getIsConnected() {
+ return connected;
+ }
+
+ /**
+ * Close the connection.
+ *
+ * @throws IOException For I/O error.
+ */
+ @Override
+ public void close() throws IOException {
+ logger.debug("Closing...");
+ connected = false;
+ }
+}
diff --git a/src/main/resources/log4j2-test.xml b/src/main/resources/log4j2-test.xml
new file mode 100644
index 0000000..602b5ab
--- /dev/null
+++ b/src/main/resources/log4j2-test.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..0dac33a
--- /dev/null
+++ b/src/main/resources/log4j2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="warn">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/src/test/java/com/intel/jndn/mock/MockFaceTest.java b/src/test/java/com/intel/jndn/mock/MockFaceTest.java
new file mode 100644
index 0000000..66db3a2
--- /dev/null
+++ b/src/test/java/com/intel/jndn/mock/MockFaceTest.java
@@ -0,0 +1,133 @@
+/*
+ * To change this license header, choose License Headers in Project Properties.
+ * To change this template file, choose Tools | Templates
+ * and open the template in the editor.
+ */
+package com.intel.jndn.mock;
+
+import java.io.IOException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnInterest;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.transport.Transport;
+import net.named_data.jndn.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class MockFaceTest {
+
+ /**
+ * Setup logging
+ */
+ private static final Logger logger = LogManager.getLogger();
+
+ /**
+ * Test setting responses for specific names
+ * @throws java.io.IOException
+ * @throws net.named_data.jndn.encoding.EncodingException
+ */
+ @Test
+ public void testWithResponses() throws IOException, EncodingException {
+ MockFace face = new MockFace();
+
+ // add response
+ Data response = new Data(new Name("/test/with/responses"));
+ response.setContent(new Blob("..."));
+ face.addResponse(new Name("/test/with/responses"), response);
+
+ // make request
+ final Counter count = new Counter();
+ logger.info("Express interest: /test/with/responses");
+ face.expressInterest(new Interest(new Name("/test/with/responses")), new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ count.inc();
+ logger.debug("Received data");
+ assertEquals(data.getContent().buf(), new Blob("...").buf());
+ }
+ });
+
+ // process face until a response is received
+ int allowedLoops = 100;
+ while (count.get() == 0 && allowedLoops > 0) {
+ allowedLoops--;
+ face.processEvents();
+ }
+ assertEquals(1, count.get());
+ }
+
+ /**
+ * Test serving data dynamically with OnInterest handlers
+ *
+ * @throws net.named_data.jndn.encoding.EncodingException
+ * @throws java.io.IOException
+ * @throws net.named_data.jndn.security.SecurityException
+ */
+ @Test
+ public void testWithHandlers() throws EncodingException, IOException, net.named_data.jndn.security.SecurityException {
+ MockFace face = new MockFace();
+
+ // add interest handler
+ logger.info("Register prefix: /test/with/responses");
+ face.registerPrefix(new Name("/test/with/handlers"), new OnInterest() {
+ @Override
+ public void onInterest(Name prefix, Interest interest, Transport transport, long registeredPrefixId) {
+ logger.debug("Received interest, responding: " + interest.getName().toUri());
+ Data response = new Data(new Name("/test/with/handlers"));
+ response.setContent(new Blob("..."));
+ try {
+ transport.send(response.wireEncode().buf());
+ } catch (IOException e) {
+ fail("Failed to send encoded data packet.");
+ }
+ }
+ }, null);
+
+ // make request
+ final Counter count = new Counter();
+ logger.info("Express interest: /test/with/responses");
+ face.expressInterest(new Interest(new Name("/test/with/handlers")), new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ count.inc();
+ logger.debug("Received data");
+ assertEquals(data.getContent().buf(), new Blob("...").buf());
+ }
+ });
+
+ // process faces until a response is received
+ int allowedLoops = 100;
+ while (count.get() == 0 && allowedLoops > 0) {
+ allowedLoops--;
+ face.processEvents();
+ }
+ assertEquals(1, count.get());
+ }
+
+ // TODO add childInherit test
+
+ /**
+ * Count reference
+ */
+ class Counter {
+
+ int count = 0;
+
+ public void inc() {
+ count++;
+ }
+
+ public int get() {
+ return count;
+ }
+ }
+}
diff --git a/src/test/java/com/intel/jndn/mock/MockTransportTest.java b/src/test/java/com/intel/jndn/mock/MockTransportTest.java
new file mode 100644
index 0000000..44ad258
--- /dev/null
+++ b/src/test/java/com/intel/jndn/mock/MockTransportTest.java
@@ -0,0 +1,159 @@
+/*
+ * File name: MockTransportTest.java
+ *
+ * Purpose: Test the MockTransport
+ *
+ * © Copyright Intel Corporation. All rights reserved.
+ * Intel Corporation, 2200 Mission College Boulevard,
+ * Santa Clara, CA 95052-8119, USA
+ */
+package com.intel.jndn.mock;
+
+import java.io.IOException;
+import net.named_data.jndn.Data;
+import net.named_data.jndn.Face;
+import net.named_data.jndn.Interest;
+import net.named_data.jndn.Name;
+import net.named_data.jndn.OnData;
+import net.named_data.jndn.OnTimeout;
+import net.named_data.jndn.encoding.EncodingException;
+import net.named_data.jndn.util.Blob;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * Mock the transport class TODO add face.registerPrefix() example
+ *
+ * @author Andrew Brown <andrew.brown@intel.com>
+ */
+public class MockTransportTest {
+
+ /**
+ * Setup logging
+ */
+ private static final Logger logger = LogManager.getLogger();
+
+ /**
+ * Test sending a Data packet.
+ *
+ * @throws java.io.IOException
+ * @throws net.named_data.jndn.encoding.EncodingException
+ */
+ @Test
+ public void testSendData() throws IOException, EncodingException {
+ MockTransport transport = new MockTransport();
+ Face face = new Face(transport, null);
+
+ // setup return data
+ Data response = new Data(new Name("/a/b/c"));
+ response.setContent(new Blob("..."));
+ transport.respondWith(response);
+
+ // express interest on the face
+ final Counter count = new Counter();
+ face.expressInterest(new Interest(new Name("/a/b/c")), new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ count.inc();
+ logger.debug("Received data");
+ assertEquals(data.getContent().buf(), new Blob("...").buf());
+ }
+ });
+
+ // process the face until one response
+ while (count.get() == 0) {
+ face.processEvents();
+ }
+
+ // check for sent packets
+ assertEquals(0, transport.getSentDataPackets().size());
+ assertEquals(1, transport.getSentInterestPackets().size());
+ }
+
+ /**
+ * Test sending multiple Data packets.
+ *
+ * @throws java.io.IOException
+ * @throws net.named_data.jndn.encoding.EncodingException
+ */
+ @Test
+ public void testSendMultipleData() throws IOException, EncodingException {
+ MockTransport transport = new MockTransport();
+ Face face = new Face(transport, null);
+
+ // setup return data
+ Data response1 = new Data(new Name("/a/b/c/1"));
+ response1.setContent(new Blob("..."));
+ transport.respondWith(response1);
+ Data response2 = new Data(new Name("/a/b/c/2"));
+ response2.setContent(new Blob("..."));
+ transport.respondWith(response2);
+
+ // express interest on the face
+ final Counter count = new Counter();
+ face.expressInterest(new Interest(new Name("/a/b/c/1")), new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ count.inc();
+ logger.debug("Received data");
+ assertEquals(data.getContent().buf(), new Blob("...").buf());
+ }
+ });
+
+ // process the face until one response received
+ while (count.get() == 0) {
+ face.processEvents();
+ }
+
+ // check for sent packets
+ assertEquals(0, transport.getSentDataPackets().size());
+ assertEquals(1, transport.getSentInterestPackets().size());
+
+ // express interest again, but this time it should time out because there
+ // is no data left on the wire; the first processEvents() has already
+ // picked it up
+ final Counter count2 = new Counter();
+ Interest failingInterest = new Interest(new Name("/a/b/c/2"));
+ failingInterest.setInterestLifetimeMilliseconds(50);
+ face.expressInterest(failingInterest, new OnData() {
+ @Override
+ public void onData(Interest interest, Data data) {
+ count2.inc();
+ fail("Should not return data; data should already be cleared");
+ }
+ }, new OnTimeout() {
+ @Override
+ public void onTimeout(Interest interest) {
+ count2.inc();
+ assertTrue(true);
+ }
+ });
+
+ // process the face until timeout
+ while (count2.get() == 0) {
+ face.processEvents();
+ }
+
+ // check for sent packets
+ assertEquals(0, transport.getSentDataPackets().size());
+ assertEquals(2, transport.getSentInterestPackets().size());
+ }
+
+ /**
+ * Count reference
+ */
+ class Counter {
+
+ int count = 0;
+
+ public void inc() {
+ count++;
+ }
+
+ public int get() {
+ return count;
+ }
+ }
+}