/testability-explorer/src/main/java/com/google/test/metric/collection/KeyedMultiStack.java

http://testability-explorer.googlecode.com/ · Java · 377 lines · 279 code · 38 blank · 60 comment · 61 complexity · dd2f48b1eee1b576d7232deb49ddff77 MD5 · raw file

  1. /*
  2. * Copyright 2007 Google Inc.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.google.test.metric.collection;
  17. import static java.util.Arrays.asList;
  18. import java.util.ArrayList;
  19. import java.util.Arrays;
  20. import java.util.Collection;
  21. import java.util.HashMap;
  22. import java.util.HashSet;
  23. import java.util.List;
  24. import java.util.Map;
  25. import java.util.Set;
  26. /**
  27. * This class acts as a stack externally. The difference is that internally it
  28. * is implemented with several stacks growing in parallel. The stack starts as a
  29. * single stack when it is constructed. As it grows it can be split into
  30. * multiple keys. The keys can then be rejoined to a new key. Any push/pop
  31. * operations will be performed on one or more internal stacks depending on how
  32. * the stack was split and rejoined.
  33. *
  34. * This is useful when analyzing stack machines (such as Java JVM) which keep
  35. * variables on the stack but whose execution can split and rejoin on the level
  36. * of byte-codes.
  37. *
  38. *
  39. * @author mhevery@google.com <Misko Hevery>
  40. *
  41. * @param <KEY>
  42. * Selector which decides which of the stacks are being pushed /
  43. * popped
  44. * @param <VALUE>
  45. * Value on stack.
  46. */
  47. public class KeyedMultiStack<KEY, VALUE> {
  48. public static class ValueCompactor<VALUE> {
  49. public List<List<VALUE>> compact(List<List<VALUE>> pushValues) {
  50. return pushValues;
  51. }
  52. }
  53. public static class StackUnderflowException extends RuntimeException {
  54. private static final long serialVersionUID = 4649233306901482842L;
  55. }
  56. public static class KeyNotFoundException extends RuntimeException {
  57. private static final long serialVersionUID = 4649233306901482842L;
  58. public <KEY> KeyNotFoundException(KEY key) {
  59. super("Key '" + key + "' not found.");
  60. }
  61. }
  62. private static class Entry<VALUE> {
  63. private final int depth;
  64. private final Set<Entry<VALUE>> parents;
  65. private final VALUE value;
  66. public Entry() {
  67. this.depth = -1;
  68. this.parents = null;
  69. this.value = null;
  70. }
  71. public Entry(Set<Entry<VALUE>> parents, VALUE value) {
  72. this.value = value;
  73. this.parents = parents;
  74. if (parents.size() == 0) {
  75. this.depth = 0;
  76. } else {
  77. this.depth = parents.iterator().next().depth + 1;
  78. }
  79. }
  80. @Override
  81. public String toString() {
  82. StringBuilder buf = new StringBuilder();
  83. buf.append("\n");
  84. toString(buf, "");
  85. return buf.toString();
  86. }
  87. private void toString(StringBuilder buf, String offset) {
  88. if (depth == -1) {
  89. return;
  90. }
  91. buf.append(offset);
  92. buf.append("#");
  93. buf.append(depth);
  94. buf.append("(");
  95. buf.append(value);
  96. buf.append(")\n");
  97. for (Entry<VALUE> parent : parents) {
  98. parent.toString(buf, offset + " ");
  99. }
  100. }
  101. public Set<Entry<VALUE>> getParents() {
  102. if (depth == -1) {
  103. throw new StackUnderflowException();
  104. }
  105. return parents;
  106. }
  107. }
  108. @SuppressWarnings("unchecked")
  109. public static class Path<VALUE> {
  110. private VALUE[] elements = (VALUE[]) new Object[4];
  111. private int size = 0;
  112. public void add(VALUE value) {
  113. ensureSize();
  114. elements[size++] = value;
  115. }
  116. private void ensureSize() {
  117. if (elements.length == size) {
  118. VALUE[] newElements = (VALUE[]) new Object[elements.length * 2];
  119. System.arraycopy(elements, 0, newElements, 0, elements.length);
  120. elements = newElements;
  121. }
  122. }
  123. public List<VALUE> asList() {
  124. return Arrays.asList(elements).subList(0, size);
  125. }
  126. @Override
  127. public String toString() {
  128. StringBuilder buf = new StringBuilder();
  129. int count = 0;
  130. for (VALUE value : elements) {
  131. if (count >= size) {
  132. break;
  133. }
  134. if (count > 0) {
  135. buf.append(" :: ");
  136. }
  137. buf.append(value);
  138. count++;
  139. }
  140. return buf.toString();
  141. }
  142. @Override
  143. public int hashCode() {
  144. final int prime = 31;
  145. int result = 1;
  146. result = prime * result + Arrays.hashCode(elements);
  147. result = prime * result + size;
  148. return result;
  149. }
  150. @Override
  151. public boolean equals(Object obj) {
  152. if (this == obj) {
  153. return true;
  154. }
  155. if (obj == null) {
  156. return false;
  157. }
  158. if (getClass() != obj.getClass()) {
  159. return false;
  160. }
  161. Path other = (Path) obj;
  162. if (!Arrays.equals(elements, other.elements)) {
  163. return false;
  164. }
  165. if (size != other.size) {
  166. return false;
  167. }
  168. return true;
  169. }
  170. }
  171. private final Entry<VALUE> root = new Entry<VALUE>();
  172. private final Map<KEY, Set<Entry<VALUE>>> head = new HashMap<KEY, Set<Entry<VALUE>>>();
  173. private final ValueCompactor<VALUE> pathCompactor;
  174. /**
  175. * @param key Initial key for the primordial stack.
  176. */
  177. public KeyedMultiStack(KEY key, ValueCompactor<VALUE> pathCompactor) {
  178. this(pathCompactor);
  179. init(key);
  180. }
  181. public KeyedMultiStack(ValueCompactor<VALUE> pathCompactor) {
  182. this.pathCompactor = pathCompactor;
  183. }
  184. public void init(KEY key) {
  185. head.clear();
  186. head.put(key, set(root));
  187. }
  188. private Set<Entry<VALUE>> getHead(KEY key) {
  189. if (!head.containsKey(key)) {
  190. throw new KeyNotFoundException(key);
  191. } else {
  192. return head.get(key);
  193. }
  194. }
  195. private Set<Entry<VALUE>> removeHead(KEY key) {
  196. if (!head.containsKey(key)) {
  197. throw new KeyNotFoundException(key);
  198. } else {
  199. return head.remove(key);
  200. }
  201. }
  202. @SuppressWarnings("unchecked")
  203. private Set<Entry<VALUE>> set(Entry<VALUE> entries) {
  204. return new HashSet<Entry<VALUE>>(asList(entries));
  205. }
  206. /**
  207. * Pop vale from the stack. The closure can be called more then once if there are parallel stacks
  208. * due to splits.
  209. *
  210. * @param key key as stack selector
  211. * @param popClosure Closer which will be called once per each virtual stack.
  212. */
  213. public void apply(KEY key, PopClosure<KEY, VALUE> popClosure) {
  214. int popSize = popClosure.getSize();
  215. Set<Path<VALUE>> paths = fillPopPaths(getHead(key), popSize);
  216. popPaths(key, popSize);
  217. List<List<VALUE>> pushValues = new ArrayList<List<VALUE>>(paths.size());
  218. int pushSize = -1;
  219. for (Path<VALUE> path : paths) {
  220. List<VALUE> pushSet = popClosure.pop(key, path.asList());
  221. if (pushSize == -1) {
  222. pushSize = pushSet.size();
  223. } else if (pushSize != pushSet.size()) {
  224. throw new IllegalStateException(
  225. "All push pushes must be of same size.");
  226. }
  227. pushValues.add(pushSet);
  228. }
  229. pushValues = pathCompactor.compact(pushValues);
  230. if (pushSize > 0) {
  231. Set<Entry<VALUE>> parent = head.get(key);
  232. Set<Entry<VALUE>> newHead = new HashSet<Entry<VALUE>>();
  233. for (List<VALUE> values : pushValues) {
  234. Entry<VALUE> entry = null;
  235. for (VALUE value : values) {
  236. if (entry == null) {
  237. entry = new Entry<VALUE>(parent, value);
  238. } else {
  239. entry = new Entry<VALUE>(set(entry), value);
  240. }
  241. }
  242. newHead.add(entry);
  243. }
  244. head.put(key, newHead);
  245. }
  246. }
  247. private void popPaths(KEY key, int size) {
  248. if (size == 0) {
  249. return;
  250. }
  251. Set<Entry<VALUE>> newEntries = new HashSet<Entry<VALUE>>();
  252. for (Entry<VALUE> entry : getHead(key)) {
  253. newEntries.addAll(entry.getParents());
  254. }
  255. head.put(key, newEntries);
  256. popPaths(key, size - 1);
  257. }
  258. private Set<Path<VALUE>> fillPopPaths(Set<Entry<VALUE>> entries, int size) {
  259. Set<Path<VALUE>> paths = new HashSet<Path<VALUE>>();
  260. if (size == 0) {
  261. paths.add(new Path<VALUE>());
  262. } else {
  263. for (Entry<VALUE> entry : entries) {
  264. if (entry.depth < size - 1) {
  265. throw new StackUnderflowException();
  266. }
  267. for (Path<VALUE> path : fillPopPaths(entry.getParents(),
  268. size - 1)) {
  269. path.add(entry.value);
  270. paths.add(path);
  271. }
  272. }
  273. }
  274. return paths;
  275. }
  276. /**
  277. * Split the internal stacks to a new set of stacks
  278. *
  279. * @param key Stack(s) to split.
  280. * @param subKeys New names for those stacks
  281. */
  282. public void split(KEY key, List<KEY> subKeys) {
  283. Set<Entry<VALUE>> entries = removeHead(key);
  284. for (KEY subKey : subKeys) {
  285. if (head.containsKey(subKey)) {
  286. Set<Entry<VALUE>> existingList = head.get(subKey);
  287. entries.addAll(existingList);
  288. }
  289. }
  290. assertSameDepth(entries);
  291. for (KEY subKey : subKeys) {
  292. head.put(subKey, entries);
  293. }
  294. }
  295. /**
  296. * Rejoin the stacks. This is not always possible since each stack may now be different, but it
  297. * will make sure that the new single key will now refer to all of the stacks treating them as
  298. * one. This will make the pop operation be applied to individual stacks.
  299. *
  300. * @param subKeys a list of keys for the old stacks to join.
  301. * @param newKey new name for the stack
  302. */
  303. public void join(Collection<KEY> subKeys, KEY newKey) {
  304. Set<Entry<VALUE>> newHead = new HashSet<Entry<VALUE>>();
  305. for (KEY key : subKeys) {
  306. Set<Entry<VALUE>> entries = getHead(key);
  307. newHead.addAll(entries);
  308. }
  309. assertSameDepth(newHead);
  310. for (KEY key : subKeys) {
  311. removeHead(key);
  312. }
  313. head.put(newKey, newHead);
  314. }
  315. private void assertSameDepth(Collection<Entry<VALUE>> entries) {
  316. int expectedDepth = Integer.MIN_VALUE;
  317. for (Entry<VALUE> entry : entries) {
  318. if (expectedDepth == Integer.MIN_VALUE) {
  319. expectedDepth = entry.depth;
  320. } else if (expectedDepth != entry.depth) {
  321. throw new IllegalStateException(
  322. "Not all entries are at same depth. Can't join.");
  323. }
  324. }
  325. }
  326. void assertEmpty() {
  327. for (Collection<Entry<VALUE>> entries : head.values()) {
  328. for (Entry<VALUE> entry : entries) {
  329. if (entry.depth > -1) {
  330. throw new IllegalStateException("Stack not empty.");
  331. }
  332. }
  333. }
  334. }
  335. @Override
  336. public String toString() {
  337. return head.toString();
  338. }
  339. }