/* * Copyright (c) 2007, 2011, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.tools.visualvm.core.model; import com.sun.tools.visualvm.core.datasource.DataSource; import com.sun.tools.visualvm.core.datasupport.ClassNameComparator; import com.sun.tools.visualvm.core.datasupport.DataChangeListener; import com.sun.tools.visualvm.core.datasupport.DataChangeSupport; import java.lang.ref.Reference; import java.lang.ref.SoftReference; import java.lang.ref.WeakReference; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.Map; import java.util.SortedSet; import java.util.TreeSet; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.logging.Logger; /** * This is abstact factory class for getting model * for datasource. It as two functions. First it serves * as cache for the model associated with datasource. * Second, ModelFactory uses list of ModelProviders registered * via {@link #registerProvider(ModelProvider )} to * determine the order of in which they are consulted * to obtain the model for dataSource. First model * obtained from ModelProvider is associated with * dataSource and returned in the future. * @author Tomas Hurka */ public abstract class ModelFactory<M extends Model,D extends DataSource> { final protected static Logger LOGGER = Logger.getLogger(ModelFactory.class.getName()); /** special marker for null model */ private final Reference<M> NULL_MODEL; /** set of registered providers */ private SortedSet<ModelProvider<M, D>> providers; /** providers cannot be changed, when getModel() is running */ private ReadWriteLock providersLock; /** model cache */ private Map<DataSourceKey<D>,Reference<M>> modelCache; /** asynchronous change support */ private DataChangeSupport<ModelProvider<M, D>> factoryChange; protected ModelFactory() { NULL_MODEL = new SoftReference(null); providers = new TreeSet(new ModelProviderComparator()); modelCache = Collections.synchronizedMap(new HashMap()); factoryChange = new DataChangeSupport(); providersLock = new ReentrantReadWriteLock(); } /** * Returns model for dataSource. If model is in the cache * return it, otherwise consult registered ModelProviders. * @param dataSource {@link DataSource} for which {@link Model} should be returned * @return model for dataSource or <CODE>null</CODE> * if there is not model associated with this dataSource. */ public final M getModel(D dataSource) { // take a read lock for providers Lock rlock = providersLock.readLock(); rlock.lock(); try { // allow concurrent access to cache for different instances of DataSource // note that DataSourceKey uses reference-equality in place of object-equality // for DataSource synchronized (dataSource) { DataSourceKey<D> key = new DataSourceKey(dataSource); Reference<M> modelRef = modelCache.get(key); M model = null; if (modelRef != null) { if (modelRef == NULL_MODEL) { // cached null model, return null return null; } model = modelRef.get(); // if model is in cache return it, if (model != null) { // otherwise get it from providers return model; } } // try to get model from registered providers for (ModelProvider<M, D> factory : providers) { model = factory.createModelFor(dataSource); if (model != null) { // we have model, put it into cache modelCache.put(key,new SoftReference(model)); break; } } if (model == null) { // model was not found - cache null model modelCache.put(key,NULL_MODEL); } return model; } } finally { rlock.unlock(); } } /** * register new {@link ModelProvider}. * Model provider can be registered only once. * @param newProvider to register * @return <CODE>true</CODE> if this ModelFactory does not contain registered provider. */ public final boolean registerProvider(ModelProvider<M, D> newProvider) { // take a write lock on providers Lock wlock = providersLock.writeLock(); wlock.lock(); try { LOGGER.finer("Registering " + newProvider.getClass().getName()); // NOI18N boolean added = providers.add(newProvider); if (added) { clearCache(); factoryChange.fireChange(providers,Collections.singleton(newProvider),null); } return added; } finally { wlock.unlock(); } } /** * Unregister {@link ModelProvider}. * @param oldProvider provider, which should be unregistered * @return <CODE>true</CODE> if provider was unregistered. */ public final boolean unregisterProvider(ModelProvider<M, D> oldProvider) { // take a write lock on providers Lock wlock = providersLock.writeLock(); wlock.lock(); try { LOGGER.finer("Unregistering " + oldProvider.getClass().getName()); // NOI18N boolean removed = providers.remove(oldProvider); if (removed) { clearCache(); factoryChange.fireChange(providers,null,Collections.singleton(oldProvider)); } return removed; } finally { wlock.unlock(); } } /** * Add data change listener. Data change is fired when * {@link ModelProvider} is registered/unregister. * @param listener {@link DataChangeListener} to be added */ public final void addFactoryChangeListener(DataChangeListener<ModelProvider<M, D>> listener) { factoryChange.addChangeListener(listener); } /** * Remove data change listener. * @param listener {@link DataChangeListener} to be removed */ public final void removeFactoryChangeListener(DataChangeListener<ModelProvider<M, D>> listener) { factoryChange.removeChangeListener(listener); } /** * Default priority. Subclass of ModelFactory can implement * {@link ModelProvider}. In that case such provider should be * used as last provider. This is ensured by returning -1, since * providers, which subclass {@link AbstractModelProvider}, return * positive numbers. * @return -1 */ public int priority() { return -1; } private void clearCache() { modelCache.clear(); } /** compare ModelProvider-s using priority. Providers with higher priority * gets precedence over those with lower priority */ private class ModelProviderComparator implements Comparator<ModelProvider<M,D>> { public int compare(ModelProvider<M, D> provider1, ModelProvider<M, D> provider2) { int thisVal = provider1.priority(); int anotherVal = provider2.priority(); if (thisVal<anotherVal) { return 1; } if (thisVal>anotherVal) { return -1; } // same depth -> use class name to create artifical ordering return ClassNameComparator.INSTANCE.compare(provider1, provider2); } } /** * DataSource wrapper object, which weakly reference datasource and uses * reference-equality of DataSources when implementing hashCode and equals * this class is used as keys in modelCache */ private static class DataSourceKey<D extends DataSource> { Reference<D> weakReference; DataSourceKey(D ds) { weakReference = new WeakReference(ds); } public int hashCode() { D ds = weakReference.get(); if (ds != null) { return ds.hashCode(); } return 0; } public boolean equals(Object obj) { if (obj == null) return false; if (obj instanceof DataSourceKey) { D ds = weakReference.get(); D otherDs = ((DataSourceKey<D>)obj).weakReference.get(); return ds != null && ds == otherDs; } throw new IllegalArgumentException(obj.getClass().getName()); } public String toString() { DataSource ds = weakReference.get(); return "DataSourceKey for "+System.identityHashCode(this)+" for "+ds==null?"NULL":ds.toString(); // NOI18N } } }