1 /*
2 * Copyright (C) 2008-2011, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.lib;
45
46 import java.util.concurrent.Future;
47 import java.util.concurrent.TimeUnit;
48
49 import org.eclipse.jgit.lib.internal.WorkQueue;
50
51 /**
52 * ProgressMonitor that batches update events.
53 */
54 public abstract class BatchingProgressMonitor implements ProgressMonitor {
55 private long delayStartTime;
56
57 private TimeUnit delayStartUnit = TimeUnit.MILLISECONDS;
58
59 private Task task;
60
61 /**
62 * Set an optional delay before the first output.
63 *
64 * @param time
65 * how long to wait before output. If 0 output begins on the
66 * first {@link #update(int)} call.
67 * @param unit
68 * time unit of {@code time}.
69 */
70 public void setDelayStart(long time, TimeUnit unit) {
71 delayStartTime = time;
72 delayStartUnit = unit;
73 }
74
75 /** {@inheritDoc} */
76 @Override
77 public void start(int totalTasks) {
78 // Ignore the number of tasks.
79 }
80
81 /** {@inheritDoc} */
82 @Override
83 public void beginTask(String title, int work) {
84 endTask();
85 task = new Task(title, work);
86 if (delayStartTime != 0)
87 task.delay(delayStartTime, delayStartUnit);
88 }
89
90 /** {@inheritDoc} */
91 @Override
92 public void update(int completed) {
93 if (task != null)
94 task.update(this, completed);
95 }
96
97 /** {@inheritDoc} */
98 @Override
99 public void endTask() {
100 if (task != null) {
101 task.end(this);
102 task = null;
103 }
104 }
105
106 /** {@inheritDoc} */
107 @Override
108 public boolean isCancelled() {
109 return false;
110 }
111
112 /**
113 * Update the progress monitor if the total work isn't known,
114 *
115 * @param taskName
116 * name of the task.
117 * @param workCurr
118 * number of units already completed.
119 */
120 protected abstract void onUpdate(String taskName, int workCurr);
121
122 /**
123 * Finish the progress monitor when the total wasn't known in advance.
124 *
125 * @param taskName
126 * name of the task.
127 * @param workCurr
128 * total number of units processed.
129 */
130 protected abstract void onEndTask(String taskName, int workCurr);
131
132 /**
133 * Update the progress monitor when the total is known in advance.
134 *
135 * @param taskName
136 * name of the task.
137 * @param workCurr
138 * number of units already completed.
139 * @param workTotal
140 * estimated number of units to process.
141 * @param percentDone
142 * {@code workCurr * 100 / workTotal}.
143 */
144 protected abstract void onUpdate(String taskName, int workCurr,
145 int workTotal, int percentDone);
146
147 /**
148 * Finish the progress monitor when the total is known in advance.
149 *
150 * @param taskName
151 * name of the task.
152 * @param workCurr
153 * total number of units processed.
154 * @param workTotal
155 * estimated number of units to process.
156 * @param percentDone
157 * {@code workCurr * 100 / workTotal}.
158 */
159 protected abstract void onEndTask(String taskName, int workCurr,
160 int workTotal, int percentDone);
161
162 private static class Task implements Runnable {
163 /** Title of the current task. */
164 private final String taskName;
165
166 /** Number of work units, or {@link ProgressMonitor#UNKNOWN}. */
167 private final int totalWork;
168
169 /** True when timer expires and output should occur on next update. */
170 private volatile boolean display;
171
172 /** Scheduled timer, supporting cancellation if task ends early. */
173 private Future<?> timerFuture;
174
175 /** True if the task has displayed anything. */
176 private boolean output;
177
178 /** Number of work units already completed. */
179 private int lastWork;
180
181 /** Percentage of {@link #totalWork} that is done. */
182 private int lastPercent;
183
184 Task(String taskName, int totalWork) {
185 this.taskName = taskName;
186 this.totalWork = totalWork;
187 this.display = true;
188 }
189
190 void delay(long time, TimeUnit unit) {
191 display = false;
192 timerFuture = WorkQueue.getExecutor().schedule(this, time, unit);
193 }
194
195 @Override
196 public void run() {
197 display = true;
198 }
199
200 void update(BatchingProgressMonitor pm, int completed) {
201 lastWork += completed;
202
203 if (totalWork == UNKNOWN) {
204 // Only display once per second, as the alarm fires.
205 if (display) {
206 pm.onUpdate(taskName, lastWork);
207 output = true;
208 restartTimer();
209 }
210 } else {
211 // Display once per second or when 1% is done.
212 int currPercent = lastWork * 100 / totalWork;
213 if (display) {
214 pm.onUpdate(taskName, lastWork, totalWork, currPercent);
215 output = true;
216 restartTimer();
217 lastPercent = currPercent;
218 } else if (currPercent != lastPercent) {
219 pm.onUpdate(taskName, lastWork, totalWork, currPercent);
220 output = true;
221 lastPercent = currPercent;
222 }
223 }
224 }
225
226 private void restartTimer() {
227 display = false;
228 timerFuture = WorkQueue.getExecutor().schedule(this, 1,
229 TimeUnit.SECONDS);
230 }
231
232 void end(BatchingProgressMonitor pm) {
233 if (output) {
234 if (totalWork == UNKNOWN) {
235 pm.onEndTask(taskName, lastWork);
236 } else {
237 int pDone = lastWork * 100 / totalWork;
238 pm.onEndTask(taskName, lastWork, totalWork, pDone);
239 }
240 }
241 if (timerFuture != null)
242 timerFuture.cancel(false /* no interrupt */);
243 }
244 }
245 }