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 }