use of com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror in project CommandHelper by EngineHub.
the class ClassDiscovery method discover.
/**
* Does the class discovery for this particular URL. This should only be called by doDiscovery. Other internal
* methods should call doDiscovery, which handles looking through the dirtyURLs.
*/
private synchronized void discover(URL rootLocation) {
long start = System.currentTimeMillis();
if (debug) {
StreamUtils.GetSystemOut().println("Beginning discovery of " + rootLocation);
}
try {
// If the ClassDiscoveryCache is set, just use this.
if (classDiscoveryCache != null) {
ClassDiscoveryURLCache cduc = classDiscoveryCache.getURLCache(rootLocation);
preCaches.put(rootLocation, cduc);
}
String url;
try {
url = URLDecoder.decode(rootLocation.toString(), "UTF-8");
} catch (UnsupportedEncodingException ex) {
// apparently this should never happen, but we have to catch it anyway
url = null;
}
if (url == null) {
url = GetClassContainer(ClassDiscovery.class).toString();
}
final File rootLocationFile;
if (!classCache.containsKey(rootLocation)) {
classCache.put(rootLocation, Collections.synchronizedSet(new HashSet<>()));
} else {
classCache.get(rootLocation).clear();
}
final Set<ClassMirror<?>> mirrors = classCache.get(rootLocation);
if (preCaches.containsKey(rootLocation)) {
if (debug) {
StreamUtils.GetSystemOut().println("Precache already contains this URL, so using it");
}
// No need, already got a cache for this url
mirrors.addAll(preCaches.get(rootLocation).getClasses());
return;
}
if (debug) {
StreamUtils.GetSystemOut().println("Precache does not contain data for this URL, so scanning now.");
}
url = url.replaceFirst("^jar:", "");
if (url.endsWith("!/")) {
url = StringUtils.replaceLast(url, "!/", "");
}
if (url.startsWith("file:") && !url.endsWith(".jar")) {
final AtomicInteger id = new AtomicInteger(0);
// ExecutorService service = Executors.newFixedThreadPool(10, new ThreadFactory() {
// @Override
// public Thread newThread(Runnable r) {
// return new Thread(r, "ClassDiscovery-Async-" + id.incrementAndGet());
// }
// });
// Remove file: from the front
String root = url.substring(5);
rootLocationFile = new File(root);
List<File> fileList = new ArrayList<>();
descend(new File(root), fileList);
// to all of them. We have to first remove the "front" part
for (File f : fileList) {
String file = f.toString();
if (!file.matches(".*\\$(?:\\d)*\\.class") && file.endsWith(".class")) {
InputStream stream = null;
try {
stream = FileUtil.readAsStream(new File(rootLocationFile, f.getAbsolutePath().replaceFirst(Pattern.quote(new File(root).getAbsolutePath() + File.separator), "")));
ClassReader reader = new ClassReader(stream);
ClassMirrorVisitor mirrorVisitor = new ClassMirrorVisitor();
reader.accept(mirrorVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
mirrors.add(mirrorVisitor.getMirror(new URL(url)));
} catch (IOException ex) {
Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (stream != null) {
try {
stream.close();
} catch (IOException ex) {
Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
}
// service.shutdown();
// try {
// //Doesn't look like 0 is an option, so we'll just wait a day.
// service.awaitTermination(1, TimeUnit.DAYS);
// } catch (InterruptedException ex) {
// Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex);
// }
} else if (url.startsWith("file:") && url.endsWith(".jar")) {
// We are running from a jar
url = url.replaceFirst("file:", "");
rootLocationFile = new File(url);
ZipIterator zi = new ZipIterator(rootLocationFile);
try {
zi.iterate(new ZipIterator.ZipIteratorCallback() {
@Override
public void handle(String filename, InputStream in) {
if (!filename.matches(".*\\$(?:\\d)*\\.class") && filename.endsWith(".class")) {
try {
ClassReader reader = new ClassReader(in);
ClassMirrorVisitor mirrorVisitor = new ClassMirrorVisitor();
reader.accept(mirrorVisitor, ClassReader.SKIP_CODE | ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
mirrors.add(mirrorVisitor.getMirror(rootLocationFile.toURI().toURL()));
} catch (IOException ex) {
Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}, progressIterator);
} catch (IOException ex) {
Logger.getLogger(ClassDiscovery.class.getName()).log(Level.SEVERE, null, ex);
}
} else {
throw new RuntimeException("Unknown url type: " + rootLocation);
}
} catch (RuntimeException e) {
e.printStackTrace(System.err);
} finally {
if (debug) {
StreamUtils.GetSystemOut().println("Scans finished for " + rootLocation + ", taking " + (System.currentTimeMillis() - start) + " ms.");
}
}
}
use of com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror in project CommandHelper by EngineHub.
the class AnnotationChecks method checkForTypeInTypeofClasses.
public static void checkForTypeInTypeofClasses() throws Exception {
Set<ClassMirror<?>> classes = ClassDiscovery.getDefaultInstance().getClassesWithAnnotation(typeof.class);
Set<String> errors = new HashSet<>();
for (ClassMirror<?> clazz : classes) {
try {
// Make sure that TYPE has the same type as the typeof annotation
CClassType TYPE = (CClassType) ReflectionUtils.get(clazz.loadClass(), "TYPE");
if (TYPE == null) {
errors.add("TYPE is null? " + clazz.getClassName());
continue;
}
if (!TYPE.val().equals(clazz.getAnnotation(typeof.class).getValue("value"))) {
errors.add(clazz.getClassName() + "'s TYPE value is different than the typeof annotation on it");
}
} catch (ReflectionUtils.ReflectionException ex) {
errors.add(clazz.getClassName() + " needs to add the following:\n\t@SuppressWarnings(\"FieldNameHidesFieldInSuperclass\")\n" + "\tpublic static final CClassType TYPE = CClassType.get(\"" + clazz.getAnnotation(typeof.class).getValue("value") + "\");");
}
}
if (!errors.isEmpty()) {
throw new Exception("\n" + StringUtils.Join(errors, "\n"));
}
}
use of com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror in project CommandHelper by EngineHub.
the class CheckOverrides method setup.
private static void setup() {
if (methods == null) {
methods = new HashMap<>();
List<ClassMirror<?>> classes = ClassDiscovery.getDefaultInstance().getKnownClasses(ClassDiscovery.GetClassContainer(CheckOverrides.class));
for (ClassMirror cm : classes) {
Class c = cm.loadClass(CheckOverrides.class.getClassLoader(), false);
if (c.isInterface()) {
continue;
}
Set<Method> mm = getPotentiallyOverridingMethods(c);
if (!mm.isEmpty()) {
methods.put(c, mm);
}
}
}
}
use of com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror in project CommandHelper by EngineHub.
the class ExtensionManager method Cache.
public static void Cache(File extCache, Class... extraClasses) {
// We will only cache on Windows, as Linux doesn't natively lock
// files that are in use. Windows prevents any modification, making
// it harder for server owners on Windows to update the jars.
boolean onWindows = (OSUtils.GetOS() == OSUtils.OS.WINDOWS);
if (!onWindows) {
return;
}
// Create the directory if it doesn't exist.
extCache.mkdirs();
// cleanup wasn't successful on the last run.
for (File f : extCache.listFiles()) {
try {
Files.delete(f.toPath());
} catch (IOException ex) {
Static.getLogger().log(Level.WARNING, "[CommandHelper] Could not delete loose file " + f.getAbsolutePath() + ": " + ex.getMessage());
}
}
// The cache, cd and dcl here will just be thrown away.
// They are only used here for the purposes of discovering what a given
// jar has to offer.
ClassDiscoveryCache cache = new ClassDiscoveryCache(CommandHelperFileLocations.getDefault().getCacheDirectory());
cache.setLogger(Static.getLogger());
DynamicClassLoader dcl = new DynamicClassLoader();
ClassDiscovery cd = new ClassDiscovery();
cd.setClassDiscoveryCache(cache);
cd.addDiscoveryLocation(ClassDiscovery.GetClassContainer(ExtensionManager.class));
for (Class klazz : extraClasses) {
cd.addDiscoveryLocation(ClassDiscovery.GetClassContainer(klazz));
}
// Look in the given locations for jars, add them to our class discovery.
List<File> toProcess = new ArrayList<>();
for (File location : locations) {
toProcess.addAll(getFiles(location));
}
// Load the files into the discovery mechanism.
for (File file : toProcess) {
if (!file.canRead()) {
continue;
}
URL jar;
try {
jar = file.toURI().toURL();
} catch (MalformedURLException ex) {
Static.getLogger().log(Level.SEVERE, null, ex);
continue;
}
dcl.addJar(jar);
cd.addDiscoveryLocation(jar);
}
cd.setDefaultClassLoader(dcl);
// Loop thru the found lifecycles, copy them to the cache using the name
// given in the lifecycle. If more than one jar has the same internal
// name, the filename will be given a number.
Set<File> done = new HashSet<>();
Map<String, Integer> namecount = new HashMap<>();
// use their internal name.
for (ClassMirror<? extends AbstractExtension> extmirror : cd.getClassesWithAnnotationThatExtend(MSExtension.class, AbstractExtension.class)) {
if (extmirror.equals(new ClassMirror<>(AbstractExtension.class))) {
continue;
}
AnnotationMirror plug = extmirror.getAnnotation(MSExtension.class);
URL plugURL = extmirror.getContainer();
// Get the internal name that this extension exposes.
if (plugURL != null && plugURL.getPath().endsWith(".jar")) {
File f;
try {
f = new File(URLDecoder.decode(plugURL.getFile(), "UTF8"));
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(ExtensionManager.class.getName()).log(Level.SEVERE, null, ex);
continue;
}
// Skip extensions that originate from commandhelpercore.
if (plugURL.equals(ClassDiscovery.GetClassContainer(ExtensionManager.class))) {
done.add(f);
continue;
}
// Skip files already processed.
if (done.contains(f)) {
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.WARNING, f.getAbsolutePath() + " contains more than one extension" + " descriptor. Bug someone about it!", Target.UNKNOWN);
continue;
}
done.add(f);
String name = plug.getValue("value").toString();
// lets track and rename them using a number scheme.
if (namecount.containsKey(name.toLowerCase())) {
int i = namecount.get(name.toLowerCase());
name += "-" + i;
namecount.put(name.toLowerCase(), i++);
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.WARNING, f.getAbsolutePath() + " contains a duplicate internally" + " named extension (" + name + "). Bug someone" + " about it!", Target.UNKNOWN);
} else {
namecount.put(name.toLowerCase(), 1);
}
// Rename the jar to use the plugin's internal name and
// copy it into the cache.
File newFile = new File(extCache, name.toLowerCase() + ".jar");
try {
Files.copy(f.toPath(), newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
Static.getLogger().log(Level.SEVERE, "Could not copy '" + f.getName() + "' to cache: " + ex.getMessage());
}
}
}
Set<ClassMirror<?>> classes = cd.getClassesWithAnnotation(api.class);
// Now process @api annotated extensions, ignoring ones already processed.
for (ClassMirror klass : classes) {
URL plugURL = klass.getContainer();
if (plugURL != null && plugURL.getPath().endsWith(".jar")) {
File f;
try {
f = new File(URLDecoder.decode(plugURL.getFile(), "UTF8"));
} catch (UnsupportedEncodingException ex) {
Logger.getLogger(ExtensionManager.class.getName()).log(Level.SEVERE, null, ex);
continue;
}
// Skip files already processed.
if (done.contains(f)) {
continue;
}
// No special processing needed.
if (cd.doesClassExtend(klass, Event.class) || cd.doesClassExtend(klass, Function.class)) {
// We're processing it here instead of above, complain about it.
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.WARNING, f.getAbsolutePath() + " is an old-style extension!" + " Bug the author to update it to the new extension system!", Target.UNKNOWN);
// Only process this file once.
done.add(f);
File newFile = new File(extCache, "oldstyle-" + f.getName());
try {
Files.copy(f.toPath(), newFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
Static.getLogger().log(Level.SEVERE, "Could not copy '" + f.getName() + "' to cache: " + ex.getMessage());
}
}
}
}
// Shut down the original dcl to "unlock" the processed jars.
// The cache and cd instances will just fall into oblivion.
dcl.destroy();
// Explicit call. Without this, jar files won't actually get unlocked on
// Windows. Of course, this is hit and miss, but that's fine; we tried.
System.gc();
}
use of com.laytonsmith.PureUtilities.ClassLoading.ClassMirror.ClassMirror in project CommandHelper by EngineHub.
the class ExtensionManager method Initialize.
/**
* Initializes the extension manager. This operation is not necessarily required, and must be guaranteed to not run
* more than once per ClassDiscovery object.
*
* @param cd the ClassDiscovery to use for loading files.
*/
public static void Initialize(ClassDiscovery cd) {
extensions.clear();
// Look in the extension folder for jars, add them to our class discover,
// then initialize everything
List<File> toProcess = new ArrayList<>();
// Grab files from the cache if on Windows. Otherwise just load
// directly from the stored locations.
boolean onWindows = (OSUtils.GetOS() == OSUtils.OS.WINDOWS);
if (onWindows) {
toProcess.addAll(getFiles(CommandHelperFileLocations.getDefault().getExtensionCacheDirectory()));
} else {
for (File location : locations) {
toProcess.addAll(getFiles(location));
}
}
DynamicClassLoader dcl = new DynamicClassLoader();
cd.setDefaultClassLoader(dcl);
for (File f : toProcess) {
if (f.getName().endsWith(".jar")) {
try {
// First, load it with our custom class loader
URL jar = f.toURI().toURL();
dcl.addJar(jar);
cd.addDiscoveryLocation(jar);
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.DEBUG, "Loaded " + f.getAbsolutePath(), Target.UNKNOWN);
} catch (MalformedURLException ex) {
Static.getLogger().log(Level.SEVERE, null, ex);
}
}
}
// one found defines the internal name.
for (ClassMirror<? extends AbstractExtension> extmirror : cd.getClassesWithAnnotationThatExtend(MSExtension.class, AbstractExtension.class)) {
if (extmirror.equals(new ClassMirror<>(AbstractExtension.class))) {
continue;
}
Extension ext;
URL url = extmirror.getContainer();
Class<? extends AbstractExtension> extcls;
if (extmirror.getModifiers().isAbstract()) {
Static.getLogger().log(Level.SEVERE, "Probably won't be able to" + " instantiate " + extmirror.getClassName() + ": The" + " class is marked as abstract! Will try anyway.");
}
try {
extcls = extmirror.loadClass(dcl, true);
} catch (Throwable ex) {
// May throw anything, and kill the loading process.
// Lets prevent that!
Static.getLogger().log(Level.SEVERE, "Could not load class '" + extmirror.getClassName() + "'");
ex.printStackTrace();
continue;
}
try {
ext = extcls.newInstance();
} catch (InstantiationException | IllegalAccessException ex) {
// Error, but skip this one, don't throw an exception ourselves, just log it.
Static.getLogger().log(Level.SEVERE, "Could not instantiate " + extcls.getName() + ": " + ex.getMessage());
continue;
}
ExtensionTracker trk = extensions.get(url);
if (trk == null) {
trk = new ExtensionTracker(url, cd, dcl);
extensions.put(url, trk);
}
// use it.
if (trk.identifier == null) {
trk.identifier = ext.getName();
try {
trk.version = ext.getVersion();
} catch (AbstractMethodError ex) {
// getVersion() was added later. This is a temporary fix
// to allow extension authors some time to update.
// TODO: Remove this soon.
trk.version = new SimpleVersion("0.0.0");
}
}
trk.allExtensions.add(ext);
}
// Lets store info about the functions and events extensions have.
// This will aide in gracefully unloading stuff later.
Set<ClassMirror<?>> classes = cd.getClassesWithAnnotation(api.class);
// Temp tracking for loading messages later on.
List<String> events = new ArrayList<>();
List<String> functions = new ArrayList<>();
// and store the instances in their trackers.
for (ClassMirror klass : classes) {
URL url = klass.getContainer();
if (cd.doesClassExtend(klass, Event.class) || cd.doesClassExtend(klass, Function.class)) {
Class c;
try {
c = klass.loadClass(dcl, true);
} catch (Throwable ex) {
// May throw anything, and kill the loading process.
// Lets prevent that!
Static.getLogger().log(Level.SEVERE, "Could not load class '" + klass.getClassName() + "'");
ex.printStackTrace();
continue;
}
ExtensionTracker trk = extensions.get(url);
if (trk == null) {
trk = new ExtensionTracker(url, cd, dcl);
extensions.put(url, trk);
}
// Instantiate, register and store.
try {
if (Event.class.isAssignableFrom(c)) {
Class<Event> cls = (Class<Event>) c;
if (klass.getModifiers().isAbstract()) {
// Abstract? Looks like they accidently @api'd
// a cheater class. We can't be sure that it is fully
// defined, so complain to the console.
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.ERROR, "Class " + c.getName() + " in " + url + " is" + " marked as an event but is also abstract." + " Bugs might occur! Bug someone about this!", Target.UNKNOWN);
}
Event e = cls.newInstance();
events.add(e.getName());
trk.registerEvent(e);
} else if (Function.class.isAssignableFrom(c)) {
Class<Function> cls = (Class<Function>) c;
if (klass.getModifiers().isAbstract()) {
// Abstract? Looks like they accidently @api'd
// a cheater class. We can't be sure that it is fully
// defined, so complain to the console.
CHLog.GetLogger().Log(CHLog.Tags.EXTENSIONS, LogLevel.ERROR, "Class " + c.getName() + " in " + url + " is" + " marked as a function but is also abstract." + " Bugs might occur! Bug someone about this!", Target.UNKNOWN);
}
Function f = cls.newInstance();
functions.add(f.getName());
trk.registerFunction(f);
}
} catch (InstantiationException ex) {
Static.getLogger().log(Level.SEVERE, ex.getMessage(), ex);
} catch (IllegalAccessException ex) {
Static.getLogger().log(Level.SEVERE, null, ex);
}
}
}
// Lets print out the details to the console, if we are in debug mode.
try {
if (Prefs.DebugMode()) {
Collections.sort(events);
String eventString = StringUtils.Join(events, ", ", ", and ", " and ");
Collections.sort(functions);
String functionString = StringUtils.Join(functions, ", ", ", and ", " and ");
StreamUtils.GetSystemOut().println(Implementation.GetServerType().getBranding() + ": Loaded the following functions: " + functionString.trim());
StreamUtils.GetSystemOut().println(Implementation.GetServerType().getBranding() + ": Loaded " + functions.size() + " function" + (functions.size() == 1 ? "." : "s."));
StreamUtils.GetSystemOut().println(Implementation.GetServerType().getBranding() + ": Loaded the following events: " + eventString.trim());
StreamUtils.GetSystemOut().println(Implementation.GetServerType().getBranding() + ": Loaded " + events.size() + " event" + (events.size() == 1 ? "." : "s."));
}
} catch (Throwable e) {
// Prefs weren't loaded, probably caused by running tests.
}
}
Aggregations