View Javadoc
1   package com.github.davidmoten.rx2.internal.flowable;
2   
3   import com.github.davidmoten.guavamini.annotations.VisibleForTesting;
4   
5   /**
6    * Enables a forward-only iteration of string values split by a delimiter across
7    * a linked list of strings.
8    */
9   public final class DelimitedStringLinkedList {
10  
11      private final String delimiter;
12      private final StringBuilder b = new StringBuilder();
13  
14      private Node head;
15      private Node tail;
16      private int headPosition;
17      private Node searchNode;
18      private int searchPosition;
19      private int nextLength;
20      private boolean added;
21  
22      public DelimitedStringLinkedList(String delimiter) {
23          this.delimiter = delimiter;
24      }
25  
26      @VisibleForTesting
27      int searchPosition() {
28          return searchPosition;
29      }
30  
31      private static final class Node {
32          final String value;
33          Node next;
34  
35          Node(String value, Node next) {
36              this.value = value;
37              this.next = next;
38          }
39  
40          @Override
41          public String toString() {
42              return "Node [value=" + value + ", next=" + next + "]";
43          }
44      }
45  
46      public boolean addCalled() {
47          return added;
48      }
49  
50      public void add(String s) {
51          added = true;
52          if (s.length() == 0) {
53              return;
54          }
55          if (head == null) {
56              head = new Node(s, null);
57              tail = head;
58              headPosition = 0;
59              searchPosition = 0;
60              searchNode = head;
61              nextLength = 0;
62          } else {
63              Node node = new Node(s, null);
64              tail.next = node;
65              tail = node;
66              if (searchNode == null) {
67                  searchNode = node;
68                  searchPosition = 0;
69              }
70          }
71      }
72  
73      public String remaining() {
74          if (head == null) {
75              return null;
76          } else {
77              b.setLength(0);
78              Node n = head;
79              do {
80                  if (n == head) {
81                      b.append(n.value.substring(headPosition, n.value.length()));
82                  } else {
83                      b.append(n.value);
84                  }
85                  n = n.next;
86              } while (n != null);
87              return b.toString();
88          }
89      }
90  
91      public String next() {
92          while (searchNode != null) {
93              if (searchNode.value.charAt(searchPosition) == delimiter.charAt(0)) {
94                  Node nd = searchNode;
95                  int pos = searchPosition + 1;
96                  int j = 1;
97                  while (j < delimiter.length()) {
98                      if (pos == nd.value.length()) {
99                          if (nd.next == null) {
100                             break;
101                         }
102                         nd = nd.next;
103                         pos = 0;
104                     }
105                     if (nd.value.charAt(pos) != delimiter.charAt(j)) {
106                         break;
107                     }
108                     j++;
109                     pos++;
110                 }
111                 boolean found = j == delimiter.length();
112                 if (found) {
113                     // at this point (nd, pos) is the location of the end of
114                     // next + delimiter
115                     // (searchNode, searchPosition) is at next + 1
116 
117                     String result = extractFromHeadPositionToSearchPosition();
118                     // reset nodes and positions
119                     resetPositionsAfterExtract(nd, pos);
120                     return result;
121                 }
122             }
123             nextLength++;
124             searchPosition += 1;
125             if (searchPosition == searchNode.value.length()) {
126                 if (searchNode.next == null) {
127                     searchNode = null;
128                     searchPosition = 0;
129                     break;
130                 } else {
131                     searchNode = searchNode.next;
132                     searchPosition = 0;
133                 }
134             }
135         }
136         return null;
137     }
138 
139     private String extractFromHeadPositionToSearchPosition() {
140         // reuse StringBuilder
141         b.setLength(0);
142         b.ensureCapacity(nextLength);
143         Node n = head;
144         while (true) {
145             if (n == searchNode && n == head) {
146                 b.append(n.value.substring(headPosition, searchPosition));
147                 break;
148             } else if (n == head) {
149                 b.append(n.value.substring(headPosition, n.value.length()));
150             } else if (n == searchNode) {
151                 b.append(n.value.substring(0, searchPosition));
152                 break;
153             } else {
154                 b.append(n.value);
155             }
156             n = n.next;
157         }
158         if (nextLength != b.length()) {
159             throw new RuntimeException("unexpected");
160         }
161         return b.toString();
162     }
163 
164     private void resetPositionsAfterExtract(Node nd, int pos) {
165         nextLength = 0;
166         if (pos == nd.value.length()) {
167             if (tail == nd) {
168                 tail = nd.next;
169             }
170             // TODO might need GC Nepotism protection
171             head = nd.next;
172             headPosition = 0;
173             searchPosition = 0;
174             searchNode = head;
175         } else {
176             head = nd;
177             headPosition = pos;
178             if (headPosition == head.value.length()) {
179                 dropFirst();
180             }
181             searchNode = head;
182             searchPosition = headPosition;
183         }
184     }
185 
186     private void dropFirst() {
187         if (head.next == null) {
188             tail = null;
189             head = null;
190             headPosition = 0;
191         } else {
192             if (tail == head) {
193                 tail = head.next;
194             }
195             Node h = head;
196             head = head.next;
197             h.next = null; // GC Nepotism protection?
198             headPosition = 0;
199         }
200     }
201 
202     public void clear() {
203         head = null;
204         tail = null;
205         searchNode = null;
206         headPosition = 0;
207         searchPosition = 0;
208     }
209 
210 }