/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.felix.scr.impl;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

import org.apache.felix.scr.impl.manager.ScrConfiguration;
import org.apache.felix.scr.info.ScrInfo;
import org.apache.felix.service.command.Converter;
import org.apache.felix.service.command.Descriptor;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.framework.dto.ServiceReferenceDTO;
import org.osgi.framework.wiring.BundleRevision;
import org.osgi.framework.wiring.BundleWiring;
import org.osgi.service.component.ComponentConstants;
import org.osgi.service.component.ComponentFactory;
import org.osgi.service.component.runtime.ServiceComponentRuntime;
import org.osgi.service.component.runtime.dto.ComponentConfigurationDTO;
import org.osgi.service.component.runtime.dto.ComponentDescriptionDTO;
import org.osgi.service.component.runtime.dto.ReferenceDTO;
import org.osgi.service.component.runtime.dto.SatisfiedReferenceDTO;
import org.osgi.service.component.runtime.dto.UnsatisfiedReferenceDTO;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;

public class ComponentCommands implements ServiceTrackerCustomizer<Object, ServiceRegistration<?>> {

    private static final String INDENT_1 = "  ";
    private static final String INDENT_2 = INDENT_1 + INDENT_1;

    private final BundleContext context;
    private final ServiceComponentRuntime scr;
    private final ScrConfiguration scrConfig;
    private final ServiceTracker<Object, ServiceRegistration<?>> gogoRuntimeTracker;

    private final Comparator<ComponentConfigurationDTO> configDtoComparator = new Comparator<ComponentConfigurationDTO>() {
        @Override
        public int compare(ComponentConfigurationDTO o1, ComponentConfigurationDTO o2) {
            long diff = o1.id - o2.id;
            return diff == 0L ? 0 : (int) (diff / Math.abs(diff));
        }
    };
    private final Comparator<ServiceReferenceDTO> serviceRefDtoComparator = new Comparator<ServiceReferenceDTO>() {
        @Override
        public int compare(ServiceReferenceDTO o1, ServiceReferenceDTO o2) {
            long diff = o1.id - o2.id;
            return diff == 0L ? 0 : (int) (diff / Math.abs(diff));
        }
    };

    private ServiceRegistration<ComponentCommands> commandsReg = null;
    private ServiceRegistration<ScrInfo> scrInfoReg = null;

    synchronized void register() {
        if (commandsReg != null) {
            throw new IllegalStateException("Component Commands already registered");
        }

        Dictionary<String, Object> svcProps;

        svcProps = new Hashtable<>();
        svcProps.put("osgi.command.scope", "scr");
        svcProps.put("osgi.command.function", new String[] {
                "config",
                "disable",
                "enable",
                "info",
                "list"
        });
        svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Gogo Shell Support");
        svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
        commandsReg = context.registerService(ComponentCommands.class, this, svcProps);

        gogoRuntimeTracker.open(true);
    }

    synchronized void unregister() {
        gogoRuntimeTracker.close();
        safeUnregister(commandsReg);
        safeUnregister(scrInfoReg);
    }

    public synchronized void updateProvideScrInfoService(boolean register) {
        if (register) {
            if (scrInfoReg == null) {
                Dictionary<String, Object> svcProps = new Hashtable<>();
                svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Info Service");
                svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
                scrInfoReg = context.registerService(ScrInfo.class, new ComponentCommandsScrInfo(this, context), svcProps);
            }
        } else {
            safeUnregister(scrInfoReg);
            scrInfoReg = null;
        }
    }

    protected ComponentCommands(BundleContext context, ServiceComponentRuntime scr, ScrConfiguration scrConfig) {
        this.context = context;
        this.scr = scr;
        this.scrConfig = scrConfig;
        this.gogoRuntimeTracker = new ServiceTracker<>(context, "org.apache.felix.service.command.CommandProcessor", this);
    }

