View Javadoc
1   /*
2    * Copyright 2007 Jim Procter
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
5    * in compliance with the License. You may obtain a copy of the License at
6    *
7    * http://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software distributed under the License
10   * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
11   * or implied. See the License for the specific language governing permissions and limitations under
12   * the License.
13   */
14  package org.castor.core.util;
15  
16  import java.util.IdentityHashMap;
17  import java.util.concurrent.locks.ReentrantReadWriteLock;
18  
19  /**
20   * <p>
21   * lightweight mechanism for thread-safe detection of cyclic calls to hashCode or equals in objects
22   * created by the XML CodeGenerator.
23   * </p>
24   * <p>
25   * Usage
26   * </p>
27   * <ol>
28   * <li>startingToCycle is called on a particular object prior to recursing on it, and recursion
29   * should only occur if this call returns false.</li>
30   * <li>releaseCycleHandle is called after the recursive call returns in order to release the cycle
31   * lock on the object.</li>
32   * </ol>
33   * <p>
34   * <strong>Note :</strong> Do not use this cycle breaking mechanism on object comparisons where two
35   * instances may share the same reference to some third object, such as a String constant.
36   * </p>
37   * 
38   * @author <a href="mailto:jimp@compbio.dundee.ac.uk">Jim Procter</a>
39   */
40  public class CycleBreaker {
41  
42    /**
43     * Hash of threads and objects that we are keeping track of.
44     */
45    private static final IdentityHashMap<Thread, IdentityHashMap<Object, Object>> _threadHash =
46        new IdentityHashMap<Thread, IdentityHashMap<Object, Object>>();
47  
48    /**
49     * Lock used to isolate write accesses to <tt>_threadHash</tt>.
50     * 
51     * @since 1.3.1
52     */
53    private static final ReentrantReadWriteLock _lock = new ReentrantReadWriteLock();
54  
55    /**
56     * Test to see if we are about to begin cycling on a method call to beingHashed.
57     * 
58     * @param beingHashed the object to check for a cycle.
59     * @return true if a cycle is about to occur on this non-null object.
60     */
61    public static boolean startingToCycle(final Object beingHashed) {
62      if (beingHashed == null) {
63        return false;
64      }
65  
66      // before accessing _threadHash acquire read lock first
67      _lock.readLock().lock();
68      IdentityHashMap<Object, Object> hthr = _threadHash.get(Thread.currentThread());
69      _lock.readLock().unlock();
70      if (hthr == null) {
71        hthr = new IdentityHashMap<Object, Object>();
72        hthr.put(beingHashed, beingHashed);
73  
74        // before accessing _threadHash acquire write lock first
75        _lock.writeLock().lock();
76        _threadHash.put(Thread.currentThread(), hthr);
77        _lock.writeLock().unlock();
78        return false; // first call. no cycle detected
79      }
80  
81      // it's not necessary to lock here because every entry
82      // read from _threadHash is accessed by the same thread which
83      // added it to _threadHash (in _threadHash all values are keyed
84      // by the threads hash)
85  
86      Object objhandle = hthr.get(beingHashed);
87      if (objhandle == null) {
88  
89        // this is the default for a hash value currently being computed.
90        hthr.put(beingHashed, beingHashed);
91        return false; // first call. no cycle detected.
92      }
93      return true;
94    }
95  
96    /**
97     * Called to release Cycling lock for this object at the end of a routine where cycles are to be
98     * detected.
99     * 
100    * @param beingHashed the object for which the cycle-lock will be released.
101    */
102   public static void releaseCycleHandle(final Object beingHashed) {
103     if (beingHashed == null) {
104       return;
105     }
106 
107     Thread currentThread = Thread.currentThread();
108 
109     // before accessing _threadHash acquire read lock first
110     _lock.readLock().lock();
111     IdentityHashMap<Object, Object> hthr = _threadHash.get(currentThread);
112     _lock.readLock().unlock();
113     if (hthr != null) {
114 
115       // it's not necessary to lock here because every entry
116       // read from _threadHash is accessed by the same thread which
117       // added it to _threadHash (in _threadHash all values are keyed
118       // by the threads hash)
119       if (hthr.containsKey(beingHashed)) {
120         hthr.remove(beingHashed);
121 
122         // release any references if we have no more CycleHandles
123         if (hthr.isEmpty()) {
124 
125           // before removing the threads empty hash from _threadHash
126           // acquire write lock first
127           _lock.writeLock().lock();
128           _threadHash.remove(currentThread);
129           _lock.writeLock().unlock();
130         }
131       }
132     }
133   }
134 }