/*
* Copyright 2000-2009 JetBrains s.r.o.
*
* 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.napile.idea.thermit;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.Alarm;
/**
* @author Eugene Zhuravlev
* Date: Mar 22, 2007
*/
public final class AntIntrospector
{
private static final Logger LOG = Logger.getInstance(AntIntrospector.class);
private final Object myHelper;
//private static final ObjectCache<String, SoftReference<Object>> ourCache = new ObjectCache<String, SoftReference<Object>>(300);
private static final HashMap<Class, Object> ourCache = new HashMap<Class, Object>();
private static final Object ourNullObject = new Object();
private static final Alarm ourCacheCleaner = new Alarm(Alarm.ThreadToUse.SHARED_THREAD);
private static final int CACHE_CLEAN_TIMEOUT = 10000; // 10 seconds
private final Class myTypeClass;
public AntIntrospector(final Class aClass)
{
myTypeClass = aClass;
myHelper = getHelper(aClass);
}
@Nullable
public static AntIntrospector getInstance(Class c)
{
final AntIntrospector antIntrospector = new AntIntrospector(c);
return antIntrospector.myHelper == null ? null : antIntrospector;
}
private <T> T invokeMethod(@NonNls String methodName, final boolean ignoreErrors, Object... params)
{
final Class helperClass = myHelper.getClass();
final Class[] types = new Class[params.length];
try
{
for(int idx = 0; idx < params.length; idx++)
{
types[idx] = params[idx].getClass();
}
final Method method = helperClass.getMethod(methodName, types);
return (T) method.invoke(myHelper, params);
}
catch(IllegalAccessException e)
{
if(!ignoreErrors)
{
LOG.error(e);
}
}
catch(NoSuchMethodException e)
{
if(!ignoreErrors)
{
LOG.error(e);
}
}
catch(InvocationTargetException e)
{
final Throwable cause = e.getCause();
if(cause instanceof RuntimeException)
{
throw (RuntimeException) cause;
}
if(cause instanceof Error)
{
throw (Error) cause;
}
if(!ignoreErrors)
{
LOG.error(e);
}
}
return null;
}
public Set<String> getExtensionPointTypes()
{
final List<Method> methods = invokeMethod("getExtensionPoints", true);
if(methods == null || methods.size() == 0)
{
return Collections.emptySet();
}
final Set<String> types = new HashSet<String>();
for(Method method : methods)
{
final Class<?>[] paramTypes = method.getParameterTypes();
for(Class<?> paramType : paramTypes)
{
types.add(paramType.getName());
}
}
return types;
}
public Enumeration<String> getNestedElements()
{
return invokeMethod("getNestedElements", false);
}
@Nullable
public Class getElementType(String name)
{
try
{
return invokeMethod("getElementType", false, name);
}
catch(RuntimeException e)
{
return null;
}
}
public Enumeration<String> getAttributes()
{
return invokeMethod("getAttributes", false);
}
@Nullable
public Class getAttributeType(final String attr)
{
try
{
return invokeMethod("getAttributeType", false, attr);
}
catch(RuntimeException e)
{
return null;
}
}
public boolean isContainer()
{
try
{
final Object isContainer = invokeMethod("isContainer", true);
if(isContainer != null)
{
return Boolean.TRUE.equals(isContainer);
}
final ClassLoader loader = myTypeClass.getClassLoader();
try
{
final Class<?> containerClass = loader != null ? loader.loadClass(ThermitClasses.TaskContainer) : null;
return containerClass.isAssignableFrom(myTypeClass);
}
catch(ClassNotFoundException ignored)
{
}
}
catch(RuntimeException e)
{
LOG.info(e);
}
return false;
}
// for debug purposes
//private static int ourAttempts = 0;
//private static int ourHits = 0;
@Nullable
private static Object getHelper(final Class aClass)
{
final ClassLoader loader = aClass.getClassLoader();
//final String key;
//final StringBuilder builder = StringBuilderSpinAllocator.alloc();
//try {
// builder.append(aClass.getName());
// if (loader != null) {
// builder.append("_");
// builder.append(loader.hashCode());
// }
// key = builder.toString();
//}
//finally {
// StringBuilderSpinAllocator.dispose(builder);
//}
Object result = null;
synchronized(ourCache)
{
result = ourCache.get(aClass);
//final SoftReference<Object> ref = ourCache.get(aClass);
//result = (ref == null) ? null : ref.get();
//if (result == null && ref != null) {
// ourCache.remove(aClass);
//}
}
if(result == null)
{
result = ourNullObject;
Class<?> helperClass = null;
try
{
helperClass = loader != null ? loader.loadClass(ThermitClasses.IntrospectionHelper) : null;
final Method getHelperMethod = helperClass.getMethod("getHelper", Class.class);
result = getHelperMethod.invoke(null, aClass);
}
catch(ClassNotFoundException e)
{
LOG.info(e);
}
catch(NoSuchMethodException e)
{
LOG.info(e);
}
catch(IllegalAccessException e)
{
LOG.info(e);
}
catch(InvocationTargetException ignored)
{
}
synchronized(ourCache)
{
if(helperClass != null)
{
clearAntStaticCache(helperClass);
}
//ourCache.put(aClass, new SoftReference<Object>(result));
ourCache.put(aClass, result);
}
}
scheduleCacheCleaning();
return result == ourNullObject ? null : result;
}
//private static int ourClearAttemptCount = 0;
private static void clearAntStaticCache(final Class helperClass)
{
//if (++ourClearAttemptCount > 1000) { // allow not more than 1000 helpers cached inside thermit
// ourClearAttemptCount = 0;
//}
//else {
// return;
//}
// for thermit 1.7, there is a dedicated method for cache clearing
try
{
final Method method = helperClass.getDeclaredMethod("clearCache");
method.invoke(null);
}
catch(Throwable e)
{
try
{
// assume it is older version of thermit
final Field helpersField = helperClass.getDeclaredField("helpers");
final Map helpersCollection = (Map) helpersField.get(null);
helpersCollection.clear();
}
catch(Throwable _e)
{
// ignore.
}
}
}
private static void scheduleCacheCleaning()
{
ourCacheCleaner.cancelAllRequests();
ourCacheCleaner.addRequest(new Runnable()
{
public void run()
{
synchronized(ourCache)
{
ourCache.clear();
}
}
}, CACHE_CLEAN_TIMEOUT);
}
}