    @Descriptor("List all components")
    public ComponentDescriptionDTO[] list() {
        ComponentDescriptionDTO[] result = scr.getComponentDescriptionDTOs().toArray(new ComponentDescriptionDTO[0]);
        Arrays.sort(result, new Comparator<ComponentDescriptionDTO>() {
            @Override
            public int compare(ComponentDescriptionDTO c1, ComponentDescriptionDTO c2) {
                return Long.compare(c1.bundle.id, c2.bundle.id);
            }
        });
        return result;
    }

    @Descriptor("List components of a specific bundle")
    public ComponentDescriptionDTO[] list(@Descriptor("ID of the bundle") long bundleId) {
        Bundle bundle = context.getBundle(bundleId);
        return bundle != null ? scr.getComponentDescriptionDTOs(bundle).toArray(new ComponentDescriptionDTO[0]) : null;
    }

    private List<ComponentDescriptionDTO> findComponents(String name) {
        String lowerName = name.toLowerCase();
        List<ComponentDescriptionDTO> matches = new LinkedList<>();
        for (ComponentDescriptionDTO dto : scr.getComponentDescriptionDTOs()) {
            if (dto.name.equalsIgnoreCase(name)) {
                // Exact match, return only this component.
                return Collections.singletonList(dto);
            }
            if (dto.name.toLowerCase().contains(lowerName))
                matches.add(dto);
        }
        return matches;
    }

    @Descriptor("Dump information of a component")
    public ComponentDescriptionDTO info(@Descriptor("Name of the component") String name) {
        List<ComponentDescriptionDTO> matches = findComponents(name);
        if (matches.isEmpty()) {
            throw new IllegalArgumentException(MessageFormat.format("No component description matching \"{0}\".", name));
        } else if (matches.size() > 1) {
            StringBuilder partialMatchesStr = new StringBuilder();
            for (Iterator<ComponentDescriptionDTO> iter = matches.iterator(); iter.hasNext(); ) {
                partialMatchesStr.append(iter.next().name);
                if (iter.hasNext()) partialMatchesStr.append(", ");
            }
            throw new IllegalArgumentException(MessageFormat.format("Multiple components matching \"{0}\": [{1}]", name, partialMatchesStr));
        }
        return matches.get(0);
    }

    @Descriptor("Dump information of a component configuration")
    public ComponentConfigurationDTO info(@Descriptor("ID of the component configuration") long id) {
        for (ComponentDescriptionDTO descDto : scr.getComponentDescriptionDTOs()) {
            for (ComponentConfigurationDTO configDto : scr.getComponentConfigurationDTOs(descDto)) {
                if (configDto.id == id)
                    return configDto;
            }
        }
        return null;
    }

    @Descriptor("Enable a disabled component")
    public boolean enable(@Descriptor("Name of the component") final String name) {
        boolean changed = false;
        for (ComponentDescriptionDTO comp : findComponents(name)) {
            if (!scr.isComponentEnabled(comp)) {
                scr.enableComponent(comp);
                changed = true;
            }
        }
        return changed;
    }

    @Descriptor("Disable an enabled component")
    public boolean disable(@Descriptor("Name of the component") final String name) {
        boolean changed = false;
        for (ComponentDescriptionDTO comp : findComponents(name)) {
            if (scr.isComponentEnabled(comp)) {
                scr.disableComponent(comp);
                changed = true;
            }
        }
        return changed;
    }

    @SuppressWarnings("deprecation")
    @Descriptor("Show the current SCR configuration")
    public String config() {
        Map<String,String> out = new LinkedHashMap<>();
        out.put("Log Level", Integer.toString(scrConfig.getLogLevel()));
        out.put("Obsolete Component Factory with Factory Configuration", Boolean.toString(scrConfig.isFactoryEnabled()));
        out.put("Keep instances with no references", scrConfig.keepInstances() ? "Supported" : "Unsupported");
        out.put("Lock timeout ms", Long.toString(scrConfig.lockTimeout()));
        out.put("Stop timeout ms", Long.toString(scrConfig.stopTimeout()));
        out.put("Global extender", Boolean.toString(scrConfig.globalExtender()));
        out.put("Info Service registered", scrConfig.infoAsService() ? "Supported" : "Unsupported");

        StringBuilder builder = new StringBuilder();
        printColumnsAligned("SCR Configuration", out, '=', builder);
        return builder.toString();
    }

