/*
* Copyright 2015 MovingBlocks
*
* 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.terasology.assets.module;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Collections2;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.common.collect.Sets;
import org.reflections.ReflectionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.terasology.assets.Asset;
import org.terasology.assets.AssetData;
import org.terasology.assets.AssetDataProducer;
import org.terasology.assets.AssetFactory;
import org.terasology.assets.AssetType;
import org.terasology.assets.ResolutionStrategy;
import org.terasology.assets.ResourceUrn;
import org.terasology.assets.format.AssetAlterationFileFormat;
import org.terasology.assets.format.AssetFileFormat;
import org.terasology.assets.management.AssetManager;
import org.terasology.assets.management.AssetTypeManager;
import org.terasology.assets.module.annotations.RegisterAssetDataProducer;
import org.terasology.assets.module.annotations.RegisterAssetDeltaFileFormat;
import org.terasology.assets.module.annotations.RegisterAssetFileFormat;
import org.terasology.assets.module.annotations.RegisterAssetSupplementalFileFormat;
import org.terasology.assets.module.annotations.RegisterAssetType;
import org.terasology.module.ModuleEnvironment;
import org.terasology.util.reflection.ClassFactory;
import org.terasology.util.reflection.GenericsUtil;
import org.terasology.util.reflection.ParameterProvider;
import org.terasology.util.reflection.SimpleClassFactory;
import java.io.Closeable;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
/**
* ModuleAwareAssetTypeManager is an AssetTypeManager that integrates with ModuleEnvironment, obtaining assets, registering extension classes and handling asset
* disposal and reloading when environments change.
* <p>
* The major features of ModuleAwareAssetTypeManager are:
* </p>
* <ul>
* <li>Registration of core AssetTypes, AssetDataProducers and file formats. These will automatically be registered when the environment is next switched,
* and will remain across environment changes.</li>
* <li>Automatic registration of extension AssetTypes, AssetDataProducers and file formats mark with annotations that are discovered within the module environment
* being switched to, and removal of these extensions when the module environment is later switched from</li>
* <li>When the module environment is changed, all assets are either reloaded if within the new environment, or disposed if no longer available.</li>
* <li>Will reload assets changed on the file system upon request</li>
* </ul>
*
* @author Immortius
*/
public class ModuleAwareAssetTypeManager implements AssetTypeManager, Closeable {
private static final Logger logger = LoggerFactory.getLogger(ModuleAwareAssetTypeManager.class);
private final AssetManager assetManager;
private volatile ImmutableMap<Class<? extends Asset>, AssetType<?, ?>> assetTypes = ImmutableMap.of();
private volatile ImmutableListMultimap<Class<? extends Asset>, Class<? extends Asset>> subtypes = ImmutableListMultimap.of();
private final List<AssetType<?, ?>> coreAssetTypes = Lists.newArrayList();
private final Set<Class<? extends Asset>> reloadOnSwitchAssetTypes = Sets.newHashSet();
private final SetMultimap<Class<? extends Asset>, String> coreAssetTypeFolderNames = HashMultimap.create();
private final ListMultimap<Class<? extends Asset<?>>, AssetDataProducer<?>> coreProducers = ArrayListMultimap.create();
private final ListMultimap<Class<? extends Asset<?>>, AssetFileFormat<?>> coreFormats = ArrayListMultimap.create();
private final ListMultimap<Class<? extends Asset<?>>, AssetAlterationFileFormat<?>> coreSupplementalFormats = ArrayListMultimap.create();
private final ListMultimap<Class<? extends Asset<?>>, AssetAlterationFileFormat<?>> coreDeltaFormats = ArrayListMultimap.create();
private final ClassFactory classFactory;
private volatile ModuleWatcher watcher;
public ModuleAwareAssetTypeManager() {
this.assetManager = new AssetManager(this);
this.classFactory = new SimpleClassFactory(new ParameterProvider() {
@Override
@SuppressWarnings("unchecked")
public <T> Optional<T> get(Class<T> type) {
if (type.equals(AssetManager.class)) {
return (Optional<T>) Optional.of(assetManager);
}
return Optional.empty();
}
});
}
public ModuleAwareAssetTypeManager(ClassFactory classFactory) {
this.assetManager = new AssetManager(this);
this.classFactory = classFactory;
}
@Override
public synchronized void close() throws IOException {
for (AssetType assetType : assetTypes.values()) {
assetType.close();
}
if (watcher != null) {
watcher.shutdown();
watcher = null;
}
}
@Override
@SuppressWarnings("unchecked")
public <T extends Asset<U>, U extends AssetData> Optional<AssetType<T, U>> getAssetType(Class<T> type) {
return Optional.ofNullable((AssetType<T, U>) assetTypes.get(type));
}
@Override
@SuppressWarnings("unchecked")
public <T extends Asset<?>> List<AssetType<? extends T, ?>> getAssetTypes(Class<T> type) {
List<AssetType<? extends T, ?>> result = Lists.newArrayList();
for (Class<? extends Asset> subtype : subtypes.get(type)) {
AssetType<? extends T, ?> subAssetType = (AssetType<? extends T, ?>) assetTypes.get(subtype);
if (subAssetType != null) {
result.add(subAssetType);
}
}
return result;
}
@Override
public void disposedUnusedAssets() {
assetTypes.values().forEach(type -> type.processDisposal());
}
/**
* Triggers the reload of any assets that have been altered in directory modules.
*/
public void reloadChangedOnDisk() {
if (watcher != null) {
SetMultimap<AssetType<?, ?>, ResourceUrn> changes = watcher.checkForChanges();
for (Map.Entry<AssetType<?, ?>, ResourceUrn> entry : changes.entries()) {
if (entry.getKey().isLoaded(entry.getValue())) {
AssetType<?, ?> assetType = entry.getKey();
ResourceUrn changedUrn = entry.getValue();
logger.info("Reloading changed asset '{}'", changedUrn);
assetType.reload(changedUrn);
}
}
}
}
/**
* Registers an asset type. It will be available after the next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called. Asset files will be
* read from modules from the provided subfolders. If there are no subfolders then assets will not be loaded from modules.
*
* @param type The type of to register as a core type
* @param factory The factory to create assets of the desired type from asset data
* @param subfolderNames The name of the subfolders which asset files related to this type will be read from within modules
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreAssetType(Class<T> type, AssetFactory<T, U> factory, String... subfolderNames) {
registerCoreAssetType(type, factory, true, subfolderNames);
}
/**
* Registers an asset type. It will be available after the next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called. Asset files will be
* read from modules from the provided subfolders. If there are no subfolders then assets will not be loaded from modules.
*
* @param type The type of to register as a core type
* @param factory The factory to create assets of the desired type from asset data
* @param reloadOnSwitch Whether assets of this type should be reloaded on environment switch rather than just disposed
* @param subfolderNames The name of the subfolders which asset files related to this type will be read from within modules
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreAssetType(Class<T> type, AssetFactory<T, U> factory, boolean reloadOnSwitch,
String... subfolderNames) {
Preconditions.checkState(!assetTypes.containsKey(type), "Asset type '" + type.getSimpleName() + "' already registered");
AssetType<T, U> assetType = new AssetType<>(type, factory);
coreAssetTypes.add(assetType);
coreAssetTypeFolderNames.putAll(type, Arrays.asList(subfolderNames));
if (reloadOnSwitch) {
reloadOnSwitchAssetTypes.add(type);
}
}
/**
* Removes an asset type. This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param type The type of asset to remove
* @param <T> The type of asset
* @param <U> The type of asset data
*/
@SuppressWarnings("unchecked")
public synchronized <T extends Asset<U>, U extends AssetData> void removeCoreAssetType(Class<T> type) {
Iterator<AssetType<?, ?>> iterator = coreAssetTypes.iterator();
while (iterator.hasNext()) {
AssetType<?, ?> assetType = iterator.next();
if (assetType.getAssetClass() == type) {
iterator.remove();
coreAssetTypeFolderNames.removeAll(type);
reloadOnSwitchAssetTypes.remove(type);
break;
}
}
}
/**
* Registers an AssetDataProducer for use by a specified asset type. This change will take affect next time
* {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset the producer should be registered with
* @param producer The AssetDataProducer.
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreProducer(Class<T> assetType, AssetDataProducer<U> producer) {
coreProducers.put(assetType, producer);
}
/**
* Removes an AssetDataProducer. This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset the producer was registered with
* @param producer The AssetDataProducer
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void removeCoreProducer(Class<T> assetType, AssetDataProducer<U> producer) {
coreProducers.remove(assetType, producer);
}
/**
* Registers an asset file format with a specific asset type.
* This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to register the format with.
* @param format The AssetFileFormat
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreFormat(Class<T> assetType, AssetFileFormat<U> format) {
coreFormats.put(assetType, format);
}
/**
* Removes an asset file format. This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to remove the format from.
* @param format The AssetFileFormat
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void removeCoreFormat(Class<T> assetType, AssetFileFormat<U> format) {
coreFormats.remove(assetType, format);
}
/**
* Registers an asset supplemental format with a specific asset type.
* This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to register the format with.
* @param format The supplemental file format
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreSupplementalFormat(Class<T> assetType, AssetAlterationFileFormat<U> format) {
coreSupplementalFormats.put(assetType, format);
}
/**
* Removes an asset supplemental format. This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to remove the format from.
* @param format The supplemental file format.
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void removeCoreSupplementalFormat(Class<T> assetType, AssetAlterationFileFormat<U> format) {
coreSupplementalFormats.remove(assetType, format);
}
/**
* Registers an asset delta format with a specific asset type.
* This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to register the format with.
* @param format The delta file format.
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void registerCoreDeltaFormat(Class<T> assetType, AssetAlterationFileFormat<U> format) {
coreDeltaFormats.put(assetType, format);
}
/**
* Removes an asset delta format.
* This change will take affect next time {@link #switchEnvironment(org.terasology.module.ModuleEnvironment)} is called.
*
* @param assetType The type of asset to remove the format from
* @param format The delta file format.
* @param <T> The type of asset
* @param <U> The type of asset data
*/
public synchronized <T extends Asset<U>, U extends AssetData> void removeCoreDeltaFormat(Class<T> assetType, AssetAlterationFileFormat<U> format) {
coreDeltaFormats.remove(assetType, format);
}
/**
* @return An asset manager over this AssetTypeManager.
*/
public AssetManager getAssetManager() {
return assetManager;
}
/**
* Switches the module environment. This triggers:
* <ul>
* <li>Removal of all extension types, producers and formats</li>
* <li>Disposal of all assets not present in the new environment</li>
* <li>Reload of all assets present in the new environment</li>
* <li>Scan for and install extension asset types, producers and formats</li>
* <li>Makes available loading assets from the new environment</li>
* </ul>
*
* @param newEnvironment The new module environment
*/
public synchronized void switchEnvironment(ModuleEnvironment newEnvironment) {
Preconditions.checkNotNull(newEnvironment);
if (watcher != null) {
try {
watcher.shutdown();
} catch (IOException e) {
logger.error("Failed to shut down watch service", e);
}
}
try {
watcher = new ModuleWatcher(newEnvironment);
} catch (IOException e) {
logger.warn("Failed to establish watch service, will not auto-reload changed assets", e);
}
for (AssetType<?, ?> assetType : assetTypes.values()) {
assetType.clearProducers();
}
ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> extensionFileFormats = scanForExtensionFormats(newEnvironment);
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionSupplementalFormats = scanForExtensionSupplementalFormats(newEnvironment);
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionDeltaFormats = scanForExtensionDeltaFormats(newEnvironment);
ResolutionStrategy resolutionStrategy = new ModuleDependencyResolutionStrategy(newEnvironment);
Map<Class<? extends Asset>, AssetType<?, ?>> newAssetTypes = Maps.newHashMap();
setupCoreAssetTypes(newEnvironment, extensionFileFormats, extensionSupplementalFormats, extensionDeltaFormats, resolutionStrategy, newAssetTypes);
setupExtensionAssetTypes(newEnvironment, extensionFileFormats, extensionSupplementalFormats, extensionDeltaFormats, resolutionStrategy, newAssetTypes);
scanForExtensionProducers(newEnvironment, newAssetTypes);
ImmutableMap<Class<? extends Asset>, AssetType<?, ?>> oldAssetTypes = assetTypes;
assetTypes = ImmutableMap.copyOf(newAssetTypes);
for (AssetType<?, ?> assetType : assetTypes.values()) {
if (reloadOnSwitchAssetTypes.contains(assetType.getAssetClass())) {
assetType.refresh();
} else {
assetType.disposeAll();
}
}
oldAssetTypes.values().stream().filter(assetType -> !coreAssetTypes.contains(assetType)).forEach(assetType -> assetType.close());
updateSubtypesMap();
}
/**
* Updates the map of subtypes based on the current asset types
*/
private void updateSubtypesMap() {
ListMultimap<Class<? extends Asset>, Class<? extends Asset>> subtypesBuilder = ArrayListMultimap.create();
for (Class<? extends Asset> type : assetTypes.keySet()) {
for (Class<?> parentType : ReflectionUtils.getAllSuperTypes(type, new Predicate<Class<?>>() {
@Override
public boolean apply(Class<?> input) {
return Asset.class.isAssignableFrom(input) && input != Asset.class;
}
})) {
subtypesBuilder.put((Class<? extends Asset>) parentType, type);
Collections.sort(subtypesBuilder.get((Class<? extends Asset>) parentType), new Comparator<Class<?>>() {
@Override
public int compare(Class<?> o1, Class<?> o2) {
return o1.getSimpleName().compareTo(o2.getSimpleName());
}
});
}
}
subtypes = ImmutableListMultimap.copyOf(subtypesBuilder);
}
private void subscribeToChanges(AssetType<?, ?> assetType, ModuleAssetDataProducer<?> producer, Collection<String> folderNames) {
if (watcher != null) {
for (String folder : folderNames) {
watcher.register(folder, producer, assetType);
}
}
}
private void setupCoreAssetTypes(ModuleEnvironment environment, ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> extensionFileFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionSupplementalFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionDeltaFormats, ResolutionStrategy resolutionStrategy,
Map<Class<? extends Asset>, AssetType<?, ?>> outAssetTypes) {
for (AssetType<?, ?> assetType : coreAssetTypes) {
Set<String> folderNames = coreAssetTypeFolderNames.get(assetType.getAssetClass());
prepareAssetType(assetType, folderNames, resolutionStrategy, environment, extensionFileFormats, extensionSupplementalFormats, extensionDeltaFormats);
outAssetTypes.put(assetType.getAssetClass(), assetType);
}
}
private void setupExtensionAssetTypes(ModuleEnvironment environment, ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> extensionFileFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionSupplementalFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionDeltaFormats,
ResolutionStrategy resolutionStrategy, Map<Class<? extends Asset>, AssetType<?, ?>> outAssetTypes) {
for (Class<?> type : environment.getTypesAnnotatedWith(RegisterAssetType.class, input -> Asset.class.isAssignableFrom(input))) {
Class<? extends Asset> assetClass = (Class<? extends Asset>) type;
Optional<Type> assetDataType = GenericsUtil.getTypeParameterBindingForInheritedClass(assetClass, Asset.class, 0);
if (!assetDataType.isPresent()) {
logger.error("Could not register AssetType for '{}' - asset data type must be bound in inheritance tree", assetClass);
continue;
}
RegisterAssetType registrationInfo = assetClass.getAnnotation(RegisterAssetType.class);
Optional<AssetFactory> factory = classFactory.instantiateClass(registrationInfo.factoryClass());
if (factory.isPresent()) {
AssetType<?, ?> assetType = new AssetType<>(assetClass, factory.get());
prepareAssetType(assetType, Arrays.asList(registrationInfo.folderName()), resolutionStrategy, environment,
extensionFileFormats, extensionSupplementalFormats, extensionDeltaFormats);
if (!outAssetTypes.containsKey(assetType.getAssetClass())) {
outAssetTypes.put(assetType.getAssetClass(), assetType);
} else {
logger.error("Asset Type already registered for type '{}' - discarding additional registration", assetType.getAssetClass());
assetType.close();
}
}
}
}
@SuppressWarnings("unchecked")
private <T extends Asset<U>, U extends AssetData> void prepareAssetType(
AssetType<T, U> assetType,
Collection<String> folderNames,
ResolutionStrategy resolutionStrategy,
ModuleEnvironment environment,
ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> extensionFileFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionSupplementalFormats,
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionDeltaFormats) {
assetType.setResolutionStrategy(resolutionStrategy);
for (AssetDataProducer producer : coreProducers.get(assetType.getAssetClass())) {
assetType.addProducer(producer);
}
if (!folderNames.isEmpty()) {
List<AssetFileFormat<?>> assetFormats = Lists.newArrayList(coreFormats.get(assetType.getAssetClass()));
assetFormats.addAll(extensionFileFormats.get(assetType.getAssetDataClass()));
List<AssetAlterationFileFormat<?>> supplementalFormats = Lists.newArrayList(coreSupplementalFormats.get(assetType.getAssetClass()));
supplementalFormats.addAll(extensionSupplementalFormats.get(assetType.getAssetDataClass()));
List<AssetAlterationFileFormat<?>> deltaFormats = Lists.newArrayList(coreDeltaFormats.get(assetType.getAssetClass()));
deltaFormats.addAll(extensionDeltaFormats.get(assetType.getAssetDataClass()));
ModuleAssetDataProducer moduleProducer = new ModuleAssetDataProducer(environment, assetFormats, supplementalFormats, deltaFormats, folderNames);
assetType.addProducer(moduleProducer);
subscribeToChanges(assetType, moduleProducer, folderNames);
}
}
@SuppressWarnings("unchecked")
private void scanForExtensionProducers(ModuleEnvironment environment, Map<Class<? extends Asset>, AssetType<?, ?>> forAssetTypes) {
for (AssetDataProducer producer : findAndInstantiateClasses(environment, AssetDataProducer.class, RegisterAssetDataProducer.class)) {
Optional<Type> assetDataType = GenericsUtil.getTypeParameterBindingForInheritedClass(producer.getClass(), AssetDataProducer.class, 0);
if (!assetDataType.isPresent()) {
logger.error("Could not register AssetProducer '{}' - asset data type must be bound in inheritance tree", producer.getClass());
continue;
}
final Class<? extends AssetData> assetDataClass = (Class<? extends AssetData>) GenericsUtil.getClassOfType(assetDataType.get());
List<AssetType<?, ?>> validAssetTypes = Lists.newArrayList(Collections2.filter(forAssetTypes.values(), new Predicate<AssetType<?, ?>>() {
@Override
public boolean apply(AssetType<?, ?> input) {
return input.getAssetDataClass().equals(assetDataClass);
}
}));
if (validAssetTypes.isEmpty()) {
logger.error("No asset type available for asset producer {}", producer.getClass());
} else {
for (AssetType<?, ?> assetType : validAssetTypes) {
assetType.addProducer(producer);
}
}
}
}
@SuppressWarnings("unchecked")
private ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> scanForExtensionFormats(ModuleEnvironment environment) {
ListMultimap<Class<? extends AssetData>, AssetFileFormat<?>> extensionFormats = ArrayListMultimap.create();
for (AssetFileFormat format : findAndInstantiateClasses(environment, AssetFileFormat.class, RegisterAssetFileFormat.class)) {
Optional<Type> assetDataType = GenericsUtil.getTypeParameterBindingForInheritedClass(format.getClass(), AssetFileFormat.class, 0);
if (!assetDataType.isPresent()) {
logger.error("Could not register AssetFormat '{}' - asset data type must be bound in inheritance tree", format.getClass());
continue;
}
final Class<? extends AssetData> assetDataClass = (Class<? extends AssetData>) GenericsUtil.getClassOfType(assetDataType.get());
extensionFormats.put(assetDataClass, format);
}
return extensionFormats;
}
@SuppressWarnings("unchecked")
private ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> scanForExtensionSupplementalFormats(ModuleEnvironment environment) {
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionFormats = ArrayListMultimap.create();
for (AssetAlterationFileFormat format : findAndInstantiateClasses(environment, AssetAlterationFileFormat.class, RegisterAssetSupplementalFileFormat.class)) {
Optional<Type> assetDataType = GenericsUtil.getTypeParameterBindingForInheritedClass(format.getClass(), AssetAlterationFileFormat.class, 0);
if (!assetDataType.isPresent()) {
logger.error("Could not register Asset Supplemental Format '{}' - asset data type must be bound in inheritance tree", format.getClass());
continue;
}
final Class<? extends AssetData> assetDataClass = (Class<? extends AssetData>) GenericsUtil.getClassOfType(assetDataType.get());
extensionFormats.put(assetDataClass, format);
}
return extensionFormats;
}
@SuppressWarnings("unchecked")
private ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> scanForExtensionDeltaFormats(ModuleEnvironment environment) {
ListMultimap<Class<? extends AssetData>, AssetAlterationFileFormat<?>> extensionFormats = ArrayListMultimap.create();
for (AssetAlterationFileFormat format : findAndInstantiateClasses(environment, AssetAlterationFileFormat.class, RegisterAssetDeltaFileFormat.class)) {
Optional<Type> assetDataType = GenericsUtil.getTypeParameterBindingForInheritedClass(format.getClass(), AssetAlterationFileFormat.class, 0);
if (!assetDataType.isPresent()) {
logger.error("Could not register Asset Delta Format '{}' - asset data type must be bound in inheritance tree", format.getClass());
continue;
}
final Class<? extends AssetData> assetDataClass = (Class<? extends AssetData>) GenericsUtil.getClassOfType(assetDataType.get());
extensionFormats.put(assetDataClass, format);
}
return extensionFormats;
}
@SuppressWarnings("unchecked")
private <T> List<T> findAndInstantiateClasses(ModuleEnvironment environment, final Class<T> baseType, Class<? extends Annotation> annotation) {
List<T> result = Lists.newArrayList();
for (Class<?> discoveredType : environment.getTypesAnnotatedWith(annotation, input -> baseType.isAssignableFrom(input)
&& !Modifier.isAbstract(input.getModifiers()))) {
Optional<T> instance = classFactory.instantiateClass((Class<? extends T>) discoveredType);
if (instance.isPresent()) {
result.add(instance.get());
}
}
return result;
}
}