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
018package org.fusesource.hawtdispatch;
019
020import java.io.InputStream;
021import java.io.PrintWriter;
022import java.io.StringWriter;
023import java.util.ArrayList;
024import java.util.Collections;
025import java.util.HashSet;
026import java.util.Properties;
027import java.util.concurrent.atomic.AtomicInteger;
028
029import 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 */
036public 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}