/* * Copyright (c) 2015 NOVA, All rights reserved. * This library is free software, licensed under GNU Lesser General Public License version 3 * * This file is part of NOVA. * * NOVA is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * NOVA 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with NOVA. If not, see <http://www.gnu.org/licenses/>. */ package nova.internal.core.launch; import nova.core.util.ProgressBar; import nova.internal.core.bootstrap.DependencyInjectionEntryPoint; import nova.internal.core.util.InjectionUtil; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; /** * @author Calclavia */ public class ModLoader<ANNOTATION extends Annotation> { protected final DependencyInjectionEntryPoint diep; private Optional<ANNOTATION> currentMod = Optional.empty(); /** * The type of the annotation */ protected final Class<ANNOTATION> annotationType; /** * Mod Java classes */ protected final Map<ANNOTATION, Class<?>> javaClasses; /** * Mod Scala classes */ protected final Map<ANNOTATION, Class<?>> scalaClasses; /** * Holds the instances of mods */ protected Map<ANNOTATION, Object> mods; protected List<Object> orderedMods; public ModLoader(Class<ANNOTATION> annotationType, DependencyInjectionEntryPoint diep, Set<Class<?>> modClasses) { this.diep = diep; this.annotationType = annotationType; /** * Final Java Classes */ javaClasses = modClasses.stream() .filter(clazz -> clazz.getAnnotation(annotationType) != null) .collect(Collectors.toMap((clazz) -> clazz.getAnnotation(annotationType), Function.identity())); /** * Find Scala Singleton Classes */ scalaClasses = modClasses.stream() .filter(c -> { try { Class.forName((c.getCanonicalName() + "$")); return true; } catch (Exception e) { return false; } }) .collect(Collectors.toMap(c -> c.getAnnotation(annotationType), c -> { try { return Class.forName(c.getCanonicalName() + "$"); } catch (Exception e) { e.printStackTrace(); throw new ExceptionInInitializerError("Failed to load NOVA mod: " + c); } } ) ); } public void load() { this.load(new ProgressBar.NullProgressBar(), true); } public void load(ProgressBar progressBar) { this.load(progressBar, true); } public void load(ProgressBar progressBar, boolean finish) { mods = new HashMap<>(); /** * Instantiate Java mods */ mods.putAll( javaClasses.entrySet().stream() .collect(Collectors.<Map.Entry<ANNOTATION, Class<?>>, ANNOTATION, Object>toMap(Map.Entry::getKey, entry -> { try { currentMod = Optional.of(entry.getKey()); progressBar.step(entry.getValue()); return InjectionUtil.newInstanceOrThrow(entry.getValue()); } catch (Exception ex) { ex.printStackTrace(); System.out.println("Failed to load NOVA Java mod: " + entry); throw new ExceptionInInitializerError(ex); } } ) ) ); /** * Instantiate Scala singleton mods */ mods.putAll( scalaClasses.entrySet().stream() .collect(Collectors.toMap(Map.Entry::getKey, entry -> { try { currentMod = Optional.of(entry.getKey()); progressBar.step(entry.getValue()); Field field = entry.getValue().getField("MODULE$"); Object loadable = field.get(null); //Inject dependencies to Scala singleton variables //TODO: Does not work recursively for all hierarchy Field[] fields = loadable.getClass().getDeclaredFields(); for (Field f : fields) { f.setAccessible(true); if (f.get(loadable) == null) { try { f.set(loadable, diep.getInjector().get().resolve(se.jbee.inject.Dependency.dependency(f.getType()))); } catch (Exception e) { e.printStackTrace(); } } f.setAccessible(false); } return loadable; } catch (Exception ex) { ex.printStackTrace(); System.out.println("Failed to load NOVA Scala mod: " + entry); throw new ExceptionInInitializerError(ex); } } ) ) ); currentMod = Optional.empty(); orderedMods = mods.values() .stream() .collect(Collectors.toList()); if (finish) progressBar.finish(); } public Set<ANNOTATION> getLoadedMods() { return mods.keySet(); } public Map<ANNOTATION, Object> getLoadedModMap() { return new HashMap<>(mods); } public Map<ANNOTATION, Class<?>> getModClasses() { return new HashMap<>(javaClasses); } public Map<ANNOTATION, Class<?>> getScalaClassesMap() { return new HashMap<>(scalaClasses); } public Optional<ANNOTATION> getCurrentMod() { return this.currentMod; } public List<Object> getOrdererdMods() { return Collections.unmodifiableList(orderedMods); } }