Upgrade SegmentedClient to be fully asynchronous (no forced blocking for first packet)
diff --git a/src/main/java/com/intel/jndn/utils/SegmentedClient.java b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
index d9deb3e..9c9d61f 100644
--- a/src/main/java/com/intel/jndn/utils/SegmentedClient.java
+++ b/src/main/java/com/intel/jndn/utils/SegmentedClient.java
@@ -16,16 +16,17 @@
 import com.intel.jndn.utils.client.FutureData;
 import com.intel.jndn.utils.client.SegmentedFutureData;
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeoutException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 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;
 
 /**
@@ -57,11 +58,12 @@
   }
 
   /**
-   * Asynchronously send Interest packets for a segmented result; will block
-   * until the first packet is received and then send remaining interests until
-   * the specified FinalBlockId.
+   * Asynchronously send Interest packets for a segmented result; will not
+   * block, but will wait for the first packet to return before sending
+   * remaining interests until using the specified FinalBlockId. Will retrieve
+   * non-segmented packets as well.
    *
-   * @param face
+   * @param face the {@link Face} on which to retrieve the packets
    * @param interest should include either a ChildSelector or an initial segment
    * number; the initial segment number will be cut off in the de-segmented
    * packet.
@@ -69,19 +71,95 @@
    * will contain one FutureData with the failure exception
    */
   @Override
