View Javadoc

1   /*
2    * Copyright (c) 2003-2008, by Henrik Arro and Contributors
3    *
4    * This file is part of JSeq, a tool to automatically create
5    * sequence diagrams by tracing program execution.
6    *
7    * See <http://jseq.sourceforge.net> for more information.
8    *
9    * JSeq is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU Lesser General Public License as
11   * published by the Free Software Foundation, either version 3 of
12   * the License, or (at your option) any later version.
13   *
14   * JSeq is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17   * GNU Lesser General Public License for more details.
18   *
19   * You should have received a copy of the GNU Lesser General Public License
20   * along with JSeq. If not, see <http://www.gnu.org/licenses/>.
21   */
22  
23  package th.co.edge.jseq;
24  
25  import java.util.Iterator;
26  import java.util.LinkedList;
27  import java.util.List;
28  
29  /**
30   * An <code>ActivationList</code> holds a number of <code>Activation</code>
31   * instances, representing a number of method calls. This can be used to hold
32   * nested method call made by a method, or all root activation, i.e., methods
33   * that are not called by one of the traced methods but "magically" from outside
34   * the sequence diagram.
35   */
36  public class ActivationList implements java.io.Serializable,
37          Iterable<Activation> {
38      private static final long serialVersionUID = 5359418912455509769L;
39  
40      private List<Activation> activations = new LinkedList<Activation>();
41  
42      /**
43       * Adds an <code>Activation</code> to this list.
44       *
45       * @param activation
46       *            the <code>Activation</code> to add
47       */
48      public void add(Activation activation) {
49          activations.add(activation);
50      }
51  
52      /**
53       * Adds all <code>Activation</code>s from another
54       * <code>ActivationList</code> to this list.
55       *
56       * @param activationList
57       *            the <code>ActivationList</code> whose
58       *            <code>Activation</code>s will be added to this list
59       */
60      public void addAll(ActivationList activationList) {
61          activations.addAll(activationList.activations);
62      }
63  
64      /**
65       * Removes an <code>Activation</code> at a certain position in this list.
66       *
67       * @param index
68       *            the position in this list to remove
69       *
70       * @return the removed <code>Activation</code>
71       */
72      public Activation remove(int index) {
73          return activations.remove(index);
74      }
75  
76      /**
77       * Returns the <code>Activation</code> at a certain position in this list.
78       *
79       * @param index
80       *            the position in this list to return
81       *
82       * @return the <code>Activation</code> at position <code>index</code> in
83       *         this list
84       */
85      public Activation get(int index) {
86          return activations.get(index);
87      }
88  
89      /**
90       * Returns the size of this <code>ActivationList</code>.
91       *
92       * @return the size of this list
93       */
94      public int size() {
95          return activations.size();
96      }
97  
98      /**
99       * Returns an iterator that iterates over the <code>Activation</code>s in
100      * this list, in the order they were added.
101      *
102      * @return an iterator that iterates over the <code>Activation</code>s in
103      *         this list
104      */
105     public Iterator<Activation> iterator() {
106         return activations.iterator();
107     }
108 
109     /**
110      * Returns a new <code>ActivationList</code> containing only the
111      * <code>Activation</code>s for which the given filter returns
112      * <code>true</code>. The filtering is also done recursively on nested
113      * <code>Activation</code>s.
114      *
115      * @param filter
116      *            the <code>Filter</code> instance used to determine which
117      *            <code>Activation</code>s should be included
118      *
119      * @return a new <code>ActivationList</code> containing only the
120      *         <code>Activation</code>s that <code>filter</code> accepts
121      */
122     public ActivationList filter(Filter filter) {
123         ActivationList filteredList = new ActivationList();
124         for (Activation activation : activations) {
125             if (filter.accept(activation)) {
126                 Activation newActivation = new Activation(null, activation
127                         .getClassName(), activation.getMethod(), -1);
128                 ActivationList nestedActivations = activation
129                         .getNestedActivations().filter(filter);
130                 newActivation.setNestedActivations(nestedActivations);
131                 nestedActivations.setParent(newActivation);
132                 filteredList.add(newActivation);
133             }
134         }
135         return filteredList;
136     }
137 
138     /**
139      * Returns a new <code>ActivationList</code> where the activations and
140      * nested activations have been pruned, so as to start with an activation
141      * that is accepted by the given filter. In other words, this method "lifts"
142      * activations accepted by the filter to be root activations.
143      *
144      * <p>
145      * For example, given the following activations:
146      *
147      * <pre>
148      *   m
149      *     m1
150      *       q1
151      *       q2
152      *     m2
153      *   p
154      *     m1
155      *       q3
156      *     p2
157      * </pre>
158      *
159      * and a filter that only accepts methods named "m1", the following list
160      * would be returned:
161      *
162      * <pre>
163      *   m1
164      *     q1
165      *     q2
166      *   m1
167      *     q3
168      * </pre>
169      *
170      * @param filter
171      *            the <code>Filter</code> instance used to determine which
172      *            <code>Activation</code>s should be used as root activations
173      *
174      * @return a new <code>ActivationList</code> with <code>Activation</code>s
175      *         accepted by <code>filter</code> as root activations
176      */
177     public ActivationList find(Filter filter) {
178         ActivationList foundActivations = new ActivationList();
179         for (Activation activation : activations) {
180             if (filter.accept(activation)) {
181                 foundActivations.add(activation.copy(null));
182             } else {
183                 foundActivations.addAll(activation.getNestedActivations().find(
184                         filter));
185             }
186         }
187         return foundActivations;
188     }
189 
190     /**
191      * Sets the parent <code>Activation</code> of all <code>Activation</code>s
192      * in this list to the given value.
193      *
194      * @param parent
195      *            the new parent activation
196      */
197     public void setParent(Activation parent) {
198         for (Activation activation : activations) {
199             activation.setParent(parent);
200         }
201     }
202 
203     /**
204      * Returns an <code>ActivationList</code> where all consecutive identical
205      * <code>Activation</code>s but the first have been removed, and its
206      * <code>numRepetitions</code> property increased accordingly.
207      *
208      * @return an <code>ActivationList</code> with no repeated identical
209      *         <code>Activation</code>s
210      */
211     public ActivationList collapseRepetitions() {
212         ActivationList newList = copy();
213         for (int i = 0; i < newList.size(); i++) {
214             Activation activation = newList.get(i);
215             while (newList.size() > i + 1) {
216                 Activation nextActivation = newList.get(i + 1);
217                 if (activation.equals(nextActivation)) {
218                     newList.remove(i + 1);
219                     activation.increaseNumRepetitions();
220                 } else {
221                     break;
222                 }
223             }
224             ActivationList nestedActivations = activation
225                     .getNestedActivations().collapseRepetitions();
226             nestedActivations.setParent(activation);
227             activation.setNestedActivations(nestedActivations);
228         }
229         return newList;
230     }
231 
232     /**
233      * Performs a deep copy of this <code>ActivationList</code>, copying all
234      * <code>Activation</code>s and recursively the nested
235      * <code>Activation</code>s.
236      *
237      * @return a new <code>ActivationList</code> containing a deep copy of all
238      *         <code>Activation</code>s in this list
239      */
240     public ActivationList copy() {
241         ActivationList newList = new ActivationList();
242         for (Activation activation : activations) {
243             newList.add(activation.copy(null));
244         }
245         return newList;
246     }
247 
248     /**
249      * Returns a string representation of this <code>ActivationList</code>.
250      *
251      * @return a string representation of this <code>ActivationList</code>
252      */
253     @Override
254     public String toString() {
255         return activations.toString();
256     }
257 
258     /**
259      * Compares this <code>ActivationList</code> to another object, and
260      * returns <code>true</code> if and only if the other object is an
261      * <code>ActivationList</code> with equal <code>Activation</code>s.
262      *
263      * @param o
264      *            the object to compare this <code>ActivationList</code> to
265      *
266      * @return <code>true</code> if <code>o</code> is an
267      *         <code>ActivationList</code> with equal activations,
268      *         <code>false</code> otherwise
269      */
270     @Override
271     public boolean equals(Object o) {
272         boolean equal = false;
273         if (o instanceof ActivationList) {
274             ActivationList otherActivationList = (ActivationList) o;
275             return activations.equals(otherActivationList.activations);
276         }
277         return equal;
278     }
279 
280     /**
281      * Returns a hash code for this <code>ActivationList</code>.
282      *
283      * @return a hash code for this <code>ActivationList</code>
284      */
285     @Override
286     public int hashCode() {
287         return activations.hashCode();
288     }
289 
290     //
291     // Nested top-level classes
292     //
293 
294     /**
295      * An interface used by the <code>filter</code> and <code>find</code>
296      * methods to determine if an <code>Activation</code> is acceptable or
297      * not.
298      *
299      * @see #filter(Filter)
300      * @see #find(Filter)
301      */
302     public interface Filter {
303 
304         /**
305          * This method determines if an <code>Activation</code> is accepted by
306          * a <code>Filter</code> instance or not. It should return
307          * <code>true</code> if the given <code>Activation</code> should be
308          * accepted, <code>false</code> otherwise.
309          *
310          * @param activation
311          *            the <code>Activation</code> to check for acceptance
312          *
313          * @return <code>true</code> if <code>activation</code> is accepted,
314          *         <code>false</code> otherwise
315          */
316         boolean accept(Activation activation);
317     }
318 }