    public Object convert(Class<?> desiredType, Object in) throws Exception {
        return null;
    }

    public CharSequence format(Object target, int level) throws Exception {
        final CharSequence result;
        if (target instanceof ComponentDescriptionDTO[]) {
            result = format((ComponentDescriptionDTO[]) target, level);
        } else if (target instanceof ComponentDescriptionDTO) {
            result = format((ComponentDescriptionDTO) target, level);
        } else if (target instanceof ComponentConfigurationDTO) {
            result = format((ComponentConfigurationDTO) target, level);
        } else {
            result = null;
        }
        return result;
    }

    CharSequence format(ComponentDescriptionDTO[] dtoArray, int level) throws Exception {
        StringBuilder sb = new StringBuilder();
        if (dtoArray == null || dtoArray.length == 0) {
            sb.append("No component descriptions found");
        } else {
            for (int i = 0; i < dtoArray.length; i++) {
                if (i > 0) sb.append('\n');
                sb.append(format(dtoArray[i], Converter.LINE));
            }
        }
        return sb;
    }

    CharSequence format(ComponentDescriptionDTO dto, int level) throws Exception {
        final StringBuilder builder = new StringBuilder();

        // Get the child ComponentConfigurationDTOs and sort by id field.
        final List<ComponentConfigurationDTO> children;
        Collection<ComponentConfigurationDTO> childrenTmp = scr.getComponentConfigurationDTOs(dto);
        if (childrenTmp == null) {
            children = Collections.emptyList();
        } else {
            children = new ArrayList<>(childrenTmp);
            Collections.sort(children, configDtoComparator);
        }

        switch (level) {
            case Converter.LINE:
                builder.append(MessageFormat.format("{0} in bundle {1} ({2}:{3}) {4}, {5,choice,0#0 instances|1#1 instance|1<{5} instances}.",
                        dto.name,
                        dto.bundle.id,
                        dto.bundle.symbolicName,
                        dto.bundle.version,
                        dto.defaultEnabled ? "enabled" : "disabled",
                        children.size()
                ));

                for (ComponentConfigurationDTO child : children)
                    builder.append("\n").append(INDENT_2).append(format(child, Converter.LINE));
                break;
            case Converter.INSPECT:
                printComponentDescriptionAndConfigs(dto, children.toArray(new ComponentConfigurationDTO[0]), builder);
                break;
            case Converter.PART:
                break;
        }
        return builder;
    }

    CharSequence format(ComponentConfigurationDTO dto, int level) throws Exception {
        final StringBuilder builder = new StringBuilder();
        switch (level) {
            case Converter.INSPECT:
                printComponentDescriptionAndConfigs(dto.description, new ComponentConfigurationDTO[]{dto}, builder);
                break;
            case Converter.LINE:
                builder.append("Id: ").append(dto.id);
                builder.append(", ").append("State:").append(stateToString(dto.state));
                String[] pids = getStringArray(dto.properties, Constants.SERVICE_PID, null);
                if (pids != null && pids.length > 0) {
                    builder.append(", ").append("PID(s): ").append(Arrays.toString(pids));
                }
                break;
            case Converter.PART:
                break;
        }
        return builder;
    }

