001    /**
002     * Copyright 2004-2012 The Kuali Foundation
003     *
004     * Licensed under the Educational Community License, Version 2.0 (the "License");
005     * you may not use this file except in compliance with the License.
006     * You may obtain a copy of the License at
007     *
008     * http://www.opensource.org/licenses/ecl2.php
009     *
010     * Unless required by applicable law or agreed to in writing, software
011     * distributed under the License is distributed on an "AS IS" BASIS,
012     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013     * See the License for the specific language governing permissions and
014     * limitations under the License.
015     */
016    package org.kuali.common.threads;
017    
018    import java.lang.Thread.UncaughtExceptionHandler;
019    import java.util.List;
020    
021    import org.kuali.common.threads.listener.ProgressNotifier;
022    
023    /**
024     * Produce ThreadHandlers that create and control execution of threads for iterating over List<T> objects
025     */
026    public class ThreadHandlerFactory {
027    
028        /**
029         * Return an array of int's that represents as even of a split as possible
030         *
031         * For example: passing in 100,7 returns 15, 15, 14, 14, 14, 14, 14
032         *
033         * @param numerator
034         * @param denominator
035         * @return
036         */
037        protected int[] getDivideEvenly(int numerator, int denominator) {
038            int quotient = numerator / denominator;
039            int remainder = numerator % denominator;
040    
041            int[] lengths = new int[denominator];
042            for (int i = 0; i < denominator; i++) {
043                int length = i < remainder ? quotient + 1 : quotient;
044                lengths[i] = length;
045            }
046            return lengths;
047        }
048    
049        /**
050         * Given some context, produce a ThreadHandler for iterating over the list of elements provided in the context
051         *
052         * @param <T>
053         * @param context
054         * @return
055         */
056        public <T> ThreadHandler<T> getThreadHandler(ThreadHandlerContext<T> context) {
057            // Extract some local variables for convenience
058            List<T> list = context.getList();
059            int elements = list.size();
060            int max = context.getMax();
061            int min = context.getMin();
062    
063            // Calculate # of threads and elements per thread
064            int threadCount = getThreadCount(max, min, elements, context.getDivisor());
065            int[] lengths = getDivideEvenly(elements, threadCount);
066    
067            // Create a new thread handler
068            ThreadHandler<T> handler = new ThreadHandler<T>();
069            handler.setThreadCount(threadCount);
070    
071            // Setup a notifier/listener for tracking progress
072            ProgressNotifier<T> notifier = new ProgressNotifier<T>();
073            notifier.setListener(context.getListener());
074            notifier.setTotal(list.size());
075            handler.setNotifier(notifier);
076    
077            // Create a thread group and threads
078            ThreadGroup group = new ThreadGroup("List Iterator Threads");
079            group.setDaemon(true);
080            Thread[] threads = getThreads(handler, list, context.getHandler(), lengths);
081    
082            // Store both in the handler
083            handler.setGroup(group);
084            handler.setThreads(threads);
085    
086            // Return the ThreadHandler
087            return handler;
088        }
089    
090        /**
091         * Generate threads where each thread iterates over a portion of the list
092         *
093         * @param <T>
094         * @param threadHandler
095         * @param list
096         * @param elementHandler
097         * @return
098         */
099        protected <T> Thread[] getThreads(ThreadHandler<T> thandler, List<T> list, ElementHandler<T> ehandler, int[] lengths) {
100            // Total threads we'll need
101            Thread[] threads = new Thread[thandler.getThreadCount()];
102    
103            // Create each thread
104            int offset = 0;
105            for (int i = 0; i < threads.length; i++) {
106    
107                // Get an identifier for this thread
108                int id = i + 1;
109    
110                // The number of elements this thread needs to iterate over
111                int length = lengths[i];
112    
113                // Give this thread some context
114                ListIteratorContext<T> context = new ListIteratorContext<T>(id, offset, length, list);
115                context.setNotifier(thandler.getNotifier());
116                context.setThreadHandler(thandler);
117                context.setElementHandler(ehandler);
118    
119                // Create a thread and store it in the array
120                Runnable runnable = new ListIteratorThread<T>(context);
121                threads[i] = getThread(runnable, id, thandler.getGroup(), thandler);
122    
123                // Increment offset
124                offset += length;
125            }
126    
127            // Return an array of threads ready to execute
128            return threads;
129        }
130    
131        /**
132         * Construct a Thread from the information provided
133         *
134         * @param <T>
135         * @param runnable
136         * @param id
137         * @param group
138         * @param handler
139         * @return
140         */
141        protected <T> Thread getThread(Runnable runnable, int id, ThreadGroup group, UncaughtExceptionHandler handler) {
142            Thread thread = new Thread(group, runnable, "ListIterator-" + id);
143            thread.setUncaughtExceptionHandler(handler);
144            thread.setDaemon(true);
145            return thread;
146        }
147    
148        /**
149         * Get the number of threads to use. The number returned here will never be greater than max. It may be less than
150         * min, but only in the case where elements is also less than min.
151         *
152         * @param max
153         * @param min
154         * @param elements
155         * @param divisor
156         * @return
157         */
158        protected int getThreadCount(int max, int min, int elements, int divisor) {
159            // Reset min to max if needed
160            min = (min > max) ? max : min;
161    
162            // Validate the params
163            validate(max, min, elements, divisor);
164    
165            // Reduce max if appropriate
166            max = (max > elements) ? elements : max;
167    
168            // Reduce min if appropriate
169            min = (min > elements) ? elements : min;
170    
171            // Divisor allows clients to scale threads in proportion to the number of elements in the list
172            int threads = (divisor > 0) ? (elements / divisor) : max;
173    
174            // Reset to max if we have exceeded it
175            threads = (threads > max) ? max : threads;
176    
177            // Reset to min if we have dropped below it
178            threads = (threads < min) ? min : threads;
179    
180            // Return the thread count to use
181            return threads;
182        }
183    
184        protected void validate(int max, int min, int elements, int divisor) {
185            StringBuilder sb = new StringBuilder();
186            if (divisor < 0) {
187                sb.append(" [divisor must be >= 0] ");
188            }
189            if (max < 1) {
190                sb.append(" [max must be >= 1] ");
191            }
192            if (min < 0) {
193                sb.append(" [min must be >= 0] ");
194            }
195            if (elements < 0) {
196                sb.append(" [elements must be >= 0] ");
197            }
198            if (min > max) {
199                sb.append(" [min must be <= max] ");
200            }
201            if (sb.length() > 0) {
202                throw new IllegalArgumentException(sb.toString());
203            }
204        }
205    
206    }