1 package org.codehaus.plexus.util;
2
3 /*
4 * Copyright The Codehaus Foundation.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18
19 import java.util.ArrayList;
20
21 /**
22 * Pools a bunch of objects . Runs a sweeper periodically to keep it down to size. The objects in the pool first get
23 * disposed first.
24 *
25 * @author <a href="mailto:bert@tuaworks.co.nz">Bert van Brakel</a>
26 *
27 */
28 public class SweeperPool {
29 /***/
30 private static final boolean DEBUG = false;
31
32 /** Sweeps the pool periodically to trim it's size */
33 private transient Sweeper sweeper;
34
35 /** Absolute maximum size of the pool. */
36 private transient int maxSize;
37
38 /** The size the pool gets trimmed down to */
39 private transient int minSize;
40
41 /**
42 * When the sweeper runs and the pool is over this size, then the pool is trimmed
43 */
44 private int triggerSize;
45
46 /** Holds the pooled objects */
47 private ArrayList<Object> pooledObjects;
48
49 /** Flag indicating this pool is shuting down */
50 private boolean shuttingDown = false;
51
52 // private Vector used;
53
54 /**
55 * There are a number of settings to control how the pool operates.
56 * @param maxSize if the pool has reached this size, any objects added are immediately disposed. If the
57 * pool is this size when the sweeper runs, then the pool is also trimmed to <code>minSize</code> irrespective of
58 * the triggerSize.
59 * @param minSize - this is the size the pool is trimmed to
60 * @param triggerSize - this determines if the pool is trimmed when the sweeper runs. If the pool size is
61 * greater or equal than this value then the pool is trimmed to <code>minSize</code>.
62 *
63 * @param sweepInterval how often the sweeper runs. Is actually the time since the sweeper last finished
64 * a pass. 0 if the sweeper should not run.
65 * @param intialCapacity the intial capacity
66 * <p>Any value less than 0 is automatically converted to 0</p>
67 */
68 public SweeperPool(int maxSize, int minSize, int intialCapacity, int sweepInterval, int triggerSize) {
69 super();
70 this.maxSize = saneConvert(maxSize);
71 this.minSize = saneConvert(minSize);
72 this.triggerSize = saneConvert(triggerSize);
73 pooledObjects = new ArrayList(intialCapacity);
74
75 // only run a sweeper if sweep interval is positive
76 if (sweepInterval > 0) {
77 sweeper = new Sweeper(this, sweepInterval);
78 sweeper.start();
79 }
80 }
81
82 private int saneConvert(int value) {
83 return Math.max(value, 0);
84 }
85
86 /**
87 * Return the pooled object
88 * @return first available object from the pool
89 */
90 public synchronized Object get() {
91 if ((pooledObjects.size() == 0) || shuttingDown) {
92 return null;
93 } else {
94 Object obj = pooledObjects.remove(0);
95 objectRetrieved(obj);
96
97 // used.add(obj);
98 return obj;
99 }
100 }
101
102 /**
103 * Add an object to the pool
104 *
105 * @param obj the object to pool. Can be null.
106 * @return true if the object was added to the pool, false if it was disposed or null
107 */
108 public synchronized boolean put(Object obj) {
109 objectAdded(obj);
110
111 if ((obj != null) && (pooledObjects.size() < maxSize) && (shuttingDown == false)) {
112 pooledObjects.add(obj);
113
114 return true;
115 } else if (obj != null) {
116 // no longer need the object, so dispose it
117 objectDisposed(obj);
118 }
119
120 return false;
121 }
122
123 /**
124 * Return the number of pooled objects. This is never greater than t maximum size of the pool
125 *
126 * @return the number of pooled objects
127 */
128 public synchronized int getSize() {
129 return pooledObjects.size();
130 }
131
132 /**
133 * Dispose of this pool. Stops the sweeper and disposes each object in the pool
134 */
135 public void dispose() {
136 shuttingDown = true;
137
138 if (sweeper != null) {
139 sweeper.stop();
140 try {
141 sweeper.join();
142 } catch (InterruptedException e) {
143 System.err.println("Unexpected exception occurred: ");
144 e.printStackTrace();
145 }
146 }
147
148 synchronized (this) {
149 // use an array here as objects may still be being put back in the pool
150 // and we don't want to throw a ConcurrentModificationException
151 Object[] objects = pooledObjects.toArray();
152
153 for (Object object : objects) {
154 objectDisposed(object);
155 }
156
157 pooledObjects.clear();
158 }
159 }
160
161 /**
162 * A pool has been disposed if has been shutdown and the sweeper has completed running.
163 *
164 * @return true if the pool has been disposed, false otherwise
165 */
166 boolean isDisposed() {
167 if (!shuttingDown) {
168 return false;
169 }
170
171 // A null sweeper means one was never started.
172 if (sweeper == null) {
173 return true;
174 }
175
176 return sweeper.hasStopped();
177 }
178
179 /**
180 * Trim the pool down to min size
181 */
182 public synchronized void trim() {
183 if (((triggerSize > 0) && (pooledObjects.size() >= triggerSize))
184 || ((maxSize > 0) && (pooledObjects.size() >= maxSize))) {
185 while (pooledObjects.size() > minSize) {
186 objectDisposed(pooledObjects.remove(0));
187 }
188 }
189 }
190
191 /**
192 * Override this to be notified of object disposal. Called after the object has been removed. Occurs when the pool
193 * is trimmed.
194 *
195 * @param obj the Object
196 */
197 public void objectDisposed(Object obj) {}
198
199 /**
200 * Override this to be notified of object addition. Called before object is to be added.
201 *
202 * @param obj the Object
203 */
204 public void objectAdded(Object obj) {}
205
206 /**
207 * Override this to be notified of object retrieval. Called after object removed from the pool, but before returned
208 * to the client.
209 *
210 * @param obj the Object
211 */
212 public void objectRetrieved(Object obj) {}
213
214 /**
215 * Periodically at <code>sweepInterval</code> goes through and tests if the pool should be trimmed.
216 *
217 * @author bert
218 */
219 private static class Sweeper implements Runnable {
220 private final transient SweeperPool pool;
221
222 private transient boolean service = false;
223
224 private final transient int sweepInterval;
225
226 private transient Thread t = null;
227
228 /**
229 *
230 */
231 public Sweeper(SweeperPool pool, int sweepInterval) {
232 super();
233 this.sweepInterval = sweepInterval;
234 this.pool = pool;
235 }
236
237 /**
238 * Run the sweeper.
239 *
240 * @see java.lang.Runnable#run()
241 */
242 @Override
243 public void run() {
244 debug("started");
245
246 if (sweepInterval > 0) {
247 synchronized (this) {
248 while (service) {
249 try {
250 // wait specified number of seconds
251 // before running next sweep
252 wait(sweepInterval * 1000);
253 } catch (InterruptedException e) {
254 }
255 runSweep();
256 }
257 }
258 }
259
260 debug("stopped");
261 }
262
263 public void start() {
264 if (!service) {
265 service = true;
266 t = new Thread(this);
267 t.setName("Sweeper");
268 t.start();
269 }
270 }
271
272 public synchronized void stop() {
273 service = false;
274 notifyAll();
275 }
276
277 void join() throws InterruptedException {
278 t.join();
279 }
280
281 boolean hasStopped() {
282 return !service && !t.isAlive();
283 }
284
285 private final void debug(String msg) {
286 if (DEBUG) {
287 System.err.println(this + ":" + msg);
288 }
289 }
290
291 private void runSweep() {
292 debug("runningSweep. time=" + System.currentTimeMillis());
293 pool.trim();
294 }
295 }
296 }