/** * Copyright (C) 2012 FuseSource, Inc. * http://fusesource.com * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.fusesource.hawtdispatch; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import static java.lang.String.format; /** * Base class that implements the {@link Retained} interface. * * @author <a href="http://hiramchirino.com">Hiram Chirino</a> */ public class BaseRetained implements Retained { private static final int MAX_TRACES = Integer.getInteger("org.fusesource.hawtdispatch.BaseRetained.MAX_TRACES", 100); private static final boolean TRACE = Boolean.getBoolean("org.fusesource.hawtdispatch.BaseRetained.TRACE"); final private AtomicInteger retained = new AtomicInteger(1); volatile private Task disposer; /** * <p> * Adds a disposer runnable that is executed once the object is disposed. * </p><p> * A dispatch object's disposer runnable will be invoked on the object's target queue * once the object's retain counter reaches zero. This disposer may be * used by the application to release any resources associated with the object. * </p> * * @param disposer */ final public void setDisposer(final Runnable disposer) { this.setDisposer(new TaskWrapper(disposer)); } /** * <p> * Adds a disposer runnable that is executed once the object is disposed. * </p><p> * A dispatch object's disposer runnable will be invoked on the object's target queue * once the object's retain counter reaches zero. This disposer may be * used by the application to release any resources associated with the object. * </p> * * @param disposer */ final public void setDisposer(Task disposer) { assertRetained(); this.disposer = disposer; } final public Task getDisposer() { return disposer; } /** * <p> * Increment the reference count of this object. * </p> * * Calls to {@link #retain()} must be balanced with calls to * {@link #release()}. */ final public void retain() { if( TRACE ) { synchronized(traces) { assertRetained(); final int x = retained.incrementAndGet(); trace("retained", x); } } else { assertRetained(); retained.getAndIncrement(); } } /** * <p> * Decrement the reference count of this object. * </p><p> * An object is asynchronously disposed once all references are * released. Using a disposed object will cause undefined errors. * The system does not guarantee that a given client is the last or * only reference to a given object. * </p> */ final public void release() { if( TRACE ) { synchronized(traces) { assertRetained(); final int x = retained.decrementAndGet(); trace("released", x); if (x == 0) { dispose(); trace("disposed", x); } } } else { assertRetained(); if (retained.decrementAndGet() == 0) { dispose(); } } } /** * <p> * Decrements the reference count by n. * </p><p> * An object is asynchronously disposed once all references are * released. Using a disposed object will cause undefined errors. * The system does not guarantee that a given client is the last or * only reference to a given object. * </p> * @param n */ final protected void release(int n) { if( TRACE ) { synchronized(traces) { assertRetained(); int x = retained.addAndGet(-n); trace("released "+n, x); if ( x == 0) { trace("disposed", x); dispose(); } } } else { assertRetained(); if (retained.addAndGet(-n) == 0) { dispose(); } } } /** * Subclasses can use this method to validate that the object has not yet been released. * it will throw an IllegalStateException if it has been released. * * @throws IllegalStateException if the object has been released. */ final protected void assertRetained() { if( TRACE ){ synchronized(traces) { if( retained.get() <= 0 ) { throw new AssertionError(format("%s: Use of object not allowed after it has been released. %s", this.toString(), traces)); } } } else { assert retained.get() > 0 : format("%s: Use of object not allowed after it has been released.", this.toString()); } } /** * @return the retained counter */ final public int retained() { return retained.get(); } /** * <p> * This method will be called once the release retained reaches zero. It causes * the set disposer runnabled to be executed if not null. * <p></p> * Subclasses should override if they want to implement a custom disposing action in * addition to the actions performed by the disposer object. * </p> */ protected void dispose() { Runnable disposer = this.disposer; if( disposer!=null ) { disposer.run(); } } final private ArrayList<String> traces = TRACE ? new ArrayList<String>(MAX_TRACES+1) : null; final private void trace(final String action, final int counter) { if( traces.size() < MAX_TRACES) { Exception ex = new Exception() { public String toString() { return "Trace "+(traces.size()+1)+": "+action+", counter: "+counter+", thread: "+Thread.currentThread().getName(); } }; String squashed = squash(ex.getStackTrace()); if( squashed == null ) { StringWriter sw = new StringWriter(); ex.printStackTrace(new PrintWriter(sw)); traces.add("\n"+sw); } // else { // traces.add("\n"+ex.toString()+"\n at "+squashed+"\n"); // } } else if (traces.size() == MAX_TRACES) { traces.add("MAX_TRACES reached... no more traces will be recorded."); } } // // Hide system generated balanced calls to retain/release since the tracing facility is // here to help end users figure out where THEY are failing to pair up the calls. // static private String squash(StackTraceElement[] st) { if( st.length > 2) { final String traceData = st[2].getClassName()+"."+st[2].getMethodName(); if( CALLERS.contains(traceData) ) { return traceData; } } return null; } static HashSet<String> CALLERS = new HashSet<String>(); static { if( TRACE ) { Properties p = new Properties(); final InputStream is = BaseRetained.class.getResourceAsStream("BaseRetained.CALLERS"); try { p.load(is); } catch (Exception ignore) { ignore.printStackTrace(); } finally { try { is.close(); } catch (Exception ignore) { } } for (Object key : Collections.list(p.keys())) { CALLERS.add((String) key); } } } }