/**
* Copyright (C) 2013 Kametic <epo.jemba@kametic.com>
*
* Licensed under the GNU LESSER GENERAL PUBLIC LICENSE, Version 3, 29 June 2007;
* or any later version
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.gnu.org/licenses/lgpl-3.0.txt
*
* 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.nuunframework.kernel.internal.scanner;
import static org.reflections.util.FilterBuilder.prefix;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import org.nuunframework.kernel.KernelException;
import org.nuunframework.kernel.annotations.Ignore;
import org.nuunframework.kernel.commons.AssertUtils;
import org.nuunframework.kernel.commons.specification.Specification;
import org.reflections.ReflectionUtils;
import org.reflections.Reflections;
import org.reflections.Store;
import org.reflections.scanners.ResourcesScanner;
import org.reflections.scanners.Scanner;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.scanners.TypeAnnotationsScanner;
import org.reflections.scanners.TypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import org.reflections.util.FilterBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Predicate;
import com.google.common.collect.Collections2;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
class ClasspathScannerInternal implements ClasspathScanner
{
Logger logger = LoggerFactory.getLogger(ClasspathScannerInternal.class);
private final List<String> packageRoots;
private final boolean reachAbstractClass;
private final ClasspathStrategy classpathStrategy;
private Set<URL> additionalClasspath;
private Set<URL> urls;
private final List<ScannerCommand> commands;
public ClasspathScannerInternal(ClasspathStrategy classpathStrategy, String... packageRoots_)
{
this(classpathStrategy, false, null, packageRoots_);
}
public ClasspathScannerInternal(ClasspathStrategy classpathStrategy, boolean reachAbstractClass, String packageRoot, String... packageRoots_)
{
this.packageRoots = new LinkedList<String>();
if (packageRoot != null)
{
this.packageRoots.add(packageRoot);
}
for (String packageRoot_ : packageRoots_)
{
this.packageRoots.add(packageRoot_);
}
this.reachAbstractClass = reachAbstractClass;
this.classpathStrategy = classpathStrategy;
commands = new ArrayList<ClasspathScannerInternal.ScannerCommand>();
}
static interface ScannerCommand
{
void execute (Reflections reflections);
Scanner scanner ();
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForAnnotation(final Class<? extends Annotation> annotationType , final Callback callback)
{
// ConfigurationBuilder configurationBuilder = configurationBuilder();
// Set<URL> computeUrls = computeUrls();
// Reflections reflections = new Reflections(configurationBuilder.addUrls(computeUrls).setScanners(new TypeAnnotationsScanner()));
ScannerCommand command = new ScannerCommand()
{
@Override
public Scanner scanner()
{
return new TypeAnnotationsScanner();
}
@Override
public void execute(Reflections reflections)
{
Collection<Class<?>> typesAnnotatedWith = reflections.getTypesAnnotatedWith(annotationType);
if (typesAnnotatedWith == null)
{
typesAnnotatedWith = Collections.emptySet();
}
callback.callback((Collection) postTreatment((Collection) typesAnnotatedWith));
}
};
queue(command);
}
private void queue(ScannerCommand command)
{
commands.add(command);
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForMetaAnnotation(final Class<? extends Annotation> annotationType , final Callback callback)
{
// ConfigurationBuilder configurationBuilder = configurationBuilder();
// Set<URL> computeUrls = computeUrls();
// Reflections reflections = new Reflections(configurationBuilder.addUrls(computeUrls).setScanners(new TypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Multimap<String, String> multimap = reflections.getStore().get(TypesScanner.class);
Collection<Class<?>> typesAnnotatedWith = Sets.newHashSet();
for (String className : multimap.keys())
{
Class<?> klass = toClass(className);
if (annotationType != null && klass != null && AssertUtils.hasAnnotationDeep(klass, annotationType) && !klass.isAnnotation())
{
typesAnnotatedWith.add(klass);
}
}
callback.callback((Collection) postTreatment((Collection) typesAnnotatedWith));
}
@Override
public Scanner scanner()
{
return new TypesScanner();
}
});
}
@Override
public void scanClasspathForMetaAnnotationRegex(final String metaAnnotationRegex , final Callback callback)
{
// ConfigurationBuilder configurationBuilder = configurationBuilder();
// Set<URL> computeUrls = computeUrls();
// Reflections reflections = new Reflections(configurationBuilder.addUrls(computeUrls).setScanners(new TypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Multimap<String, String> multimap = reflections.getStore().get(TypesScanner.class);
Collection<Class<?>> typesAnnotatedWith = Sets.newHashSet();
for( String className : multimap.keys())
{
Class<?> klass = toClass(className);
if ( metaAnnotationRegex != null && klass != null&& AssertUtils.hasAnnotationDeepRegex(klass, metaAnnotationRegex) && ! klass.isAnnotation() )
// if ( annotationType != null && klass != null && AssertUtils.hasAnnotationDeep(klass, annotationType) && ! klass.isAnnotation() )
{
typesAnnotatedWith.add(klass);
}
}
callback.callback((Collection) postTreatment((Collection) typesAnnotatedWith));
}
@Override
public Scanner scanner()
{
return new TypesScanner();
}
});
}
private Class<?> toClass(String candidate)
{
try
{
return Class.forName(candidate);
}
catch (Throwable e)
{
logger.debug("String to Class : " + e.getMessage() );
}
return null;
}
static class IgnorePredicate implements Predicate<Class<?>>
{
Logger logger = LoggerFactory.getLogger(ClasspathScannerInternal.IgnorePredicate.class);
private final boolean reachAbstractClass;
public IgnorePredicate(boolean reachAbstractClass)
{
this.reachAbstractClass = reachAbstractClass;
}
@Override
public boolean apply(Class<?> clazz)
{
logger.trace("Checking {} for Ignore", clazz.getName());
boolean toKeep = true;
if ((Modifier.isAbstract(clazz.getModifiers()) && !reachAbstractClass) && (!clazz.isInterface()))
{
toKeep = false;
}
for (Annotation annotation : clazz.getAnnotations())
{
logger.trace("Checking annotation {} for Ignore", annotation.annotationType().getName());
if (annotation.annotationType().equals(Ignore.class) || annotation.annotationType().getName().endsWith("Ignore"))
{
toKeep = false;
}
logger.trace("Result tokeep = {}.", toKeep);
if (!toKeep)
{
break;
}
}
return toKeep;
}
}
private Collection<Class<?>> postTreatment(Collection<Class<?>> set)
{
// Sanity Check : throw a KernelException if one of the returned classes is null
for (Class<?> class1 : set)
{
if (null == class1)
{
throw new KernelException("Scanned classes results can not be null. Please check Integrity of the classes.");
}
}
Collection<Class<?>> filtered = Collections2.filter(set, new IgnorePredicate(reachAbstractClass));
return filtered;
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForAnnotationRegex(final String annotationTypeRegex, final Callback callback)
{
// Reflections reflections = new Reflections(configurationBuilder().addUrls(computeUrls()).setScanners(new TypeAnnotationsScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Store store = reflections.getStore();
Multimap<String, String> multimap = store.get(TypeAnnotationsScanner.class);
List<String> key = new ArrayList<String>();
for (String loopKey : multimap.keySet())
{
if (loopKey.matches(annotationTypeRegex))
{
key.add(loopKey);
}
}
Collection<Class<?>> typesAnnotatedWith = new HashSet<Class<?>>();
for (String k : key)
{
Collection<String> collectionOfString = multimap.get(k);
typesAnnotatedWith.addAll(toClasses(collectionOfString));
}
callback .callback((Collection) postTreatment((Collection) typesAnnotatedWith));
}
@Override
public Scanner scanner()
{
return new TypeAnnotationsScanner();
}
});
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForTypeRegex(final String typeName, final Callback callback)
{
// Reflections reflections = new Reflections(configurationBuilder().addUrls(computeUrls()).setScanners(new TypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Store store = reflections.getStore();
Multimap<String, String> multimap = store.get(TypesScanner.class);
Collection<String> collectionOfString = new HashSet<String>();
for (String loopKey : multimap.keySet())
{
if (loopKey.matches(typeName))
{
collectionOfString.add(loopKey);
}
}
Collection<Class<?>> types = null;
if (collectionOfString.size() > 0)
{
types = toClasses(collectionOfString);
}
else
{
types = Collections.emptySet();
}
callback.callback((Collection) postTreatment((Collection) types));
}
@Override
public Scanner scanner()
{
return new TypesScanner();
}
});
}
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForSpecification(final Specification<Class<?>> specification , final Callback callback)
{
// Reflections reflections = new Reflections(configurationBuilder().addUrls(computeUrls()).setScanners(new TypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Store store = reflections.getStore();
Multimap<String, String> multimap = store.get(TypesScanner.class);
Collection<String> collectionOfString = multimap.keySet();
Collection<Class<?>> types = null;
Collection<Class<?>> filteredTypes = new HashSet<Class<?>>();
// Convert String to classes
if (collectionOfString.size() > 0)
{
types = toClasses(collectionOfString);
}
else
{
types = Collections.emptySet();
}
// Filter via specification
for (Class<?> candidate : types)
{
if (specification.isSatisfiedBy(candidate))
{
filteredTypes.add(candidate);
}
}
callback.callback((Collection) postTreatment((Collection) filteredTypes));
}
@Override
public Scanner scanner()
{
return new TypesScanner();
}
});
}
@SuppressWarnings({})
@Override
public void scanClasspathForSubTypeRegex(final String subTypeName , final Callback callback)
{
// Reflections reflections = new Reflections(configurationBuilder().addUrls(computeUrls()).setScanners(new TypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
// empty just add
}
@Override
public Scanner scanner()
{
return new SubTypesScanner();
}
});
queue(new ScannerCommand()
{
@SuppressWarnings({
"rawtypes", "unchecked"
})
@Override
public void execute(Reflections reflections)
{
Store store = reflections.getStore();
Multimap<String, String> multimap = store.get(TypesScanner.class);
Collection<String> collectionOfString = new HashSet<String>();
for (String loopKey : multimap.keySet())
{
if (loopKey.matches(subTypeName))
{
collectionOfString.add(loopKey);
}
}
Collection<Class<?>> types = null;
if (collectionOfString.size() > 0)
{
types = toClasses(collectionOfString);
}
else
{
types = Collections.emptySet();
}
// Then find subclasses of types
Collection<Class<?>> finalClasses = new HashSet<Class<?>>();
for (Class<?> subType : types)
{
// Collection<Class<?>> scanClasspathForSubTypeClass =
// scanClasspathForSubTypeClass(class1);
// ///////////////////////////////
Collection<?> typesAnnotatedWith = (Collection<?>) reflections.getSubTypesOf(subType);
if (typesAnnotatedWith == null)
{
typesAnnotatedWith = Collections.emptySet();
}
// ///////////////////////////////
finalClasses.addAll((Collection) postTreatment((Collection) typesAnnotatedWith));
}
// removed ignored already done
callback.callback(finalClasses);
// return (Collection) removeIgnore((Collection)types);
}
@Override
public Scanner scanner()
{
return new TypesScanner();
}
});
}
@Override
public void scanClasspathForResource(final String pattern , final CallbackResources callback)
{
// Reflections reflections = new Reflections(new ConfigurationBuilder().addUrls(computeUrls()).setScanners(new ResourcesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Set<String> resources = reflections.getResources(Pattern.compile(pattern));
callback.callback(resources);
}
@Override
public Scanner scanner()
{
return new ResourcesScanner();
}
});
}
// queue(new ScannerCommand()
// {
// @Override
// public void execute(Reflections reflections)
// {
//
// }
//
// @Override
// public Scanner scanner()
// {
// return
// }
//
// });
@SuppressWarnings({
"unchecked", "rawtypes"
})
@Override
public void scanClasspathForSubTypeClass(final Class<?> subType , final Callback callback)
{
// Reflections reflections = new Reflections(configurationBuilder().addUrls(computeUrls()).setScanners(new SubTypesScanner()));
queue(new ScannerCommand()
{
@Override
public void execute(Reflections reflections)
{
Collection<?> typesAnnotatedWith = (Collection<?>) reflections.getSubTypesOf(subType);
if (typesAnnotatedWith == null)
{
typesAnnotatedWith = Collections.emptySet();
}
callback.callback ( (Collection) postTreatment((Collection) typesAnnotatedWith));
}
@Override
public Scanner scanner()
{
return new SubTypesScanner();
}
});
}
/**
* Unique reflections object. Unique scan
*/
@Override
public void doClasspathScan()
{
Scanner[] scanners = getScanners();
ConfigurationBuilder configurationBuilder = configurationBuilder().addUrls(computeUrls()).setScanners ( scanners ) ;
Reflections reflections = new Reflections(configurationBuilder);
for( ScannerCommand command : commands)
{
command.execute(reflections);
}
}
private Scanner[] getScanners()
{
Map<Class<?> , Scanner> scannersByClass = Maps.newHashMap();
for( ScannerCommand command : commands)
{
Scanner scanner = command.scanner();
if (!scannersByClass.containsKey(scanner.getClass()))
{
scannersByClass.put(scanner.getClass(), scanner);
}
}
Collection<Scanner> scanners = scannersByClass.values();
int size = scanners.size();
Scanner[] arrayOfScanners = new Scanner[size];
scanners.toArray(arrayOfScanners);
return arrayOfScanners;
}
public void setAdditionalClasspath(Set<URL> additionalClasspath)
{
this.additionalClasspath = additionalClasspath;
}
private ConfigurationBuilder configurationBuilder()
{
ConfigurationBuilder cb = new ConfigurationBuilder();
FilterBuilder fb = new FilterBuilder();
for (String packageRoot : packageRoots)
{
fb.include(prefix(packageRoot));
}
cb.filterInputsBy(fb);
return cb;
}
private Set<URL> computeUrls()
{
if (urls == null)
{
urls = new HashSet<URL>();
switch (classpathStrategy.getStrategy())
{
case SYSTEM:
urls.addAll(ClasspathHelper.forJavaClassPath());
break;
case CLASSLOADER:
urls.addAll(ClasspathHelper.forClassLoader());
break;
case ALL:
urls.addAll(ClasspathHelper.forJavaClassPath());
urls.addAll(ClasspathHelper.forClassLoader());
break;
case NONE:
break;
default:
throw new IllegalArgumentException("Unsupported classpath strategy " + classpathStrategy.toString());
}
if (classpathStrategy.isAdditional() && this.additionalClasspath != null)
{
urls.addAll(this.additionalClasspath);
}
}
urls.addAll(ClasspathHelper.forManifest(urls));
return urls;
}
private <T> Collection<Class<?>> toClasses2(Collection<String> names)
{
Collection<Class<?>> classes = new HashSet();
for (String name : names)
{
try
{
classes.add((Class<T>) Class.forName(name));
}
catch (Exception e)
{
logger.warn("Error when converting " + name + " to class.", e);
}
}
return classes;
}
private <T> Collection<Class<? extends T>> toClasses(Collection<String> names)
{
return ReflectionUtils.<T> forNames(names, this.getClass().getClassLoader());
}
}