/**
* Copyright (C) 2014 cherimojava (http://github.com/cherimojava/orchidae) 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.cherimojava.orchidae.hook;
import java.net.URL;
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import org.apache.commons.collections.comparators.ReverseComparator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.reflections.Configuration;
import org.reflections.Reflections;
import org.reflections.scanners.SubTypesScanner;
import org.reflections.util.ClasspathHelper;
import org.reflections.util.ConfigurationBuilder;
import com.github.cherimojava.orchidae.api.hook.Hook;
import com.github.cherimojava.orchidae.api.hook.Order;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
/**
* Performs all the Hook handling
*
* @author philnate
* @since 1.0.0
*/
public class HookHandler
{
private static Logger LOG = LogManager.getLogger();
private static final HookComparator comparator = new HookComparator();
private Map<Class<?>, SortedSet> hooks;
private static final SortedSet EMPTY_SET = ImmutableSortedSet.of();
/**
* creates a new HookHandler containing all Hooks found
*
* @param config
* @param pluginURL
*/
public HookHandler( Configuration config, URL pluginURL )
{
hooks = Maps.newHashMap();
Reflections r = new Reflections( config );
Set<Class<?>> hookTypes = r.getTypesAnnotatedWith( Hook.class );
for ( Class<?> c : hookTypes )
{
hooks.put( c, ImmutableSortedSet.copyOf( HookHandler.getHookOrdering( c, pluginURL ) ) );
}
}
/**
* Returns all hooks for the given hook Interface
*
* @param hook
* @return
*/
protected <T> SortedSet<T> getHook( Class<T> hook )
{
return (SortedSet<T>) ( hooks.containsKey( hook ) ? hooks.get( hook ) : EMPTY_SET );
}
/**
* Entry point into calling hooks, allowing to configure the invocation of those hooks
*
* @param hook to be called
* @param <H> Hook type
* @return HookExecutor allowing to configure hook invocation
*/
public <H> HookExecutor<H> callHook( Class<H> hook )
{
return new HookExecutor<>( hook, getHook( hook ) );
}
/**
* creates a sortedset of all hook implementations found for the given hook. Ordering is done through
* {@link com.github.cherimojava.orchidae.hook.HookHandler.HookComparator} in reverse order
*
* @param hook to find all implementations from
* @param url where to search for hook implementations
* @param <H> Hook type
* @return sortedset with all found hook implementation
*/
public static <H> SortedSet<? extends H> getHookOrdering( Class<H> hook, URL url )
{
Configuration config = new ConfigurationBuilder().addUrls( url )
.addUrls( ClasspathHelper.forPackage( HookHandler.class.getPackage().getName() ) )
.setScanners( new SubTypesScanner() );
Reflections reflect = new Reflections( config );
SortedSet<H> hooks = Sets.newTreeSet( new ReverseComparator( comparator ) );
LOG.info( "Searching for hooks of {}", hook );
for ( Class<? extends H> c : reflect.getSubTypesOf( hook ) )
{
try
{
LOG.info( "Found hook {}", c );
hooks.add( c.newInstance() );
}
catch ( IllegalAccessException | InstantiationException e )
{
LOG.error( "Failed to instantiate {} please make sure it has a no param Constructor. {}", c, e );
}
}
return hooks;
}
/**
* Hook ordering is following these principles:
* <ul>
* <li>Hooks with Order definition have higher precedence</li>
* <li>{@link com.github.cherimojava.orchidae.api.hook.Order.Category#SYSTEM} hooks before
* {@link com.github.cherimojava.orchidae.api.hook.Order.Category#CUSTOM}</li>
* <li>Higher order int, before lower ones</li>
* <li>Alphabetical ordering based on name for same ordering</li>
* </ul>
*
* @author philnate
* @since 1.0.0
*/
protected static class HookComparator
implements Comparator<Object>
{
@Override
public int compare( Object o1, Object o2 )
{
boolean b1 = o1.getClass().isAnnotationPresent( Order.class );
boolean b2 = o2.getClass().isAnnotationPresent( Order.class );
if ( !b1 && !b2 )
{
return 0;
}
else if ( b1 && !b2 )
{
return 1;
}
else if ( !b1 && b2 )
{
return -1;
}
else
{
Order ord1 = o1.getClass().getAnnotation( Order.class );
Order ord2 = o2.getClass().getAnnotation( Order.class );
if ( ord1.category() == ord2.category() )
{
int order = Integer.compare( ord1.order(), ord2.order() );
switch ( order )
{
case -1:/* fallthrough */
case 1:
break;
case 0:
// need to flip as the lexicographic ordering is invers to the compare methodology
return o2.getClass().getName().compareTo( o1.getClass().getName() );
}
return order;
}
else if ( ord1.category() == Order.Category.SYSTEM )
{
return 1;
}
else
{
return -1;
}
}
}
}
}