    void printComponentDescriptionAndConfigs(ComponentDescriptionDTO descDto, ComponentConfigurationDTO[] configs, StringBuilder builder) {
        final Map<String, String> out = new LinkedHashMap<>();

        // Component Description
        out.put("Class", descDto.implementationClass);
        out.put("Bundle", String.format("%d (%s:%s)", descDto.bundle.id, descDto.bundle.symbolicName, descDto.bundle.version));
        out.put("Enabled", Boolean.toString(descDto.defaultEnabled));
        out.put("Immediate", Boolean.toString(descDto.immediate));
        out.put("Services", arrayToString(descDto.serviceInterfaces));
        if (descDto.scope != null) {
            out.put("Scope", descDto.scope);
        }
        out.put("Config PID(s)", String.format("%s, Policy: %s", arrayToString(descDto.configurationPid), descDto.configurationPolicy));
        out.put("Base Props", printProperties(descDto.properties, INDENT_1));
        if (descDto.factory != null) {
            out.put("Factory", descDto.factory);
            try {
                ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(ComponentFactory.class.getName(), String.format("(&(%s=%s)(%s=%d))", ComponentConstants.COMPONENT_NAME, descDto.name, Constants.SERVICE_BUNDLEID, descDto.bundle.id));
                if (serviceRefs != null && serviceRefs.length > 0) {
                    out.put("Factory Service", printPublishedServices(serviceRefs));
                }
            } catch (InvalidSyntaxException e) {
                // shouldn't happen
            }
        }
        printColumnsAligned(String.format("Component Description: %s", descDto.name), out, '=', builder);

        if (configs != null) for (ComponentConfigurationDTO configDto : configs) {
            out.clear();

            // Blank line separator
            builder.append("\n\n");

            // Inspect configuration DTO
            String title = String.format("Component Configuration Id: %d", configDto.id);
            out.put("State", stateToString(configDto.state));

            // Print service registration
            try {
                ServiceReference<?>[] serviceRefs = context.getAllServiceReferences(null, String.format("(%s=%d)", ComponentConstants.COMPONENT_ID, configDto.id));
                if (serviceRefs != null && serviceRefs.length > 0) {
                    out.put("Service", printPublishedServices(serviceRefs));
                }
            } catch (InvalidSyntaxException e) {
                // Shouldn't happen...
            }

            // Print Configuration Properties
            out.put("Config Props", printProperties(configDto.properties, INDENT_1));

            // Print References
            out.put("References", printServiceReferences(configDto.satisfiedReferences, configDto.unsatisfiedReferences, descDto.references));

            // Print Failure
            if (configDto.failure != null) {
                out.put("Failure", configDto.failure);
            }
            printColumnsAligned(title, out, '-', builder);
        }
    }

    String printPublishedServices(ServiceReference<?>[] serviceRefs) {
        StringBuilder sb = new StringBuilder();

        if (serviceRefs.length > 1) {
            sb.append("(total ").append(serviceRefs.length).append(')');
            sb.append('\n').append(INDENT_1);
        }

        for (ServiceReference<?> serviceRef : serviceRefs) {
            sb.append(serviceRef.getProperty(Constants.SERVICE_ID));
            sb.append(' ').append(Arrays.toString((String[]) serviceRef.getProperty(Constants.OBJECTCLASS)));
            Bundle[] consumers = serviceRef.getUsingBundles();
            if (consumers != null) for (Bundle consumer : consumers) {
                sb.append("\n").append(INDENT_2);
                sb.append(String.format("Used by bundle %d (%s:%s)", consumer.getBundleId(), consumer.getSymbolicName(), consumer.getVersion()));
            }
        }

        return sb.toString();
    }

    private String arrayToString(String[] array) {
        return array == null || array.length == 0 ? "<<none>>" : Arrays.toString(array);
    }

    static final String stateToString(int state) {
        final String string;
        switch (state) {
            case ComponentConfigurationDTO.ACTIVE:
                string = "ACTIVE";
                break;
            case ComponentConfigurationDTO.SATISFIED:
                string = "SATISFIED";
                break;
            case ComponentConfigurationDTO.UNSATISFIED_CONFIGURATION:
                string = "UNSATISFIED CONFIGURATION";
                break;
            case ComponentConfigurationDTO.UNSATISFIED_REFERENCE:
                string = "UNSATISFIED REFERENCE";
                break;
            case ComponentConfigurationDTO.FAILED_ACTIVATION:
                string = "FAILED ACTIVATION";
                break;
            default:
                string = String.format("<<UNKNOWN: %d>>", state);
        }
        return string;
    }

