/* * This file is part of Mixin, licensed under the MIT License (MIT). * * Copyright (c) SpongePowered * Copyright (c) contributors * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.spongepowered.asm.service; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.ServiceConfigurationError; import java.util.ServiceLoader; import java.util.Set; import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.logging.LoggerAdapterConsole; import com.google.common.base.Joiner; import com.google.common.collect.ObjectArrays; /** * Provides access to the service layer which connects the mixin transformer to * a particular host environment. Host environments are implemented as services * implementing {@link IMixinService} in order to decouple them from mixin's * core. This allows us to support LegacyLauncher */ public final class MixinService { /** * Since we want to log things during startup but need the service itself to * provide a logging implementation adapter, we need a place to store log * messages prior to the service startup which can later be flushed into the * logger itself if startup succeeds, or into the console if startup fails. */ static class LogBuffer { public static class LogEntry { public String message; public Object[] params; public Throwable t; public LogEntry(String message, Object[] params, Throwable t) { this.message = message; this.params = params; this.t = t; } } private final List buffer = new ArrayList(); private ILogger logger; synchronized void debug(String message, Object... params) { if (this.logger != null) { this.logger.debug(message, params); return; } this.buffer.add(new LogEntry(message, params, null)); } synchronized void debug(String message, Throwable t) { if (this.logger != null) { this.logger.debug(message, t); return; } this.buffer.add(new LogEntry(message, new Object[0], t)); } /** * Flush the contents of the buffer into the specified logger */ synchronized void flush(ILogger logger) { for (LogEntry buffered : this.buffer) { if (buffered.t != null) { logger.debug(buffered.message, ObjectArrays.concat(buffered.params, buffered.t)); } else { logger.debug(buffered.message, buffered.params); } } this.buffer.clear(); this.logger = logger; } } /** * Log buffer for messages generated during service startup but before the * actual logger can be retrieved from the service, flushed once the service * is started */ private static LogBuffer logBuffer = new LogBuffer(); /** * Singleton */ private static MixinService instance; private ServiceLoader bootstrapServiceLoader; private final Set bootedServices = new HashSet(); /** * Service loader */ private ServiceLoader serviceLoader; /** * Service */ private IMixinService service = null; /** * Global Property Service */ private IGlobalPropertyService propertyService; /** * Singleton pattern */ private MixinService() { this.runBootServices(); } private void runBootServices() { // bypass service loader if the mixin.bootstrapService system property yields the desired IMixinServiceBootstrap implementation directly String serviceCls = System.getProperty("mixin.bootstrapService"); if (serviceCls != null) { try { IMixinServiceBootstrap bootService = (IMixinServiceBootstrap) Class.forName(serviceCls).getConstructor().newInstance(); bootService.bootstrap(); bootedServices.add(bootService.getServiceClassName()); return; } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } this.bootstrapServiceLoader = ServiceLoader.load(IMixinServiceBootstrap.class, this.getClass().getClassLoader()); Iterator iter = this.bootstrapServiceLoader.iterator(); while (iter.hasNext()) { try { IMixinServiceBootstrap bootService = iter.next(); bootService.bootstrap(); this.bootedServices.add(bootService.getServiceClassName()); } catch (ServiceInitialisationException ex) { // Expected if service cannot start MixinService.logBuffer.debug("Mixin bootstrap service {} is not available: {}", ex.getStackTrace()[0].getClassName(), ex.getMessage()); } catch (Throwable th) { MixinService.logBuffer.debug("Catching {}:{} initialising service", th.getClass().getName(), th.getMessage(), th); } } } /** * Singleton pattern, get or create the instance */ private static MixinService getInstance() { if (MixinService.instance == null) { MixinService.instance = new MixinService(); } return MixinService.instance; } /** * Boot */ public static void boot() { MixinService.getInstance(); } public static IMixinService getService() { return MixinService.getInstance().getServiceInstance(); } private synchronized IMixinService getServiceInstance() { if (this.service == null) { try { this.service = this.initService(); ILogger serviceLogger = this.service.getLogger("mixin"); MixinService.logBuffer.flush(serviceLogger); } catch (Error err) { ILogger defaultLogger = MixinService.getDefaultLogger(); MixinService.logBuffer.flush(defaultLogger); defaultLogger.error(err.getMessage(), err); throw err; } } return this.service; } private IMixinService initService() { // bypass service loader if the mixin.service system property yields the desired IMixinService implementation directly String serviceCls = System.getProperty("mixin.service"); // FIXME: there is overlap with bootedServices, may just use that directly? if (serviceCls != null) { try { IMixinService service = (IMixinService) Class.forName(serviceCls).getConstructor().newInstance(); if (!service.isValid()) { throw new RuntimeException("invalid service " + serviceCls + " configured via system property"); } return service; } catch (ReflectiveOperationException e) { throw new RuntimeException(e); } } this.serviceLoader = ServiceLoader.load(IMixinService.class, this.getClass().getClassLoader()); Iterator iter = this.serviceLoader.iterator(); List badServices = new ArrayList(); int brokenServiceCount = 0; while (iter.hasNext()) { try { IMixinService service = iter.next(); if (this.bootedServices.contains(service.getClass().getName())) { MixinService.logBuffer.debug("MixinService [{}] was successfully booted in {}", service.getName(), this.getClass().getClassLoader()); } if (service.isValid()) { return service; } MixinService.logBuffer.debug("MixinService [{}] is not valid", service.getName()); badServices.add(String.format("INVALID[%s]", service.getName())); } catch (ServiceConfigurationError sce) { // sce.printStackTrace(); brokenServiceCount++; } catch (Throwable th) { String faultingClassName = th.getStackTrace()[0].getClassName(); MixinService.logBuffer.debug("MixinService [{}] failed initialisation: {}", faultingClassName, th.getMessage()); int pos = faultingClassName.lastIndexOf('.'); badServices.add(String.format("ERROR[%s]", pos < 0 ? faultingClassName : faultingClassName.substring(pos + 1))); // th.printStackTrace(); } } String brokenServiceNote = brokenServiceCount == 0 ? "" : " and " + brokenServiceCount + " other invalid services."; throw new ServiceNotAvailableError("No mixin host service is available. Services: " + Joiner.on(", ").join(badServices) + brokenServiceNote); } /** * Blackboard */ public static IGlobalPropertyService getGlobalPropertyService() { return MixinService.getInstance().getGlobalPropertyServiceInstance(); } /** * Retrieves the GlobalPropertyService Instance... FactoryProviderBean... * Observer...InterfaceStream...Function...Broker... help me why won't it * stop */ private IGlobalPropertyService getGlobalPropertyServiceInstance() { if (this.propertyService == null) { this.propertyService = this.initPropertyService(); } return this.propertyService; } private IGlobalPropertyService initPropertyService() { ServiceLoader serviceLoader = ServiceLoader.load(IGlobalPropertyService.class, this.getClass().getClassLoader()); Iterator iter = serviceLoader.iterator(); while (iter.hasNext()) { try { IGlobalPropertyService service = iter.next(); return service; } catch (ServiceConfigurationError serviceError) { // serviceError.printStackTrace(); } catch (Throwable th) { // th.printStackTrace(); } } throw new ServiceNotAvailableError("No mixin global property service is available"); } /** * Returns Object so that ILogger is not classloaded until after it doesn't * matter any more */ @SuppressWarnings("unchecked") private static T getDefaultLogger() { return (T)new LoggerAdapterConsole("mixin").setDebugStream(System.err); } }