001    /**
002     * Copyright (C) 2012 FuseSource, Inc.
003     * http://fusesource.com
004     *
005     * Licensed under the Apache License, Version 2.0 (the "License");
006     * you may not use this file except in compliance with the License.
007     * You may obtain a copy of the License at
008     *
009     *    http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    
018    package org.fusesource.hawtdispatch;
019    
020    import java.io.InputStream;
021    import java.io.PrintWriter;
022    import java.io.StringWriter;
023    import java.util.ArrayList;
024    import java.util.Collections;
025    import java.util.HashSet;
026    import java.util.Properties;
027    import java.util.concurrent.atomic.AtomicInteger;
028    
029    import static java.lang.String.format;
030    
031    /**
032     * Base class that implements the {@link Retained} interface.
033     *
034     * @author <a href="http://hiramchirino.com">Hiram Chirino</a>
035     */
036    public class BaseRetained implements Retained {
037    
038        private static final int MAX_TRACES = Integer.getInteger("org.fusesource.hawtdispatch.BaseRetained.MAX_TRACES", 100);
039        private static final boolean TRACE = Boolean.getBoolean("org.fusesource.hawtdispatch.BaseRetained.TRACE");
040    
041        final private AtomicInteger retained = new AtomicInteger(1);
042        volatile private Task disposer;
043        /**
044         * <p>
045         * Adds a disposer runnable that is executed once the object is disposed.
046         * </p><p>
047         * A dispatch object's disposer runnable will be invoked on the object's target queue
048         * once the object's retain counter reaches zero. This disposer may be
049         * used by the application to release any resources associated with the object.
050         * </p>
051         *
052         * @param disposer
053         */
054        final public void setDisposer(final Runnable disposer) {
055            this.setDisposer(new TaskWrapper(disposer));
056        }
057    
058        /**
059         * <p>
060         * Adds a disposer runnable that is executed once the object is disposed.
061         * </p><p>
062         * A dispatch object's disposer runnable will be invoked on the object's target queue
063         * once the object's retain counter reaches zero. This disposer may be
064         * used by the application to release any resources associated with the object.
065         * </p>
066         *
067         * @param disposer
068         */
069        final public void setDisposer(Task disposer) {
070            assertRetained();
071            this.disposer = disposer;
072        }
073    
074        final public Task getDisposer() {
075            return disposer;
076        }
077    
078        /**
079         * <p>
080         * Increment the reference count of this object.
081         * </p>
082         *
083         * Calls to {@link #retain()} must be balanced with calls to
084         * {@link #release()}.
085         */
086        final public void retain() {
087            if( TRACE ) {
088                synchronized(traces) {
089                    assertRetained();
090                    final int x = retained.incrementAndGet();
091                    trace("retained", x);
092                }
093            } else {
094                assertRetained();
095                retained.getAndIncrement();
096            }
097        }
098    
099        /**
100         * <p>
101         * Decrement the reference count of this object.
102         * </p><p>
103         * An object is asynchronously disposed once all references are
104         * released. Using a disposed object will cause undefined errors.
105         * The system does not guarantee that a given client is the last or
106         * only reference to a given object.
107         * </p>
108         */
109        final public void release() {
110            if( TRACE ) {
111                synchronized(traces) {
112                    assertRetained();
113                    final int x = retained.decrementAndGet();
114                    trace("released", x);
115                    if (x == 0) {
116                        dispose();
117                        trace("disposed", x);
118                    }
119                }
120            } else {
121                assertRetained();
122                if (retained.decrementAndGet() == 0) {
123                    dispose();
124                }
125            }
126        }
127    
128        /**
129         * <p>
130         * Decrements the reference count by n.
131         * </p><p>
132         * An object is asynchronously disposed once all references are
133         * released. Using a disposed object will cause undefined errors.
134         * The system does not guarantee that a given client is the last or
135         * only reference to a given object.
136         * </p>
137         * @param n
138         */
139        final protected void release(int n) {
140            if( TRACE ) {
141                synchronized(traces) {
142                    assertRetained();
143                    int x = retained.addAndGet(-n);
144                    trace("released "+n, x);
145                    if ( x == 0) {
146                        trace("disposed", x);
147                        dispose();
148                    }
149                }
150            } else {
151                assertRetained();
152                if (retained.addAndGet(-n) == 0) {
153                    dispose();
154                }
155            }
156        }
157    
158        /**
159         * Subclasses can use this method to validate that the object has not yet been released.
160         * it will throw an IllegalStateException if it has been released.
161         *
162         * @throws IllegalStateException if the object has been released.
163         */
164        final protected void assertRetained() {
165            if( TRACE ){
166                synchronized(traces) {
167                    if( retained.get() <= 0 ) {
168                        throw new AssertionError(format("%s: Use of object not allowed after it has been released. %s", this.toString(), traces));
169                    }
170                }
171            } else {
172                assert retained.get() > 0 : format("%s: Use of object not allowed after it has been released.", this.toString());
173            }
174        }
175    
176        /**
177         * @return the retained counter
178         */
179        final public int retained() {
180            return retained.get();
181        }
182    
183        /**
184         * <p>
185         * This method will be called once the release retained reaches zero.  It causes
186         * the set disposer runnabled to be executed if not null.
187         * <p></p>
188         * Subclasses should override if they want to implement a custom disposing action in
189         * addition to the actions performed by the disposer object.
190         * </p>
191         */
192        protected void dispose() {
193            Runnable disposer = this.disposer;
194            if( disposer!=null ) {
195                disposer.run();
196            }
197        }
198    
199        final private ArrayList<String> traces = TRACE ? new ArrayList<String>(MAX_TRACES+1) : null;
200        final private void trace(final String action, final     int counter) {
201            if( traces.size() < MAX_TRACES) {
202                Exception ex = new Exception() {
203                    public String toString() {
204                        return "Trace "+(traces.size()+1)+": "+action+", counter: "+counter+", thread: "+Thread.currentThread().getName();
205                    }
206                };
207    
208                String squashed =  squash(ex.getStackTrace());
209                if( squashed == null ) {
210                    StringWriter sw = new StringWriter();
211                    ex.printStackTrace(new PrintWriter(sw));
212                    traces.add("\n"+sw);
213                }
214    //            else {
215    //                traces.add("\n"+ex.toString()+"\n  at "+squashed+"\n");
216    //            }
217            }  else if (traces.size() == MAX_TRACES) {
218                traces.add("MAX_TRACES reached... no more traces will be recorded.");
219            }
220        }
221    
222        //
223        // Hide system generated balanced calls to retain/release since the tracing facility is
224        // here to help end users figure out where THEY are failing to pair up the calls. 
225        //
226        static private String squash(StackTraceElement[] st) {
227            if( st.length > 2) {
228                final String traceData = st[2].getClassName()+"."+st[2].getMethodName();
229                if( CALLERS.contains(traceData) ) {
230                    return traceData;
231                }
232            }
233            return null;
234        }
235    
236    
237        static HashSet<String> CALLERS = new HashSet<String>();
238        static {
239            if( TRACE ) {
240                Properties p = new Properties();
241                final InputStream is = BaseRetained.class.getResourceAsStream("BaseRetained.CALLERS");
242                try {
243                    p.load(is);
244                } catch (Exception ignore) {
245                    ignore.printStackTrace();
246                } finally {
247                    try {
248                        is.close();
249                    } catch (Exception ignore) {
250                    }
251                }
252                for (Object key : Collections.list(p.keys())) {
253                    CALLERS.add((String) key);
254                }
255            }
256        }
257    
258    }