    static String printProperties(Map<String, ?> props, String indent) {
        StringBuilder builder = new StringBuilder();
        int size = props.size();
        builder.append('(').append(Integer.toString(size)).append(' ').append(size == 1 ? "entry" : "entries").append(')');
        if (size > 0) {
            final SortedMap<String, ?> sortedMap = new TreeMap<>(props);
            for (Map.Entry<String, ?> e : sortedMap.entrySet()) {
                builder.append('\n').append(indent);

                final Object value = e.getValue();
                final String typeName;
                final String valueStr;

                if (value == null) {
                    typeName = valueStr = "null";
                } else {
                    typeName = value.getClass().getSimpleName();
                    if (value instanceof int[])
                        valueStr = Arrays.toString((int[]) value);
                    else if (value instanceof long[])
                        valueStr = Arrays.toString((long[]) value);
                    else if (value instanceof byte[])
                        valueStr = Arrays.toString((byte[]) value);
                    else if (value instanceof short[])
                        valueStr = Arrays.toString((short[]) value);
                    else if (value instanceof byte[])
                        valueStr = Arrays.toString((byte[]) value);
                    else if (value instanceof char[])
                        valueStr = Arrays.toString((char[]) value);
                    else if (value instanceof boolean[])
                        valueStr = Arrays.toString((boolean[]) value);
                    else if (value instanceof float[])
                        valueStr = Arrays.toString((boolean[]) value);
                    else if (value instanceof double[])
                        valueStr = Arrays.toString((boolean[]) value);
                    else if (value instanceof Object[])
                        valueStr = Arrays.deepToString((Object[]) value);
                    else
                        valueStr = value.toString();
                }
                builder.append(String.format("%s<%s> = %s", e.getKey(), typeName, valueStr));
            }
        }
        return builder.toString();
    }

    String printServiceReferences(SatisfiedReferenceDTO[] satisfiedReferences, UnsatisfiedReferenceDTO[] unsatisfiedReferences, ReferenceDTO[] references) {
        StringBuilder builder = new StringBuilder();
        final Map<String, ReferenceDTO> refDtoMap = new HashMap<>();
        if (references != null) {
            for (ReferenceDTO refDto : references)
                refDtoMap.put(refDto.name, refDto);
        }
        int refCount = (satisfiedReferences != null ? satisfiedReferences.length : 0)
                + (unsatisfiedReferences != null ? unsatisfiedReferences.length : 0);
        builder.append("(total ").append(Integer.toString(refCount)).append(")");
        if (unsatisfiedReferences != null) {
            for (UnsatisfiedReferenceDTO refDto : unsatisfiedReferences)
                printServiceReference(refDtoMap.get(refDto.name), "UNSATISFIED", null, builder);
        }
        if (satisfiedReferences != null) {
            for (SatisfiedReferenceDTO refDto : satisfiedReferences)
                printServiceReference(refDtoMap.get(refDto.name), "SATISFIED", refDto.boundServices != null ? refDto.boundServices : new ServiceReferenceDTO[0], builder);
        }
        return builder.toString();
    }

    void printServiceReference(ReferenceDTO reference, String state, ServiceReferenceDTO[] bindings, StringBuilder builder) {
        StringBuilder policyWithOption = new StringBuilder().append(reference.policy);
        if (!"reluctant".equals(reference.policyOption))
            policyWithOption.append('+').append(reference.policyOption);

        builder.append(String.format("%n" + INDENT_1 + "- %s: %s %s %s %s", reference.name, reference.interfaceName, state, reference.cardinality, policyWithOption));
        builder.append(String.format("%n" + INDENT_1 + "  target=%s scope=%s", reference.target == null ? "(*)" : reference.target, reference.scope == null ? "bundle" : reference.scope));
        if (reference.collectionType != null) {
            builder.append(" collectionType=").append(reference.collectionType);
        }

        if (bindings != null) {
            Arrays.sort(bindings, serviceRefDtoComparator);
            builder.append(MessageFormat.format(" {0,choice,0#(no active bindings)|1#(1 binding):|1<({0} bindings):}", bindings.length));
            for (ServiceReferenceDTO svcDto : bindings) {
                Bundle provider = context.getBundle(svcDto.bundle);
                builder.append(String.format("%n" + INDENT_1 + "  * Bound to [%d] from bundle %d (%s:%s)", svcDto.id, svcDto.bundle, provider.getSymbolicName(), provider.getVersion()));
            }
        }
    }

