/*
* Copyright (C) 2014 Francis Galiegue <fgaliegue@gmail.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 com.github.fge.grappa.transform;
import com.github.fge.grappa.misc.AsmUtils;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.objectweb.asm.Type;
import javax.annotation.ParametersAreNonnullByDefault;
import java.lang.reflect.Array;
/**
* A class cache using a Guava {@link LoadingCache}
*
* <p>Only one cache exists for each instance.</p>
*/
public final class ClassCache
{
public static final ClassCache INSTANCE = new ClassCache();
private final LoadingCache<String, Class<?>> cache;
private ClassCache()
{
cache = CacheBuilder.newBuilder().build(ClassCacheLoader.LOADER);
}
/**
* Load a class from a JVM string class descriptor (for instance {@code
* Ljava/lang/String}
*
* <p>Note that for arrays, the class of the array elements is returned.</p>
*
* <p>Note also that this uses {@link LoadingCache#getUnchecked(Object)} to
* retrieve the result.</p>
*
* @param className the internal name
* @return a class
* @throws UncheckedExecutionException cannot load the class
*/
public Class<?> loadClass(final String className)
{
return cache.getUnchecked(className);
}
@ParametersAreNonnullByDefault
private static final class ClassCacheLoader
extends CacheLoader<String, Class<?>>
{
private static final ClassCacheLoader LOADER = new ClassCacheLoader();
/**
* Computes or retrieves the value corresponding to {@code key}.
*
* @param key the non-null key whose value should be loaded
* @return the value associated with {@code key}; <b>must not be
* null</b>
*
* @throws ClassNotFoundException if unable to load the result
* @throws InterruptedException if this method is interrupted. {@code
* InterruptedException} is
* treated like any other {@code Exception} in all respects except
* that, when it is caught,
* the thread's interrupt status is set
*/
@Override
public Class<?> load(final String key)
throws ClassNotFoundException
{
// Array...
if (key.startsWith("[")) {
final String elementName = key.substring(1);
final Type type = Type.getType(elementName);
final Class<?> c = AsmUtils.getClassForType(type);
return Array.newInstance(c, 0).getClass();
}
final String name = key.replace('/', '.');
ClassLoader cl = ClassCacheLoader.class.getClassLoader();
try {
return cl.loadClass(name);
} catch (ClassNotFoundException ignored) {
cl = Thread.currentThread().getContextClassLoader();
return cl.loadClass(name);
}
}
}
}