AbstractEventRouter.java
001 /*
002  * Copyright 2008-2016 the original author or authors.
003  *
004  * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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.codehaus.griffon.runtime.core.event;
017 
018 import griffon.core.CallableWithArgs;
019 import griffon.core.ExceptionHandler;
020 import griffon.core.ExecutorServiceManager;
021 import griffon.core.RunnableWithArgs;
022 import griffon.core.event.Event;
023 import griffon.core.event.EventRouter;
024 import griffon.util.GriffonClassUtils;
025 import griffon.util.MethodDescriptor;
026 import griffon.util.MethodUtils;
027 import org.slf4j.Logger;
028 import org.slf4j.LoggerFactory;
029 
030 import javax.annotation.Nonnull;
031 import javax.annotation.Nullable;
032 import javax.inject.Inject;
033 import java.lang.reflect.Method;
034 import java.util.ArrayList;
035 import java.util.Collection;
036 import java.util.HashSet;
037 import java.util.LinkedHashMap;
038 import java.util.List;
039 import java.util.Map;
040 import java.util.Set;
041 import java.util.concurrent.ConcurrentHashMap;
042 import java.util.concurrent.ExecutorService;
043 import java.util.concurrent.Executors;
044 import java.util.concurrent.ThreadFactory;
045 import java.util.concurrent.atomic.AtomicInteger;
046 
047 import static griffon.util.GriffonClassUtils.convertToTypeArray;
048 import static griffon.util.GriffonNameUtils.capitalize;
049 import static griffon.util.GriffonNameUtils.requireNonBlank;
050 import static java.util.Arrays.asList;
051 import static java.util.Collections.EMPTY_LIST;
052 import static java.util.Collections.unmodifiableCollection;
053 import static java.util.Objects.requireNonNull;
054 
055 /**
056  @author Andres Almiray
057  */
058 public abstract class AbstractEventRouter implements EventRouter {
059     protected static final Object[] LOCK = new Object[0];
060     private static final String ERROR_EVENT_NAME_BLANK = "Argument 'eventName' must not be blank";
061     private static final String ERROR_EVENT_HANDLER_BLANK = "Argument 'eventHandler' must not be blank";
062     private static final String ERROR_MODE_BLANK = "Argument 'mode' must not be blank";
063     private static final String ERROR_LISTENER_NULL = "Argument 'listener' must not be null";
064     private static final String ERROR_EVENT_CLASS_NULL = "Argument 'eventClass' must not be null";
065     private static final String ERROR_EVENT_NULL = "Argument 'event' must not be null";
066     private static final String ERROR_CALLABLE_NULL = "Argument 'callable' must not be null";
067     private static final String ERROR_RUNNABLE_NULL = "Argument 'runnable' must not be null";
068     private static final String ERROR_PARAMS_NULL = "Argument 'params' must not be null";
069     private static final String ERROR_INSTANCE_NULL = "Argument 'instance' must not be null";
070     private static final String ERROR_OWNER_NULL = "Argument 'owner' must not be null";
071     private static final Logger LOG = LoggerFactory.getLogger(AbstractEventRouter.class);
072     protected final Map<String, List<Object>> instanceListeners = new ConcurrentHashMap<>();
073     protected final Map<String, List<Object>> functionalListeners = new ConcurrentHashMap<>();
074     private final MethodCache methodCache = new MethodCache();
075     private boolean enabled = true;
076 
077     protected static final AtomicInteger EVENT_ROUTER_ID = new AtomicInteger(1);
078 
079     protected ExecutorServiceManager executorServiceManager;
080     protected final ExecutorService executorService;
081     protected final int eventRouterId;
082 
083     @Inject
084     private ExceptionHandler exceptionHandler;
085 
086     public AbstractEventRouter() {
087         eventRouterId = EVENT_ROUTER_ID.getAndIncrement();
088         executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()new DefaultThreadFactory(eventRouterId));
089     }
090 
091     @Inject
092     public void setExecutorServiceManager(@Nonnull ExecutorServiceManager executorServiceManager) {
093         requireNonNull(executorServiceManager, "Argument 'executorServiceManager' must not be null");
094         if (this.executorServiceManager != null) {
095             this.executorServiceManager.remove(executorService);
096         }
097         this.executorServiceManager = executorServiceManager;
098         this.executorServiceManager.add(executorService);
099     }
100 
101     protected void runInsideExecutorService(@Nonnull final Runnable runnable) {
102         requireNonNull(runnable, ERROR_RUNNABLE_NULL);
103         executorService.submit(new Runnable() {
104             public void run() {
105                 try {
106                     runnable.run();
107                 catch (Throwable throwable) {
108                     exceptionHandler.uncaughtException(Thread.currentThread(), throwable);
109                 }
110             }
111         });
112     }
113 
114     @Override
115     public boolean isEventPublishingEnabled() {
116         synchronized (LOCK) {
117             return this.enabled;
118         }
119     }
120 
121     @Override
122     public void setEventPublishingEnabled(boolean enabled) {
123         synchronized (LOCK) {
124             this.enabled = enabled;
125         }
126     }
127 
128     @Override
129     public void publishEvent(@Nonnull String eventName) {
130         publishEvent(eventName, EMPTY_LIST);
131     }
132 
133     @Override
134     public void publishEvent(@Nonnull String eventName, @Nullable List<?> params) {
135         if (!isEventPublishingEnabled()) return;
136         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
137         if (params == nullparams = EMPTY_LIST;
138         buildPublisher(eventName, params, "synchronously").run();
139     }
140 
141     @Override
142     public void publishEventOutsideUI(@Nonnull String eventName) {
143         publishEventOutsideUI(eventName, EMPTY_LIST);
144     }
145 
146     @Override
147     public void publishEventOutsideUI(@Nonnull String eventName, @Nullable List<?> params) {
148         if (!isEventPublishingEnabled()) return;
149         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
150         if (params == nullparams = EMPTY_LIST;
151         final Runnable publisher = buildPublisher(eventName, params, "outside UI");
152         doPublishOutsideUI(publisher);
153     }
154 
155     protected abstract void doPublishOutsideUI(@Nonnull Runnable publisher);
156 
157     @Override
158     public void publishEventAsync(@Nonnull String eventName) {
159         publishEventAsync(eventName, EMPTY_LIST);
160     }
161 
162     @Override
163     public void publishEventAsync(@Nonnull String eventName, @Nullable List<?> params) {
164         if (!isEventPublishingEnabled()) return;
165         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
166         if (params == nullparams = EMPTY_LIST;
167         final Runnable publisher = buildPublisher(eventName, params, "asynchronously");
168         doPublishAsync(publisher);
169     }
170 
171     protected abstract void doPublishAsync(@Nonnull Runnable publisher);
172 
173     @Override
174     public void publishEvent(@Nonnull Event event) {
175         requireNonNull(event, ERROR_EVENT_NULL);
176         publishEvent(event.getClass().getSimpleName(), asList(event));
177     }
178 
179     @Override
180     public void publishEventOutsideUI(@Nonnull Event event) {
181         requireNonNull(event, ERROR_EVENT_NULL);
182         publishEventOutsideUI(event.getClass().getSimpleName(), asList(event));
183     }
184 
185     @Override
186     public void publishEventAsync(@Nonnull Event event) {
187         requireNonNull(event, ERROR_EVENT_NULL);
188         publishEventAsync(event.getClass().getSimpleName(), asList(event));
189     }
190 
191     @Override
192     public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
193         requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
194         removeEventListener(eventClass.getSimpleName(), listener);
195     }
196 
197     @Override
198     public <E extends Event> void removeEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
199         requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
200         removeEventListener(eventClass.getSimpleName(), listener);
201     }
202 
203     protected void fireEvent(@Nonnull RunnableWithArgs runnable, @Nonnull List<?> params) {
204         requireNonNull(runnable, ERROR_RUNNABLE_NULL);
205         requireNonNull(params, ERROR_PARAMS_NULL);
206         runnable.run(asArray(params));
207     }
208 
209     protected void fireEvent(@Nonnull CallableWithArgs<?> callable, @Nonnull List<?> params) {
210         requireNonNull(callable, ERROR_CALLABLE_NULL);
211         requireNonNull(params, ERROR_PARAMS_NULL);
212         callable.call(asArray(params));
213     }
214 
215     protected void fireEvent(@Nonnull Object instance, @Nonnull String eventHandler, @Nonnull List<?> params) {
216         requireNonNull(instance, ERROR_INSTANCE_NULL);
217         requireNonBlank(eventHandler, ERROR_EVENT_HANDLER_BLANK);
218         requireNonNull(params, ERROR_PARAMS_NULL);
219 
220         Class[] argTypes = convertToTypeArray(asArray(params));
221         MethodDescriptor target = new MethodDescriptor(eventHandler, argTypes);
222         Method method = methodCache.findMatchingMethodFor(instance.getClass(), target);
223 
224         if (method != null) {
225             MethodUtils.invokeSafe(method, instance, asArray(params));
226         }
227     }
228 
229     @Override
230     public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull CallableWithArgs<?> listener) {
231         requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
232         addEventListener(eventClass.getSimpleName(), listener);
233     }
234 
235     @Override
236     public <E extends Event> void addEventListener(@Nonnull Class<E> eventClass, @Nonnull RunnableWithArgs listener) {
237         requireNonNull(eventClass, ERROR_EVENT_CLASS_NULL);
238         addEventListener(eventClass.getSimpleName(), listener);
239     }
240 
241     @Override
242     @SuppressWarnings("unchecked")
243     public void addEventListener(@Nonnull Object listener) {
244         requireNonNull(listener, ERROR_LISTENER_NULL);
245         if (listener instanceof RunnableWithArgs) {
246             throw new IllegalArgumentException("Cannot add an event listener of type " + RunnableWithArgs.class.getName() +
247                 " because the target event name is missing. " + listener);
248         }
249         if (listener instanceof CallableWithArgs) {
250             throw new IllegalArgumentException("Cannot add an event listener of type " + CallableWithArgs.class.getName() +
251                 " because the target event name is missing. " + listener);
252         }
253 
254         if (listener instanceof Map) {
255             addEventListener((Maplistener);
256             return;
257         }
258 
259         if (!methodCache.isEventListener(listener.getClass())) {
260             return;
261         }
262 
263         boolean added = false;
264         for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
265             eventName = eventName.substring(2)// cut off "on" from the name
266             List<Object> instances = instanceListeners.get(eventName);
267             if (instances == null) {
268                 instances = new ArrayList<>();
269                 instanceListeners.put(eventName, instances);
270             }
271             synchronized (instances) {
272                 if (!instances.contains(listener)) {
273                     added = true;
274                     instances.add(listener);
275                 }
276             }
277         }
278 
279         if (added) {
280             try {
281                 LOG.debug("Adding listener {}", listener);
282             catch (UnsupportedOperationException uoe) {
283                 LOG.debug("Adding listener {}", listener.getClass().getName());
284             }
285         }
286     }
287 
288     @Override
289     public void addEventListener(@Nonnull Map<String, Object> listener) {
290         requireNonNull(listener, ERROR_LISTENER_NULL);
291         for (Map.Entry<String, Object> entry : listener.entrySet()) {
292             Object eventHandler = entry.getValue();
293             if (eventHandler instanceof RunnableWithArgs) {
294                 addEventListener(entry.getKey()(RunnableWithArgseventHandler);
295             else if (eventHandler instanceof CallableWithArgs) {
296                 addEventListener(entry.getKey()(CallableWithArgseventHandler);
297             else {
298                 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
299             }
300         }
301     }
302 
303     @Override
304     @SuppressWarnings("unchecked")
305     public void removeEventListener(@Nonnull Object listener) {
306         requireNonNull(listener, ERROR_LISTENER_NULL);
307         if (listener instanceof RunnableWithArgs) {
308             throw new IllegalArgumentException("Cannot remove an event listener of type " + RunnableWithArgs.class.getName() +
309                 " because the target event name is missing. " + listener);
310         }
311         if (listener instanceof CallableWithArgs) {
312             throw new IllegalArgumentException("Cannot remove an event listener of type " + CallableWithArgs.class.getName() +
313                 " because the target event name is missing. " + listener);
314         }
315 
316         if (listener instanceof Map) {
317             removeEventListener((Maplistener);
318             return;
319         }
320 
321         boolean removed = false;
322         for (String eventName : methodCache.fetchMethodMetadata(listener.getClass()).keySet()) {
323             eventName = eventName.substring(2)// cut off "on" from the name
324             List<Object> instances = instanceListeners.get(eventName);
325             if (instances != null && instances.contains(listener)) {
326                 instances.remove(listener);
327                 removed = true;
328                 if (instances.isEmpty()) {
329                     instanceListeners.remove(eventName);
330                 }
331             }
332         }
333 
334         boolean nestedRemoved = removeNestedListeners(listener);
335 
336         if (removed || nestedRemoved) {
337             try {
338                 LOG.debug("Removing listener {}", listener);
339             catch (UnsupportedOperationException uoe) {
340                 LOG.debug("Removing listener {}", listener.getClass().getName());
341             }
342         }
343     }
344 
345     @Override
346     public void removeEventListener(@Nonnull Map<String, Object> listener) {
347         requireNonNull(listener, ERROR_LISTENER_NULL);
348         for (Map.Entry<String, Object> entry : listener.entrySet()) {
349             Object eventHandler = entry.getValue();
350             if (eventHandler instanceof RunnableWithArgs) {
351                 removeEventListener(entry.getKey()(RunnableWithArgseventHandler);
352             else if (eventHandler instanceof CallableWithArgs) {
353                 removeEventListener(entry.getKey()(CallableWithArgseventHandler);
354             else {
355                 throw new IllegalArgumentException("Unsupported functional event listener " + eventHandler);
356             }
357         }
358     }
359 
360     @Override
361     public void addEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
362         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
363         requireNonNull(listener, ERROR_LISTENER_NULL);
364         synchronized (functionalListeners) {
365             List<Object> list = functionalListeners.get(capitalize(eventName));
366             if (list == null) {
367                 list = new ArrayList<>();
368                 functionalListeners.put(capitalize(eventName), list);
369             }
370             if (list.contains(listener)) return;
371             LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
372             list.add(listener);
373         }
374     }
375 
376     @Override
377     public void addEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
378         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
379         requireNonNull(listener, ERROR_LISTENER_NULL);
380         synchronized (functionalListeners) {
381             List<Object> list = functionalListeners.get(capitalize(eventName));
382             if (list == null) {
383                 list = new ArrayList<>();
384                 functionalListeners.put(capitalize(eventName), list);
385             }
386             if (list.contains(listener)) return;
387             LOG.debug("Adding listener {} on {}", listener.getClass().getName(), capitalize(eventName));
388             list.add(listener);
389         }
390     }
391 
392     @Override
393     public void removeEventListener(@Nonnull String eventName, @Nonnull CallableWithArgs<?> listener) {
394         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
395         requireNonNull(listener, ERROR_LISTENER_NULL);
396         synchronized (functionalListeners) {
397             List<Object> list = functionalListeners.get(capitalize(eventName));
398             if (list != null) {
399                 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
400                 list.remove(listener);
401             }
402         }
403     }
404 
405     @Override
406     public void removeEventListener(@Nonnull String eventName, @Nonnull RunnableWithArgs listener) {
407         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
408         requireNonNull(listener, ERROR_LISTENER_NULL);
409         synchronized (functionalListeners) {
410             List<Object> list = functionalListeners.get(capitalize(eventName));
411             if (list != null) {
412                 LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
413                 list.remove(listener);
414             }
415         }
416     }
417 
418     @Nonnull
419     @Override
420     public Collection<Object> getEventListeners() {
421         List<Object> listeners = new ArrayList<>();
422         synchronized (instanceListeners) {
423             Set<Object> instances = new HashSet<>();
424             for (List<Object> objects : instanceListeners.values()) {
425                 instances.addAll(objects);
426             }
427             listeners.addAll(instances);
428         }
429 
430         synchronized (functionalListeners) {
431             for (List<Object> objects : functionalListeners.values()) {
432                 listeners.addAll(objects);
433             }
434         }
435 
436         return unmodifiableCollection(listeners);
437     }
438 
439     @Nonnull
440     @Override
441     public Collection<Object> getEventListeners(@Nonnull String eventName) {
442         requireNonBlank(eventName, ERROR_EVENT_NAME_BLANK);
443         List<Object> listeners = new ArrayList<>();
444         List<Object> instances = instanceListeners.get(eventName);
445         if (instances != nulllisteners.addAll(instances);
446         instances = functionalListeners.get(eventName);
447         if (instances != nulllisteners.addAll(instances);
448         return unmodifiableCollection(listeners);
449     }
450 
451     protected Runnable buildPublisher(@Nonnull final String event, @Nonnull final List<?> params, @Nonnull final String mode) {
452         requireNonNull(event, ERROR_EVENT_NULL);
453         requireNonNull(params, ERROR_PARAMS_NULL);
454         requireNonBlank(mode, ERROR_MODE_BLANK);
455         return new Runnable() {
456             public void run() {
457                 String eventName = capitalize(event);
458                 LOG.debug("Triggering event '{}' {}", eventName, mode);
459                 String eventHandler = "on" + eventName;
460                 // defensive copying to avoid CME during event dispatching
461                 List<Object> listenersCopy = new ArrayList<>();
462                 List<Object> instances = instanceListeners.get(eventName);
463                 if (instances != null) {
464                     listenersCopy.addAll(instances);
465                 }
466                 synchronized (functionalListeners) {
467                     List list = functionalListeners.get(eventName);
468                     if (list != null) {
469                         for (Object listener : list) {
470                             listenersCopy.add(listener);
471                         }
472                     }
473                 }
474 
475                 for (Object listener : listenersCopy) {
476                     if (listener instanceof RunnableWithArgs) {
477                         fireEvent((RunnableWithArgslistener, params);
478                     else if (listener instanceof CallableWithArgs) {
479                         fireEvent((CallableWithArgs<?>listener, params);
480                     else {
481                         fireEvent(listener, eventHandler, params);
482                     }
483                 }
484             }
485         };
486     }
487 
488     protected boolean removeNestedListeners(@Nonnull Object owner) {
489         requireNonNull(owner, ERROR_OWNER_NULL);
490 
491         boolean removed = false;
492         synchronized (functionalListeners) {
493             for (Map.Entry<String, List<Object>> event : functionalListeners.entrySet()) {
494                 String eventName = event.getKey();
495                 List<Object> listenerList = event.getValue();
496                 List<Object> toRemove = new ArrayList<>();
497                 for (Object listener : listenerList) {
498                     if (isNestedListener(listener, owner)) {
499                         toRemove.add(listener);
500                     }
501                 }
502                 removed = toRemove.size() 0;
503                 for (Object listener : toRemove) {
504                     LOG.debug("Removing listener {} on {}", listener.getClass().getName(), capitalize(eventName));
505                     listenerList.remove(listener);
506                 }
507             }
508         }
509 
510         return removed;
511     }
512 
513     protected boolean isNestedListener(@Nonnull Object listener, @Nonnull Object owner) {
514         requireNonNull(listener, ERROR_LISTENER_NULL);
515         requireNonNull(owner, ERROR_OWNER_NULL);
516         Class<?> listenerClass = listener.getClass();
517         return (listenerClass.isMemberClass() || listenerClass.isAnonymousClass() || listenerClass.isLocalClass()) &&
518             owner.getClass().equals(listenerClass.getEnclosingClass()) &&
519             owner.equals(GriffonClassUtils.getFieldValue(listener, "this$0"));
520     }
521 
522     protected Object[] asArray(@Nonnull List<?> list) {
523         return list.toArray(new Object[list.size()]);
524     }
525 
526     protected static class MethodCache {
527         private final Map<Class<?>, Map<String, List<MethodInfo>>> methodMap = new ConcurrentHashMap<>();
528 
529         public boolean isEventListener(@Nonnull Class<?> klass) {
530             Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
531             if (methodMetadata == null) {
532                 methodMetadata = fetchMethodMetadata(klass);
533                 if (!methodMetadata.isEmpty()) {
534                     methodMap.put(klass, methodMetadata);
535                 else {
536                     methodMetadata = null;
537                 }
538             }
539             return methodMetadata != null;
540         }
541 
542         @Nullable
543         public Method findMatchingMethodFor(@Nonnull Class<?> klass, @Nonnull MethodDescriptor target) {
544             Map<String, List<MethodInfo>> methodMetadata = methodMap.get(klass);
545 
546             List<MethodInfo> descriptors = methodMetadata.get(target.getName());
547             if (descriptors != null) {
548                 for (MethodInfo info : descriptors) {
549                     if (info.descriptor.matches(target)) {
550                         return info.method;
551                     }
552                 }
553             }
554 
555             return null;
556         }
557 
558         private Map<String, List<MethodInfo>> fetchMethodMetadata(Class<?> klass) {
559             Map<String, List<MethodInfo>> methodMetadata = new LinkedHashMap<>();
560 
561             for (Method method : klass.getMethods()) {
562                 MethodDescriptor descriptor = MethodDescriptor.forMethod(method);
563                 if (GriffonClassUtils.isEventHandler(descriptor)) {
564                     String methodName = method.getName();
565                     List<MethodInfo> descriptors = methodMetadata.get(methodName);
566                     if (descriptors == null) {
567                         descriptors = new ArrayList<>();
568                         methodMetadata.put(methodName, descriptors);
569                     }
570                     descriptors.add(new MethodInfo(descriptor, method));
571                 }
572             }
573 
574             return methodMetadata;
575         }
576     }
577 
578     protected static class MethodInfo {
579         private final MethodDescriptor descriptor;
580         private final Method method;
581 
582         public MethodInfo(MethodDescriptor descriptor, Method method) {
583             this.descriptor = descriptor;
584             this.method = method;
585         }
586 
587         public MethodDescriptor getDescriptor() {
588             return descriptor;
589         }
590 
591         public Method getMethod() {
592             return method;
593         }
594     }
595 
596     private static class DefaultThreadFactory implements ThreadFactory {
597         private final ThreadGroup group;
598         private final AtomicInteger threadNumber = new AtomicInteger(1);
599         private final String namePrefix;
600 
601         private DefaultThreadFactory(int eventRouterId) {
602             SecurityManager s = System.getSecurityManager();
603             group = (s != null? s.getThreadGroup() :
604                 Thread.currentThread().getThreadGroup();
605             namePrefix = "event-router-" + eventRouterId + "-thread-";
606         }
607 
608         public Thread newThread(Runnable r) {
609             Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement()0);
610             if (t.isDaemon()) t.setDaemon(false);
611             if (t.getPriority() != Thread.NORM_PRIORITYt.setPriority(Thread.NORM_PRIORITY);
612             return t;
613         }
614     }
615 }