/*
* JBoss, Home of Professional Open Source.
* Copyright 2008, Red Hat Middleware LLC, and individual contributors
* as indicated by the @author tags. See the copyright.txt file in the
* distribution for a full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.wildfly.iiop.openjdk.rmi;
import java.lang.ref.SoftReference;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.wildfly.iiop.openjdk.logging.IIOPLogger;
/**
* Instances of this class cache the most complex analyse types.
* <p/>
* The analyse types cached are:
* <ul>
* <li><code>InterfaceAnalysis</code> for interfaces.</li>
* <li><code>ValueAnalysis</code> for value types.</li>
* <li><code>ExceptionAnalysis</code> for exceptions.</li>
* </ul>
* <p/>
* Besides caching work already done, this caches work in progress,
* as we need to know about this to handle cyclic graphs of analyses.
* When a thread re-enters the <code>getAnalysis()</code> method, an
* unfinished analysis will be returned if the same thread is already
* working on this analysis.
*
* @author <a href="mailto:osh@sparre.dk">Ole Husgaard</a>
*/
class WorkCacheManager {
/**
* The analysis constructor of our analysis class.
* This constructor takes a single argument of type <code>Class</code>.
*/
private final Constructor constructor;
/**
* The analysis initializer of our analysis class.
* This method takes no arguments, and is named doAnalyze.
*/
private final Method initializer;
/**
* This maps the classes of completely done analyses to soft
* references of their analysis.
*/
private final Map<Class, SoftReference<ContainerAnalysis>> workDone;
/**
* This maps the classes of analyses in progress to their
* analysis.
*/
private final Map<InProgressKey, ContainerAnalysis> workInProgress;
private final Map<ClassLoader, Set<Class<?>>> classesByLoader;
/**
* Create a new work cache manager.
*
* @param cls The class of the analysis type we cache here.
*/
WorkCacheManager(final Class cls) {
// Find the constructor and initializer.
try {
constructor = cls.getDeclaredConstructor(new Class[]{Class.class});
initializer = cls.getDeclaredMethod("doAnalyze");
} catch (NoSuchMethodException ex) {
throw IIOPLogger.ROOT_LOGGER.unexpectedException(ex);
}
workDone = new HashMap<Class, SoftReference<ContainerAnalysis>>();
workInProgress = new HashMap<InProgressKey, ContainerAnalysis>();
classesByLoader = new HashMap<ClassLoader, Set<Class<?>>>();
}
public void clearClassLoader(final ClassLoader cl) {
Set<Class<?>> classes = classesByLoader.remove(cl);
if(classes != null) {
for(Class<?> clazz : classes) {
workDone.remove(clazz);
}
}
}
/**
* Returns an analysis.
* If the calling thread is currently doing an analysis of this
* class, an unfinished analysis is returned.
*/
ContainerAnalysis getAnalysis(final Class cls) throws RMIIIOPViolationException {
ContainerAnalysis ret = null;
boolean created = false;
try {
synchronized (this) {
ret = lookupDone(cls);
if (ret != null) {
return ret;
}
// is it work-in-progress?
final ContainerAnalysis inProgress = workInProgress.get(new InProgressKey(cls, Thread.currentThread()));
if (inProgress != null) {
return inProgress; // return unfinished
// Do not wait for the other thread: We may deadlock
// Double work is better that deadlock...
}
ret = createWorkInProgress(cls);
}
created = true;
// Do the work
doTheWork(cls, ret);
} finally {
// We did it
synchronized (this) {
if(created) {
workInProgress.remove(new InProgressKey(cls, Thread.currentThread()));
workDone.put(cls, new SoftReference<ContainerAnalysis>(ret));
ClassLoader classLoader = cls.getClassLoader();
if (classLoader != null) {
Set<Class<?>> classes = classesByLoader.get(classLoader);
if (classes == null) {
classesByLoader.put(classLoader, classes = new HashSet<Class<?>>());
}
classes.add(cls);
}
}
notifyAll();
}
}
return ret;
}
/**
* Lookup an analysis in the fully done map.
*/
private ContainerAnalysis lookupDone(Class cls) {
SoftReference ref = (SoftReference) workDone.get(cls);
if (ref == null)
return null;
ContainerAnalysis ret = (ContainerAnalysis) ref.get();
if (ret == null)
workDone.remove(cls); // clear map entry if soft ref. was cleared.
return ret;
}
/**
* Create new work-in-progress.
*/
private ContainerAnalysis createWorkInProgress(final Class cls) {
final ContainerAnalysis analysis;
try {
analysis = (ContainerAnalysis) constructor.newInstance(cls);
} catch (InstantiationException ex) {
throw new RuntimeException(ex.toString());
} catch (IllegalAccessException ex) {
throw new RuntimeException(ex.toString());
} catch (InvocationTargetException ex) {
throw new RuntimeException(ex.toString());
}
workInProgress.put(new InProgressKey(cls, Thread.currentThread()), analysis);
return analysis;
}
private void doTheWork(final Class cls, final ContainerAnalysis ret)
throws RMIIIOPViolationException {
try {
initializer.invoke(ret);
} catch (Throwable t) {
synchronized (this) {
workInProgress.remove(new InProgressKey(cls, Thread.currentThread()));
}
if (t instanceof InvocationTargetException) // unwrap
t = ((InvocationTargetException) t).getTargetException();
if (t instanceof RMIIIOPViolationException)
throw (RMIIIOPViolationException) t;
if (t instanceof RuntimeException)
throw (RuntimeException) t;
if (t instanceof Error)
throw (Error) t;
throw new RuntimeException(t.toString());
}
}
private static class InProgressKey {
final Class<?> clazz;
final Thread thread;
private InProgressKey(Class<?> clazz, Thread thread) {
this.clazz = clazz;
this.thread = thread;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InProgressKey that = (InProgressKey) o;
if (clazz != null ? !clazz.equals(that.clazz) : that.clazz != null) return false;
if (thread != null ? !thread.equals(that.thread) : that.thread != null) return false;
return true;
}
@Override
public int hashCode() {
int result = clazz != null ? clazz.hashCode() : 0;
result = 31 * result + (thread != null ? thread.hashCode() : 0);
return result;
}
}
}