    static void printColumnsAligned(String title, Map<String, String> properties, char underlineChar, StringBuilder builder) {
        builder.append(title);

        // Generate the title underline
        char[] carray = new char[title.length()];
        Arrays.fill(carray, underlineChar);
        builder.append('\n');
        builder.append(carray);

        int widestKey = 0;
        for (String key : properties.keySet()) {
            widestKey = Math.max(widestKey, key.length());
        }

        for (Map.Entry<String, String> e : properties.entrySet()) {
            String key = e.getKey();
            int padLength = widestKey - key.length();
            char[] padding = new char[padLength];
            Arrays.fill(padding, ' ');

            builder.append('\n');
            builder.append(key).append(": ");
            builder.append(padding);
            builder.append(e.getValue());
        }
    }

    private static String[] getStringArray(Map<String, ?> map, String name, String[] defaultValue) throws IllegalArgumentException {
        Object o = map.get(name);
        if (o instanceof String) {
            return new String[]{(String) o};
        } else if (o instanceof String[]) {
            return (String[]) o;
        } else if (o instanceof Collection) {
            Collection<?> c = (Collection<?>) o;
            if (c.isEmpty()) {
                return new String[0];
            } else {
                String[] a = new String[c.size()];
                Iterator<?> iter = c.iterator();
                for (int i = 0; i < a.length; i++) {
                    Object elem = iter.next();
                    if (!(elem instanceof String))
                        throw new IllegalArgumentException(String.format("Collection value for field '%s' contains non-String element at index %d.", name, i));
                    a[i] = (String) elem;
                }
                return a;
            }
        } else if (o == null) {
            return defaultValue;
        } else {
            throw new IllegalArgumentException(String.format("Value for field '%s' is not a String, String-array or Collection of String. Actual type was %s.", name, o.getClass().getName()));
        }
    }

    private void safeUnregister(ServiceRegistration<?> registration) {
        try {
            if (registration != null) registration.unregister();
        } catch (Exception e) {
            // ignore
        }
    }

    @Override
    public ServiceRegistration<?> addingService(ServiceReference<Object> reference) {
        Bundle b = reference.getBundle();
        BundleRevision rev = b == null ? null : b.adapt(BundleRevision.class);
        if (rev != null) {
            Object converter = createConverter(b);
            if (converter != null) {
                Dictionary<String, Object> svcProps = new Hashtable<>();
                svcProps.put("osgi.converter.classes", new String[] {
                        ComponentDescriptionDTO.class.getName(),
                        ComponentConfigurationDTO.class.getName()
                });
                svcProps.put(Constants.SERVICE_DESCRIPTION, "SCR Runtime DTO Converter");
                svcProps.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");

                return b.getBundleContext().registerService("org.apache.felix.service.command.Converter", converter, svcProps);
            }
        }
        return null;
    }

    private Object createConverter(Bundle bundle) {
        BundleWiring wiring = bundle.adapt(BundleWiring.class);
        if (wiring != null) {
            ClassLoader cl = wiring.getClassLoader();
            if (cl != null) {
                try {
                    Class<?>[] types = new Class[] {cl.loadClass("org.apache.felix.service.command.Converter")};
                    return Proxy.newProxyInstance(cl, types, new InvocationHandler() {
                        
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            if ("convert".equals(method.getName())) {
                                return convert((Class<?>) args[0], args[1]);
                            }
                            if ("format".equals(method.getName())) {
                                return format(args[0], (int) args[1]);
                            }
                            return method.invoke(this, args);
                        }
                    });
                } catch (ClassNotFoundException e) {
                    
                }

            }
        }
        return null;
    }

    @Override
    public void modifiedService(ServiceReference<Object> reference, ServiceRegistration<?> reg) {
        // nothing
    }

    @Override
    public void removedService(ServiceReference<Object> reference, ServiceRegistration<?> reg) {
        safeUnregister(reg);
    }

}