-  public Future<Data> getAsync(Face face, Interest interest) {
-    List<Future<Data>> segments = getAsyncList(face, interest);
-    return new SegmentedFutureData(interest.getName(), segments);
+  public Future<Data> getAsync(final Face face, Interest interest) {
+    final long firstSegmentId = parseFirstSegmentId(interest);
+    final SegmentedFutureData segmentedData = new SegmentedFutureData(face, interest.getName());
+    final FutureData firstData = new FutureData(face, interest.getName());
+    segmentedData.add(0, firstData);
+
+    // send first interest
+    logger.log(Level.FINER, "Sending first interest for: " + interest.getName().toUri());
+    try {
+      face.expressInterest(interest, new OnData() {
+        @Override
+        public void onData(Interest interest, Data data) {
+          firstData.resolve(data);
+          // now request subsequent segments using FinalBlockId and the Interest template
+          try {
+            long lastSegmentId = parseLastSegmentId(data);
+            Interest template = new Interest(interest);
+            template.setName(removeSegment(data.getName()));
+            requestRemainingSegments(face, segmentedData, template, firstSegmentId + 1, lastSegmentId);
+          } catch (EncodingException ex) {
+            Logger.getLogger(SegmentedClient.class.getName()).log(Level.SEVERE, null, ex);
+          }
+        }
+      }, new OnTimeout() {
+        @Override
+        public void onTimeout(Interest interest) {
+          segmentedData.reject(new TimeoutException());
+        }
+      });
+    } catch (IOException e) {
+      logger.log(Level.FINE, "IO failure while sending interest: ", e);
+      segmentedData.reject(e);
+    }
+
+    return segmentedData;
   }
 
   /**
-   * Asynchronously send Interest packets for a segmented result; will block
-   * until the first packet is received and then send remaining interests until
-   * the specified FinalBlockId.
+   * @param interest the request {@link Interest}
+   * @return the first segment the interest is requesting, or 0 if none found
+   */
+  private long parseFirstSegmentId(Interest interest) {
+    try {
+      return interest.getName().get(-1).toSegment();
+    } catch (EncodingException e) {
+      if (interest.getChildSelector() == -1) {
+        logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
+        // allow this interest to pass without a segment marker since it may still succeed
+      }
+      return 0;
+    }
+  }
+
+  /**
+   * @param firstData the first returned {@link Data}
+   * @return the last segment number available as specified in the FinalBlockId
+   * @throws EncodingException
+   */
+  private long parseLastSegmentId(Data firstData) throws EncodingException {
+    return firstData.getMetaInfo().getFinalBlockId().toSegment();
+  }
+
+  /**
+   * Send interests for remaining segments; adding them to the segmented future
    *
    * @param face
+   * @param segmentedData
+   * @param template
+   * @param fromSegment
+   * @param toSegment
+   */
+  private void requestRemainingSegments(Face face, SegmentedFutureData segmentedData, Interest template, long fromSegment, long toSegment) {
+    // send interests in remaining segments
+    for (long i = fromSegment; i <= toSegment; i++) {
+      Interest segmentedInterest = new Interest(template);
+      segmentedInterest.getName().appendSegment(i);
+      Future<Data> futureData = SimpleClient.getDefault().getAsync(face, segmentedInterest);
+      segmentedData.add((int) i, futureData);
+    }
+  }
+
+  /**
+   * Asynchronously send Interest packets for a segmented result; see {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Interest)
+   * } for more information.
+   *
+   * @param face the {@link Face} on which to retrieve the packets
    * @param name the {@link Name} of the packet to retrieve using a default
-   * interest
+   * interest; may optionally end with the segment component of the first
+   * segment to retrieve
    * @return an aggregated data packet from all received segments
    */
   public Future<Data> getAsync(Face face, Name name) {
@@ -89,85 +167,10 @@
   }
 
   /**
-   * Asynchronously send Interest packets for a segmented result; will block
-   * until the first packet is received and then send remaining interests until
-   * the specified FinalBlockId.
-   *
-   * @param face
-   * @param interest should include either a ChildSelector or an initial segment
-   * number
-   * @return a list of FutureData packets; if the first segment fails, the list
-   * will contain one FutureData with the failure exception
-   */
-  public List<Future<Data>> getAsyncList(Face face, Interest interest) {
-    // get first segment; default 0 or use a specified start segment
-    long firstSegment = 0;
-    try {
-      firstSegment = interest.getName().get(-1).toSegment();
-    } catch (EncodingException e) {
-      // check for interest selector if no initial segment found
-      if (interest.getChildSelector() == -1) {
-        logger.log(Level.WARNING, "No child selector set for a segmented Interest; this may result in incorrect retrieval.");
-        // allow this interest to pass without a segment marker since it may still succeed
-      }
-    }
-
-    // setup segments
-    final List<Future<Data>> segments = new ArrayList<>();
-    segments.add(SimpleClient.getDefault().getAsync(face, interest));
-
-    // retrieve first packet and find the FinalBlockId
-    Data firstData;
-    long lastSegment;
-    try {
-      firstData = segments.get(0).get();
-      lastSegment = firstData.getMetaInfo().getFinalBlockId().toSegment();
-    } catch (ExecutionException | InterruptedException e) {
-      logger.log(Level.FINE, "Failed to retrieve first segment: " + interest.toUri(), e);
-      ((FutureData) segments.get(0)).reject(e); // TODO implies knowledge of underlying data structure
-      return segments;
-    } catch (EncodingException e) {
-      logger.log(Level.FINER, "No FinalBlockId, returning first packet: " + interest.toUri());
-      return segments;
-    }
-
-    // set follow-on segment names to match first segment retrieve
-    Interest interestCopy = new Interest(interest);
-    if (hasSegment(firstData.getName())) {
-      interestCopy.setName(firstData.getName().getPrefix(-1)); // cut last segment number if present
-    } else {
-      logger.log(Level.FINER, "First packet retrieved does not have a segment number, continuing on: " + firstData.getName().toUri());
-      interestCopy.setName(firstData.getName());
-    }
-
-    // send interests in remaining segments
-    for (long i = firstSegment + 1; i <= lastSegment; i++) {
-      Interest segmentedInterest = new Interest(interestCopy);
-      segmentedInterest.getName().appendSegment(i);
-      Future<Data> futureData = SimpleClient.getDefault().getAsync(face, segmentedInterest);
-      segments.add((int) i, futureData);
-    }
-
-    return segments;
-  }
-
-  /**
-   * Asynchronously send Interests for a segmented Data packet using a default
-   * interest (e.g. 2 second timeout); this will block until complete (i.e.
-   * either data is received or the interest times out). See getAsync(Face face)
-   * for more information.
-   *
-   * @param face
-   * @param name
-   * @return
-   */
-  public List<Future<Data>> getAsyncList(Face face, Name name) {
-    return getAsyncList(face, SimpleClient.getDefaultInterest(name));
-  }
-
-  /**
-   * Retrieve a segmented Data packet; will block until all segments are
-   * received and will re-assemble these.
+   * Retrieve a segmented Data packet; see {@link #getAsync(net.named_data.jndn.Face, net.named_data.jndn.Interest)
+   * } for more information. This method will block and call
+   * {@link Face#processEvents()} until the sent interests timeout or the
+   * corresponding data packets are retrieved.
    *
    * @param face
    * @param interest should include either a ChildSelector or an initial segment
@@ -190,9 +193,9 @@
 
   /**
    * Synchronously retrieve the Data for a Name using a default interest (e.g. 2
-   * second timeout); this will block until complete (i.e. either data is
-   * received or the interest times out). See getSync(Face face) for more
-   * information.
+   * second timeout). This method will block and call
+   * {@link Face#processEvents()} until the sent interests timeout or the
+   * corresponding data packets are retrieved.
    *
    * @param face
    * @param name
@@ -214,4 +217,12 @@
   public static boolean hasSegment(Name name) {
     return name.get(-1).getValue().buf().get(0) == 0x00;
   }
+
+  /**
+   * @param name the {@link Name} to check
+   * @return a new instance of {@link Name} with no segment component appended
+   */
+  public static Name removeSegment(Name name) {
+    return hasSegment(name) ? name.getPrefix(-1) : new Name(name);
+  }
 }