PageRenderTime 48ms CodeModel.GetById 19ms app.highlight 24ms RepoModel.GetById 2ms app.codeStats 0ms

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