package io.advantageous.qbit.example.perf;

import io.advantageous.boon.core.Sys;
import io.advantageous.qbit.message.MethodCall;
import io.advantageous.qbit.message.MethodCallBuilder;
import io.advantageous.qbit.queue.Queue;
import io.advantageous.qbit.queue.QueueBuilder;
import io.advantageous.qbit.queue.SendQueue;
import io.advantageous.qbit.service.ServiceBuilder;
import io.advantageous.qbit.service.ServiceProxyUtils;
import io.advantageous.qbit.service.ServiceQueue;
import io.advantageous.qbit.service.impl.ServiceConstants;
import io.advantageous.qbit.stream.QueueToStreamRoundRobin;
import io.advantageous.qbit.stream.QueueToStreamUnicast;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicLong;

public class Main {

    public static class Trade {
        final String name;
        final long amount;

        public Trade(String name, long amount) {
            this.name = name;
            this.amount = amount;
        }

        public String getName() {
            return name;
        }

        public long getAmount() {
            return amount;
        }
    }

    public interface TradeService {

        void trade(Trade trade);
    }

    public static class TradeServiceImpl {

        final AtomicLong tradeCounter = new AtomicLong();

        void trade(Trade trade) {
            trade.getAmount();
            tradeCounter.incrementAndGet();
        }

    }

    private static void runService(int runs, int tradeCount, int batchSize, int checkEvery, boolean dynamic, boolean noStub,
                                   int numThreads) {

        final QueueBuilder queueBuilder = QueueBuilder.queueBuilder().setName("trades").setBatchSize(batchSize)
                .setPollWait(1_000)
                .setSize(20_000_000);

        if (checkEvery>0) {
            queueBuilder.setLinkTransferQueue();
            queueBuilder.setCheckEvery(checkEvery);
        }

        final TradeServiceImpl tradeServiceImpl = new TradeServiceImpl();

        final ServiceBuilder serviceBuilder = ServiceBuilder.serviceBuilder()
                .setRequestQueueBuilder(queueBuilder)
                .setInvokeDynamic(dynamic).setServiceObject(tradeServiceImpl);

        if (noStub) {
            serviceBuilder.setServiceMethodHandler(methodCall -> {
                final Trade trade = (Trade) methodCall.args()[0];
                tradeServiceImpl.trade(trade);
                return ServiceConstants.VOID;
            });
        }

        final ServiceQueue serviceQueue = serviceBuilder.build().startServiceQueue();

        final long startRun = System.currentTimeMillis();

        for (int r = 0; r < runs; r++) {

            final long startTime = System.currentTimeMillis();

            final List<Thread> threadList = new ArrayList<>();

            for (int th = 0; th < numThreads; th++) {
                final Thread thread = new Thread(() -> {

                    final SendQueue<MethodCall<Object>> requests = serviceQueue.requests();
                    final TradeService tradeService = noStub ? trade -> {
                        final MethodCall<Object> methodCall = MethodCallBuilder.methodCallBuilder().setName("trade")
                                .setLocal(true).setBodyArgs(new Object[]{trade}).build();
                        requests.send(methodCall);
                    } : serviceQueue.createProxy(TradeService.class);

                    for (int t = 0; t < tradeCount; t++) {
                        tradeService.trade(new Trade("ibm", 100L));
                    }
                    ServiceProxyUtils.flushServiceProxy(tradeService);
                    requests.flushSends();

                });
                thread.start();
                threadList.add(thread);

            }

            for (int index = 0; index < 1000; index++) {
                Sys.sleep(1);
                if (tradeServiceImpl.tradeCounter.get() >=
                        (tradeCount * numThreads)) {
                    break;
                }
            }

            threadList.forEach(thread -> {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

            System.out.printf("%d traded %,d in %d batchSize = %,d\n",
                    r,
                    tradeCount * numThreads,
                    System.currentTimeMillis() - startTime,
                    batchSize);
        }

        System.out.printf("DONE traded %,d in %d batchSize = %,d, checkEvery = %,d\n",
                tradeCount * runs * numThreads,
                System.currentTimeMillis() - startRun,
                batchSize,
                checkEvery);
        serviceQueue.stop();

        Sys.sleep(2_000);
        System.gc();
        Sys.sleep(2_000);
    }

    public static void mainService(String... args) throws Exception {

        final int runs = 20;
        final int tradeCount = 250_000;
        final int batchSize = 1_000;
        final boolean dynamic = false;

        //int currentBatchSize = batchSize;

        for (int index=0; index< 100; index++) {
            runService(runs, tradeCount, batchSize, 0, dynamic, true, 4);
            //currentBatchSize*=2;
        }

        Sys.sleep(Integer.MAX_VALUE);
        System.out.println("DONE");
    }

    //public static void mainArrayBlockingQueue(String... args) throws Exception {
    public static void mainArrayBlockingQueue(String... args) throws Exception {

        final int runs = 20;
        final int tradeCount = 5_000_000;
        final int batchSize = 125;

        int currentBatchSize = batchSize;

        run(runs, tradeCount, 1);

        run(runs, tradeCount, batchSize);

        for (int index=0; index< 10; index++) {
            run(runs, tradeCount, currentBatchSize);
            currentBatchSize*=2;
        }
    }

    public static void mainStream(String... args) throws Exception {

        final int runs = 10;
        final int tradeCount = 10_000_000;
        final int batchSize = 100_000;
        final int checkEvery = 0;

        run(runs, tradeCount, batchSize);
        run(runs, tradeCount, batchSize);
        run(runs, tradeCount, batchSize);

        for (int index=0; index< 10; index++) {
            runPublisher(runs, tradeCount, batchSize, checkEvery);
        }
    }

    public static void main(String... args) throws Exception {

        final int runs = 10;
        final int tradeCount = 710_000;
        final int batchSize = 100_000;
        final int checkEvery = 0;
        final int subscribers = 4;
        final int numThreads = 7;

        for (int index=0; index< 10; index++) {
            runPublisherMultiSubs(runs, tradeCount, batchSize, checkEvery, subscribers, numThreads);
        }
    }

    //public static void main(String... args) throws Exception {

    public static void mainTransferQueue(String... args) throws Exception {

        final int runs = 20;
        final int tradeCount = 5_000_000;
        final int batchSize = 50_000;
        final int checkEvery = 1000;

        int currentCheckEvery = checkEvery;

        run(runs, tradeCount, batchSize, 10);

        for (int index=0; index< 10; index++) {
            run(runs, tradeCount, batchSize, currentCheckEvery);
            currentCheckEvery*=2;
        }
    }

    private static void run(int runs, int tradeCount, int batchSize) {
        run(runs, tradeCount, batchSize, 0);
    }

    private static void run(int runs, int tradeCount, int batchSize, int checkEvery) {

        final QueueBuilder queueBuilder = QueueBuilder.queueBuilder().setName("trades").setBatchSize(batchSize)
                .setSize(1_000_000);

        if (checkEvery>0) {
            queueBuilder.setLinkTransferQueue();
            queueBuilder.setCheckEvery(checkEvery);
        }

        final Queue<Trade> queue = queueBuilder.build();
        final AtomicLong tradeCounter = new AtomicLong();

        queue.startListener(item -> {

            tradeCounter.incrementAndGet();
        });

        final SendQueue<Trade> tradeSendQueue = queue.sendQueue();

        final long startRun = System.currentTimeMillis();

        for (int r = 0; r < runs; r++) {

           //final long startTime = System.currentTimeMillis();
           for (int t =0; t < tradeCount; t++) {
               tradeSendQueue.send(new Trade("ibm", 100L));
           }
           tradeSendQueue.flushSends();

            for (int index = 0; index < 1000; index++) {
                Sys.sleep(10);
                if (tradeCounter.get() >= tradeCount) {
                    break;
                }
            }

//            System.out.printf("%d traded %,d in %d batchSize = %,d\n",
//                    r,
//                    tradeCount,
//                    System.currentTimeMillis() - startTime,
//                    batchSize);
        }

        System.out.printf("DONE traded %,d in %d batchSize = %,d, checkEvery = %,d\n",
                tradeCount * runs,
                System.currentTimeMillis() - startRun,
                batchSize,
                checkEvery);
        queue.stop();

        Sys.sleep(2_000);
        System.gc();
        Sys.sleep(2_000);
    }

    private static void runPublisher(int runs, int tradeCount, final int batchSize, int checkEvery) throws InterruptedException {

        final QueueBuilder queueBuilder = QueueBuilder.queueBuilder().setName("trades").setBatchSize(batchSize)
                .setSize(1_000_000);

        if (checkEvery>0) {
            queueBuilder.setLinkTransferQueue();
            queueBuilder.setCheckEvery(checkEvery);
        }

        final Queue<Trade> queue = queueBuilder.build();
        final AtomicLong tradeCounter = new AtomicLong();

        final QueueToStreamUnicast<Trade> stream = new QueueToStreamUnicast<>(queue);

        stream.subscribe(new Subscriber<Trade>() {

            int count = batchSize * 2;
            Subscription subscription;

            @Override
            public void onSubscribe(Subscription s) {
                subscription = s;
                s.request(100_000);
            }

            @Override
            public void onNext(Trade trade) {
                tradeCounter.incrementAndGet();

                count--;
                if (count <= batchSize) {
                    count = batchSize * 2;
                    subscription.request(count);
                }

            }

            @Override
            public void onError(Throwable t) {

            }

            @Override
            public void onComplete() {

            }
        });

        final SendQueue<Trade> tradeSendQueue = queue.sendQueue();

        final long startRun = System.currentTimeMillis();

        for (int r = 0; r < runs; r++) {

            Thread thread = new Thread(() -> {

                for (int t =0; t < tradeCount; t++) {
                    tradeSendQueue.send(new Trade("ibm", 100L));
                }
                tradeSendQueue.flushSends();

            });

            thread.start();

            for (int index = 0; index < 1000; index++) {
                Sys.sleep(10);
                if (tradeCounter.get() >= tradeCount) {
                    break;
                }
            }

            thread.join();

//            System.out.printf("%d traded %,d in %d batchSize = %,d\n",
//                    r,
//                    tradeCount,
//                    System.currentTimeMillis() - startTime,
//                    batchSize);
        }

        System.out.printf("DONE PUB traded %,d in %d batchSize = %,d, checkEvery = %,d\n",
                tradeCount * runs,
                System.currentTimeMillis() - startRun,
                batchSize,
                checkEvery);
        queue.stop();

        Sys.sleep(2_000);
        System.gc();
        Sys.sleep(2_000);
    }

    private static void runPublisherMultiSubs(int runs, int tradeCount, final int batchSize, int checkEvery,
                                              int subscribers, int numThreads) throws InterruptedException {

        final QueueBuilder queueBuilder = QueueBuilder.queueBuilder().setName("trades").setBatchSize(batchSize)
                .setSize(1_000_000);

        if (checkEvery>0) {
            queueBuilder.setLinkTransferQueue();
            queueBuilder.setCheckEvery(checkEvery);
        }

        final Queue<Trade> queue = queueBuilder.build();
        final AtomicLong tradeCounter = new AtomicLong();

        final QueueToStreamRoundRobin<Trade> stream = new QueueToStreamRoundRobin<>(queue);

        for (int s=0; s<subscribers; s++) {

            stream.subscribe(new Subscriber<Trade>() {

                int count = batchSize / numThreads;
                Subscription subscription;
                @Override
                public void onSubscribe(Subscription s) {
                    subscription = s;
                    s.request(count);
                }

                @Override
                public void onNext(Trade trade) {
                    tradeCounter.incrementAndGet();

                    count--;
                    if (count <= batchSize) {
                        count = batchSize  / numThreads;
                        subscription.request(count);
                    }

                }

                @Override
                public void onError(Throwable t) {

                }

                @Override
                public void onComplete() {

                }
            });

        }

        final long startRun = System.currentTimeMillis();

        for (int r = 0; r < runs; r++) {

            List<Thread> threads = new ArrayList<>(numThreads);

            for (int t = 0; t < numThreads; t++) {

                final SendQueue<Trade> tradeSendQueue = queue.sendQueue();

                Thread thread = new Thread(() -> {

                    for (int c = 0; c < tradeCount; c++) {
                        tradeSendQueue.send(new Trade("ibm", 100L));
                    }
                    tradeSendQueue.flushSends();

                });

                thread.start();
                threads.add(thread);
            }

            for (int index = 0; index < 1000; index++) {
                Sys.sleep(10);
                if (tradeCounter.get() >= tradeCount) {
                    break;
                }
            }

            threads.forEach(thread -> {
                try {
                    thread.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });

//            System.out.printf("%d traded %,d in %d batchSize = %,d\n",
//                    r,
//                    tradeCount,
//                    System.currentTimeMillis() - startTime,
//                    batchSize);
        }

        System.out.printf("DONE PUB traded %,d in %d batchSize = %,d, checkEvery = %,d\n",
                tradeCount * runs * numThreads,
                System.currentTimeMillis() - startRun,
                batchSize,
                checkEvery);
        queue.stop();

        Sys.sleep(2_000);
        System.gc();
        Sys.sleep(2_000);
    }

}


package org.jboss.resteasy.spi.metadata;

import org.jboss.resteasy.annotations.Body;
import org.jboss.resteasy.annotations.Form;
import org.jboss.resteasy.annotations.Suspend;
import org.jboss.resteasy.plugins.delegates.MediaTypeHeaderDelegate;
import org.jboss.resteasy.specimpl.ResteasyUriBuilder;
import org.jboss.resteasy.util.IsHttpMethod;
import org.jboss.resteasy.util.MethodHashing;
import org.jboss.resteasy.util.PickConstructor;
import org.jboss.resteasy.util.Types;
import org.projectodd.jrapidoc.annotation.DocDescription;
import org.projectodd.jrapidoc.annotation.rest.DocPathExample;
import org.projectodd.jrapidoc.annotation.rest.DocReturn;
import org.projectodd.jrapidoc.annotation.rest.DocReturns;
import org.projectodd.jrapidoc.logger.Logger;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import javax.ws.rs.*;
import javax.ws.rs.container.Suspended;
import javax.ws.rs.core.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;

import static org.jboss.resteasy.util.FindAnnotation.findAnnotation;

/**
 * @author Bill Burke
 * @version $Revision: 1 $
 */
public class ResourceBuilder {
    public static class ResourceClassBuilder {
        final ResourceClass resourceClass;
        List<FieldParameter> fields = new ArrayList<FieldParameter>();
        List<SetterParameter> setters = new ArrayList<SetterParameter>();
        List<ResourceMethod> resourceMethods = new ArrayList<ResourceMethod>();
        List<ResourceLocator> resourceLocators = new ArrayList<ResourceLocator>();

        public ResourceClassBuilder(Class<?> root, String path) {
            this.resourceClass = new ResourceClass(root, path);
        }

        public ResourceMethodBuilder method(Method method) {
            return new ResourceMethodBuilder(this, method, method);
        }

        public ResourceMethodBuilder method(Method method, Method annotatedMethod) {
            return new ResourceMethodBuilder(this, method, annotatedMethod);
        }

        public ResourceLocatorBuilder locator(Method method) {
            return new ResourceLocatorBuilder(this, method, method);
        }

        public ResourceLocatorBuilder locator(Method method, Method annotatedMethod) {
            return new ResourceLocatorBuilder(this, method, annotatedMethod);
        }

        public FieldParameterBuilder field(Field field) {
            FieldParameter param = new FieldParameter(resourceClass, field);
            return new FieldParameterBuilder(this, param);
        }

        public SetterParameterBuilder setter(Method method) {
            SetterParameter param = new SetterParameter(resourceClass, method, method);
            return new SetterParameterBuilder(this, param);
        }

        public ResourceConstructorBuilder constructor(Constructor constructor) {
            return new ResourceConstructorBuilder(this, constructor);
        }

        public ResourceClass buildClass() {
            resourceClass.fields = fields.toArray(new FieldParameter[fields.size()]);
            resourceClass.setters = setters.toArray(new SetterParameter[setters.size()]);
            resourceClass.resourceMethods = resourceMethods.toArray(new ResourceMethod[resourceMethods.size()]);
            resourceClass.resourceLocators = resourceLocators.toArray(new ResourceLocator[resourceLocators.size()]);

            return resourceClass;
        }
    }

    public static class ParameterBuilder<T extends ParameterBuilder> {
        final Parameter parameter;

        public ParameterBuilder(Parameter parameter) {
            this.parameter = parameter;
        }

        public T type(Class<?> type) {
            parameter.type = type;
            return (T) this;
        }

        public T genericType(Type type) {
            parameter.genericType = type;
            return (T) this;
        }

        public T type(GenericType type) {
            parameter.type = type.getRawType();
            parameter.genericType = type.getType();
            return (T) this;
        }

        public T beanParam() {
            parameter.paramType = Parameter.ParamType.BEAN_PARAM;
            return (T) this;
        }

        public T context() {
            parameter.paramType = Parameter.ParamType.CONTEXT;
            return (T) this;
        }

        public T messageBody() {
            parameter.paramType = Parameter.ParamType.MESSAGE_BODY;
            return (T) this;
        }

        public T encoded() {
            parameter.encoded = true;
            return (T) this;
        }

        public T defaultValue(String defaultValue) {
            parameter.defaultValue = defaultValue;
            return (T) this;
        }

        public T cookieParam(String name) {
            parameter.paramType = Parameter.ParamType.COOKIE_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        public T formParam(String name) {
            parameter.paramType = Parameter.ParamType.FORM_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        /**
         * Resteasy @Form specific injection parameter
         *
         * @param prefix
         * @return
         */
        public T form(String prefix) {
            parameter.paramType = Parameter.ParamType.FORM;
            parameter.paramName = prefix;
            return (T) this;
        }

        /**
         * Resteasy @Form specific injection parameter
         *
         * @return
         */
        public T form() {
            parameter.paramType = Parameter.ParamType.FORM;
            parameter.paramName = "";
            return (T) this;
        }

        public T headerParam(String name) {
            parameter.paramType = Parameter.ParamType.HEADER_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        public T matrixParam(String name) {
            parameter.paramType = Parameter.ParamType.MATRIX_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        public T pathParam(String name) {
            parameter.paramType = Parameter.ParamType.PATH_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        public T queryParam(String name) {
            parameter.paramType = Parameter.ParamType.QUERY_PARAM;
            parameter.paramName = name;
            return (T) this;
        }

        public T fromAnnotations() {
            Annotation[] annotations = parameter.getAnnotations();
            AccessibleObject injectTarget = parameter.getAccessibleObject();
            Class<?> type = parameter.getResourceClass().getClazz();

            parameter.encoded = findAnnotation(annotations, Encoded.class) != null || injectTarget.isAnnotationPresent(Encoded.class)
                    || type.isAnnotationPresent(Encoded.class);
            DefaultValue defaultValue = findAnnotation(annotations, DefaultValue.class);
            if (defaultValue != null)
                parameter.defaultValue = defaultValue.value();

            QueryParam query;
            HeaderParam header;
            MatrixParam matrix;
            PathParam uriParam;
            CookieParam cookie;
            FormParam formParam;
            Form form;
            Suspend suspend;
            Suspended suspended;

            DocDescription docDescription = findAnnotation(annotations, DocDescription.class);
            parameter.description = (docDescription == null) ? null : docDescription.value();
            if ((query = findAnnotation(annotations, QueryParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.QUERY_PARAM;
                parameter.paramName = query.value();
            } else if ((header = findAnnotation(annotations, HeaderParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.HEADER_PARAM;
                parameter.paramName = header.value();
            } else if ((formParam = findAnnotation(annotations, FormParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.FORM_PARAM;
                parameter.paramName = formParam.value();
            } else if ((cookie = findAnnotation(annotations, CookieParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.COOKIE_PARAM;
                parameter.paramName = cookie.value();
            } else if ((uriParam = findAnnotation(annotations, PathParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.PATH_PARAM;
                parameter.paramName = uriParam.value();
            } else if ((form = findAnnotation(annotations, Form.class)) != null) {
                parameter.paramType = Parameter.ParamType.FORM;
                parameter.paramName = form.prefix();
            } else if (findAnnotation(annotations, BeanParam.class) != null) {
                parameter.paramType = Parameter.ParamType.BEAN_PARAM;
            } else if ((matrix = findAnnotation(annotations, MatrixParam.class)) != null) {
                parameter.paramType = Parameter.ParamType.MATRIX_PARAM;
                parameter.paramName = matrix.value();
            } else if ((suspend = findAnnotation(annotations, Suspend.class)) != null) {
                parameter.paramType = Parameter.ParamType.SUSPEND;
                parameter.suspendTimeout = suspend.value();
            } else if (findAnnotation(annotations, Context.class) != null) {
                parameter.paramType = Parameter.ParamType.CONTEXT;
            } else if ((suspended = findAnnotation(annotations, Suspended.class)) != null) {
                parameter.paramType = Parameter.ParamType.SUSPENDED;
            } else if (javax.ws.rs.container.AsyncResponse.class.isAssignableFrom(type)) {
                parameter.paramType = Parameter.ParamType.SUSPENDED;
            } else if (findAnnotation(annotations, Body.class) != null) {
                parameter.paramType = Parameter.ParamType.MESSAGE_BODY;
            } else {
                parameter.paramType = Parameter.ParamType.UNKNOWN;
            }
            return (T) this;
        }
    }

    public static class ConstructorParameterBuilder extends ParameterBuilder<ConstructorParameterBuilder> {
        final ResourceConstructorBuilder constructor;
        final ConstructorParameter param;

        public ConstructorParameterBuilder(ResourceConstructorBuilder builder, ConstructorParameter param) {
            super(param);
            this.constructor = builder;
            this.param = param;
        }

        public ConstructorParameterBuilder param(int i) {
            return constructor.param(i);
        }

        public ResourceClassBuilder buildConstructor() {
            return constructor.buildConstructor();
        }

    }

    public static class LocatorMethodParameterBuilder<T extends LocatorMethodParameterBuilder> extends ParameterBuilder<T> {
        final ResourceLocatorBuilder locator;
        final MethodParameter param;

        public LocatorMethodParameterBuilder(ResourceLocatorBuilder method, MethodParameter param) {
            super(param);
            this.locator = method;
            this.param = param;
        }

        public T param(int i) {
            return (T) locator.param(i);
        }

        public ResourceClassBuilder buildMethod() {
            return locator.buildMethod();
        }

    }

    public static class ResourceMethodParameterBuilder extends LocatorMethodParameterBuilder<ResourceMethodParameterBuilder> {
        final ResourceMethodBuilder method;

        public ResourceMethodParameterBuilder(ResourceMethodBuilder method, MethodParameter param) {
            super(method, param);
            this.method = method;
        }

        public ResourceMethodParameterBuilder suspended() {
            method.method.asynchronous = true;
            parameter.paramType = Parameter.ParamType.SUSPENDED;
            return this;
        }

        public ResourceMethodParameterBuilder suspend(long timeout) {
            method.method.asynchronous = true;
            parameter.paramType = Parameter.ParamType.SUSPEND;
            parameter.suspendTimeout = timeout;
            return this;
        }

        @Override
        public ResourceMethodParameterBuilder fromAnnotations() {
            super.fromAnnotations();
            if (param.paramType == Parameter.ParamType.SUSPEND || param.paramType == Parameter.ParamType.SUSPENDED) {
                method.method.asynchronous = true;
            } else if (param.paramType == Parameter.ParamType.UNKNOWN) {
                param.paramType = Parameter.ParamType.MESSAGE_BODY;
            }
            return this;
        }
    }

    public static class ResourceConstructorBuilder {

        ResourceConstructor constructor;
        ResourceClassBuilder resourceClassBuilder;

        public ResourceConstructorBuilder(ResourceClassBuilder resourceClassBuilder, Constructor constructor) {
            this.resourceClassBuilder = resourceClassBuilder;
            this.constructor = new ResourceConstructor(resourceClassBuilder.resourceClass, constructor);
        }

        public ConstructorParameterBuilder param(int i) {
            return new ConstructorParameterBuilder(this, constructor.getParams()[i]);
        }

        public ResourceClassBuilder buildConstructor() {
            resourceClassBuilder.resourceClass.constructor = constructor;
            return resourceClassBuilder;
        }
    }

    public static class ResourceLocatorBuilder<T extends ResourceLocatorBuilder> {

        ResourceLocator locator;
        ResourceClassBuilder resourceClassBuilder;

        ResourceLocatorBuilder() {
        }

        public ResourceLocatorBuilder(ResourceClassBuilder resourceClassBuilder, Method method, Method annotatedMethod) {
            this.resourceClassBuilder = resourceClassBuilder;
            this.locator = new ResourceLocator(resourceClassBuilder.resourceClass, method, annotatedMethod);
        }

        public T returnType(Class<?> type) {
            locator.returnType = type;
            return (T) this;
        }

        public T genericReturnType(Type type) {
            locator.genericReturnType = type;
            return (T) this;
        }

        public T returnType(GenericType type) {
            locator.returnType = type.getRawType();
            locator.genericReturnType = type.getType();
            return (T) this;
        }

        public LocatorMethodParameterBuilder param(int i) {
            return new LocatorMethodParameterBuilder(this, locator.getParams()[i]);
        }

        public ResourceClassBuilder buildMethod() {

            ResteasyUriBuilder builder = new ResteasyUriBuilder();
            if (locator.resourceClass.path != null)
                builder.path(locator.resourceClass.path);
            if (locator.path != null)
                builder.path(locator.path);
            String pathExpression = builder.getPath();
            if (pathExpression == null)
                pathExpression = "";
            locator.fullpath = pathExpression;
            if (locator.resourceClass.getClazz().isAnonymousClass()) {
                locator.getMethod().setAccessible(true);
            }
            resourceClassBuilder.resourceLocators.add(locator);
            return resourceClassBuilder;
        }

        public T path(String path) {
            locator.path = path;
            return (T) this;
        }

        public T pathExample(String pathExample) {
            locator.pathExample = pathExample;
            return (T) this;
        }

        public T description(String description) {
            locator.description = description;
            return (T) this;
        }

        public T response(int http, String[] headers, String[] cookies, Class<?> type, Type parameterized, DocReturn.Structure structure,
                String description, String typeDescription) {
            ResponseObjectBuilder responseBuilder = new ResponseObjectBuilder();
            ReturnOption option = responseBuilder.status(http).headers(Arrays.asList(headers)).cookies(Arrays.asList(cookies)).type(type)
                    .parameterized(parameterized).structure(structure).description(description).typeDescription(typeDescription).buildReturnOption();
            locator.returnOptions.add(option);
            return (T) this;
        }
    }

    public static class ResourceMethodBuilder extends ResourceLocatorBuilder<ResourceMethodBuilder> {
        ResourceMethod method;

        ResourceMethodBuilder(ResourceClassBuilder resourceClassBuilder, Method method, Method annotatedMethod) {
            this.method = new ResourceMethod(resourceClassBuilder.resourceClass, method, annotatedMethod);
            this.locator = this.method;
            this.resourceClassBuilder = resourceClassBuilder;
        }

        public ResourceMethodBuilder httpMethod(String httpMethod) {
            method.httpMethods.add(httpMethod.toUpperCase());
            return this;
        }

        public ResourceMethodBuilder get() {
            method.httpMethods.add(HttpMethod.GET);
            return this;
        }

        public ResourceMethodBuilder put() {
            method.httpMethods.add(HttpMethod.PUT);
            return this;
        }

        public ResourceMethodBuilder post() {
            method.httpMethods.add(HttpMethod.POST);
            return this;
        }

        public ResourceMethodBuilder delete() {
            method.httpMethods.add(HttpMethod.DELETE);
            return this;
        }

        public ResourceMethodBuilder options() {
            method.httpMethods.add(HttpMethod.OPTIONS);
            return this;
        }

        public ResourceMethodBuilder head() {
            method.httpMethods.add(HttpMethod.HEAD);
            return this;
        }

        // public ResourceMethodBuilder produces(MediaType... produces) {
        // method.produces = produces;
        // return this;
        // }

        public ResourceMethodBuilder produces(String... produces) {
            method.produces = produces;
            return this;
        }

        protected MediaType[] parseMediaTypes(String[] produces) {
            List<MediaType> mediaTypes = new ArrayList<MediaType>();
            for (String produce : produces) {
                String[] split = produce.split(",");
                for (String s : split) {
                    MediaType mediaType = (MediaType) new MediaTypeHeaderDelegate().fromString(s);
                    mediaTypes.add(mediaType);
                }
            }
            MediaType[] types = new MediaType[mediaTypes.size()];
            types = mediaTypes.toArray(types);
            return types;
        }

        // public ResourceMethodBuilder consumes(MediaType... consumes) {
        // method.consumes = consumes;
        // return this;
        // }

        public ResourceMethodBuilder consumes(String... consumes) {
            method.consumes = consumes;
            return this;
        }

        public ResourceMethodParameterBuilder param(int i) {
            return new ResourceMethodParameterBuilder(this, locator.getParams()[i]);
        }

        public ResourceClassBuilder buildMethod() {
            ResteasyUriBuilder builder = new ResteasyUriBuilder();
            if (method.resourceClass.path != null)
                builder.path(method.resourceClass.path);
            if (method.path != null)
                builder.path(method.path);
            String pathExpression = builder.getPath();
            if (pathExpression == null)
                pathExpression = "";
            method.fullpath = pathExpression;
            if (method.resourceClass.getClazz().isAnonymousClass()) {
                method.getMethod().setAccessible(true);
            }
            resourceClassBuilder.resourceMethods.add(method);
            return resourceClassBuilder;
        }
    }

    public static class FieldParameterBuilder extends ParameterBuilder<FieldParameterBuilder> {
        FieldParameter field;
        ResourceClassBuilder resourceClassBuilder;

        FieldParameterBuilder(ResourceClassBuilder resourceClassBuilder, FieldParameter parameter) {
            super(parameter);
            this.field = parameter;
            this.resourceClassBuilder = resourceClassBuilder;
        }

        public ResourceClassBuilder buildField() {
            field.field.setAccessible(true);
            resourceClassBuilder.fields.add(field);
            return resourceClassBuilder;
        }
    }

    public static class SetterParameterBuilder extends ParameterBuilder<SetterParameterBuilder> {
        SetterParameter setter;
        ResourceClassBuilder resourceClassBuilder;

        SetterParameterBuilder(ResourceClassBuilder resourceClassBuilder, SetterParameter parameter) {
            super(parameter);
            this.setter = parameter;
            this.resourceClassBuilder = resourceClassBuilder;
        }

        public ResourceClassBuilder buildSetter() {
            setter.setter.setAccessible(true);
            resourceClassBuilder.setters.add(setter);
            return resourceClassBuilder;
        }
    }

    public static class ResponseObjectBuilder {

        ReturnOption returnOption;

        public ResponseObjectBuilder() {
            returnOption = new ReturnOption();
        }

        public ResponseObjectBuilder status(int status) {
            returnOption.status = status;
            return this;
        }

        public ResponseObjectBuilder headers(List<String> headers) {
            returnOption.headers.addAll(headers);
            return this;
        }

        public ResponseObjectBuilder cookies(List<String> cookies) {
            returnOption.cookies.addAll(cookies);
            return this;
        }

        public ResponseObjectBuilder parameterized(Type parameterized) {
            returnOption.parameterized(parameterized);
            return this;
        }

        public ResponseObjectBuilder type(Class<?> clazz) {
            returnOption.setReturnClass(clazz);
            return this;
        }

        public ResponseObjectBuilder structure(DocReturn.Structure structure) {
            returnOption.setStructure(structure);
            return this;
        }

        public ResponseObjectBuilder description(String description) {
            returnOption.setDescription(description);
            return this;
        }

        public ResponseObjectBuilder typeDescription(String typeDescription) {
            returnOption.setTypeDescription(typeDescription);
            return this;
        }

        public ReturnOption buildReturnOption() {
            return returnOption;
        }
    }

    public static ResourceClassBuilder rootResource(Class<?> root) {
        return new ResourceClassBuilder(root, "/");
    }

    public static ResourceClassBuilder rootResource(Class<?> root, String path) {
        return new ResourceClassBuilder(root, path);
    }

    public static ResourceClassBuilder locator(Class<?> root) {
        return new ResourceClassBuilder(root, null);
    }

    /**
     * Picks a constructor from an annotated resource class based on spec rules
     *
     * @param annotatedResourceClass
     * @return
     */
    public static ResourceConstructor constructor(Class<?> annotatedResourceClass) {
        Constructor constructor = PickConstructor.pickPerRequestConstructor(annotatedResourceClass);
        if (constructor == null) {
            throw new RuntimeException("Could not find constructor for class: " + annotatedResourceClass.getName());
        }
        ResourceConstructorBuilder builder = rootResource(annotatedResourceClass).constructor(constructor);
        if (constructor.getParameterTypes() != null) {
            for (int i = 0; i < constructor.getParameterTypes().length; i++)
                builder.param(i).fromAnnotations();
        }
        return builder.buildConstructor().buildClass().getConstructor();
    }

    /**
     * Build metadata from annotations on classes and methods
     *
     * @return
     */
    public static ResourceClass rootResourceFromAnnotations(Class<?> clazz) {
        ResourceClass resourceClass = fromAnnotations(false, clazz);
        return resourceClass;
    }

    public static ResourceClass locatorFromAnnotations(Class<?> clazz) {
        Logger.debug("Start meta resource class {0}", clazz.getCanonicalName());
        ResourceClass resourceClass = fromAnnotations(true, clazz);
        Logger.debug("End meta resource class {0}", clazz.getCanonicalName());
        return resourceClass;
    }

    private static final String WELD_PROXY_INTERFACE_NAME = "org.jboss.weld.bean.proxy.ProxyObject";

    /**
     * Whether the given class is a proxy created by Weld or not. This is the
     * case if the given class implements the interface
     * {@code org.jboss.weld.bean.proxy.ProxyObject}.
     *
     * @param clazz
     *            the class of interest
     * @return {@code true} if the given class is a Weld proxy, {@code false}
     *         otherwise
     */
    private static boolean isWeldProxy(Class<?> clazz) {
        for (Class<?> implementedInterface : clazz.getInterfaces()) {
            if (implementedInterface.getName().equals(WELD_PROXY_INTERFACE_NAME)) {
                return true;
            }
        }

        return false;
    }

    private static ResourceClass fromAnnotations(boolean isLocator, Class<?> clazz) {
        // stupid hack for Weld as it loses parameterized type information, but
        // retains annotations.
        if (!clazz.isInterface() && clazz.getSuperclass() != null && !clazz.getSuperclass().equals(Object.class) && isWeldProxy(clazz)) {
            clazz = clazz.getSuperclass();
        }
        ResourceClassBuilder builder = null;
        DocDescription docDescription = clazz.getAnnotation(DocDescription.class);
        String descValue = (docDescription == null) ? null : docDescription.value();
        DocPathExample docPathExample = clazz.getAnnotation(DocPathExample.class);
        String pExamValue = (docPathExample == null) ? "" : docPathExample.value();
        Produces produces = clazz.getAnnotation(Produces.class);
        String[] producesValue = (produces == null) ? null : produces.value();
        Consumes consumes = clazz.getAnnotation(Consumes.class);
        String[] consumesValue = (consumes == null) ? null : consumes.value();
        if (isLocator) {
            builder = locator(clazz);
        } else {
            Path path = clazz.getAnnotation(Path.class);
            if (path == null) {
                builder = rootResource(clazz, "");
            } else {
                builder = rootResource(clazz, path.value());
            }
        }
        builder.resourceClass.setDescription(descValue);
        builder.resourceClass.setPathExample(pExamValue);
        builder.resourceClass.setProduces(producesValue);
        builder.resourceClass.setConsumes(consumesValue);
        ResourceConstructor constructor = constructor(clazz);
        builder.resourceClass.constructor = constructor;
        for (Method method : clazz.getMethods()) {
            if (!method.isSynthetic() && !method.getDeclaringClass().equals(Object.class))
                processMethod(isLocator, builder, clazz, method);

        }
        if (!clazz.isInterface()) {
            processFields(builder, clazz);
        }
        processSetters(builder, clazz);
        return builder.buildClass();
    }

    private static Method findAnnotatedInterfaceMethod(Class<?> root, Class<?> iface, Method implementation) {
        for (Method method : iface.getMethods()) {
            if (method.isSynthetic())
                continue;

            if (!method.getName().equals(implementation.getName()))
                continue;
            if (method.getParameterTypes().length != implementation.getParameterTypes().length)
                continue;

            Method actual = Types.getImplementingMethod(root, method);
            if (!actual.equals(implementation))
                continue;

            if (method.isAnnotationPresent(Path.class) || IsHttpMethod.getHttpMethods(method) != null)
                return method;

        }
        for (Class<?> extended : iface.getInterfaces()) {
            Method m = findAnnotatedInterfaceMethod(root, extended, implementation);
            if (m != null)
                return m;
        }
        return null;
    }

    private static Method findAnnotatedMethod(Class<?> root, Method implementation) {
        // check the method itself
        if (implementation.isAnnotationPresent(Path.class) || IsHttpMethod.getHttpMethods(implementation) != null)
            return implementation;

        if (implementation.isAnnotationPresent(Produces.class) || implementation.isAnnotationPresent(Consumes.class)) {
            // completely abort this method
            return null;
        }

        // Per
        // http://download.oracle.com/auth/otn-pub/jcp/jaxrs-1.0-fr-oth-JSpec/jaxrs-1.0-final-spec.pdf
        // Section 3.2 Annotation Inheritance

        // Check possible superclass declarations
        for (Class<?> clazz = implementation.getDeclaringClass().getSuperclass(); clazz != null; clazz = clazz.getSuperclass()) {
            try {
                Method method = clazz.getDeclaredMethod(implementation.getName(), implementation.getParameterTypes());
                if (method.isAnnotationPresent(Path.class) || IsHttpMethod.getHttpMethods(method) != null)
                    return method;
                if (method.isAnnotationPresent(Produces.class) || method.isAnnotationPresent(Consumes.class)) {
                    // completely abort this method
                    return null;
                }
            } catch (NoSuchMethodException e) {
                // ignore
            }
        }

        // Not found yet, so next check ALL interfaces from the root,
        // but ensure no redefinition by peer interfaces (ambiguous) to preserve
        // logic found in
        // original implementation
        for (Class<?> clazz = root; clazz != null; clazz = clazz.getSuperclass()) {
            Method method = null;
            for (Class<?> iface : clazz.getInterfaces()) {
                Method m = findAnnotatedInterfaceMethod(root, iface, implementation);
                if (m != null) {
                    if (method != null && !m.equals(method))
                        throw new RuntimeException("Ambiguous inherited JAX-RS annotations applied to method: " + implementation);
                    method = m;
                }
            }
            if (method != null)
                return method;
        }
        return null;
    }

    protected static void processFields(ResourceClassBuilder resourceClassBuilder, Class<?> root) {
        do {
            processDeclaredFields(resourceClassBuilder, root);
            root = root.getSuperclass();
            // } while (root.getSuperclass() != null &&
            // !root.getSuperclass().equals(Object.class));
        } while (root != null && !root.equals(Object.class));
    }

    protected static void processSetters(ResourceClassBuilder resourceClassBuilder, Class<?> root) {
        HashSet<Long> hashes = new HashSet<Long>();
        do {
            processDeclaredSetters(resourceClassBuilder, root, hashes);
            root = root.getSuperclass();
        } while (root != null && !root.equals(Object.class));
    }

    protected static void processDeclaredFields(ResourceClassBuilder resourceClassBuilder, Class<?> root) {
        for (Field field : root.getDeclaredFields()) {
            FieldParameterBuilder builder = resourceClassBuilder.field(field).fromAnnotations();
            if (builder.field.paramType == Parameter.ParamType.MESSAGE_BODY && !field.isAnnotationPresent(Body.class))
                continue;
            if (builder.field.paramType == Parameter.ParamType.UNKNOWN)
                continue;
            builder.buildField();
        }
    }

    protected static void processDeclaredSetters(ResourceClassBuilder resourceClassBuilder, Class<?> root, Set<Long> visitedHashes) {
        for (Method method : root.getDeclaredMethods()) {
            if (!method.getName().startsWith("set"))
                continue;
            if (method.getParameterTypes().length != 1)
                continue;
            long hash = 0;
            try {
                hash = MethodHashing.methodHash(method);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
            if (!Modifier.isPrivate(method.getModifiers()) && visitedHashes.contains(hash))
                continue;
            visitedHashes.add(hash);
            SetterParameterBuilder builder = resourceClassBuilder.setter(method).fromAnnotations();
            if (builder.setter.paramType == Parameter.ParamType.MESSAGE_BODY && !method.isAnnotationPresent(Body.class))
                continue;
            if (builder.setter.paramType == Parameter.ParamType.UNKNOWN)
                continue;
            builder.buildSetter();
        }
    }

    protected static void processMethod(boolean isLocator, ResourceClassBuilder resourceClassBuilder, Class<?> root, Method implementation) {
        Logger.debug("{0} method preprocessing started", implementation.toGenericString());
        Method method = findAnnotatedMethod(root, implementation);
        if (method != null) {
            Set<String> httpMethods = IsHttpMethod.getHttpMethods(method);
            ResponseObjectBuilder responseObjectBuilder;
            ResourceLocatorBuilder resourceLocatorBuilder;

            if (httpMethods == null) {
                resourceLocatorBuilder = resourceClassBuilder.locator(implementation, method);
            } else {
                ResourceMethodBuilder resourceMethodBuilder = resourceClassBuilder.method(implementation, method);
                resourceLocatorBuilder = resourceMethodBuilder;
                DocDescription docDescription = method.getAnnotation(DocDescription.class);
                String descValue = (docDescription == null) ? null : docDescription.value();
                resourceMethodBuilder.description(descValue);
                DocPathExample docPathExample = method.getAnnotation(DocPathExample.class);
                String pExampValue = (docPathExample == null) ? null : docPathExample.value();
                resourceMethodBuilder.pathExample(pExampValue);
                for (String httpMethod : httpMethods) {
                    if (httpMethod.equalsIgnoreCase(HttpMethod.GET))
                        resourceMethodBuilder.get();
                    else if (httpMethod.equalsIgnoreCase(HttpMethod.PUT))
                        resourceMethodBuilder.put();
                    else if (httpMethod.equalsIgnoreCase(HttpMethod.POST))
                        resourceMethodBuilder.post();
                    else if (httpMethod.equalsIgnoreCase(HttpMethod.DELETE))
                        resourceMethodBuilder.delete();
                    else if (httpMethod.equalsIgnoreCase(HttpMethod.OPTIONS))
                        resourceMethodBuilder.options();
                    else if (httpMethod.equalsIgnoreCase(HttpMethod.HEAD))
                        resourceMethodBuilder.head();
                    else
                        resourceMethodBuilder.httpMethod(httpMethod);
                }
                Produces produces = method.getAnnotation(Produces.class);
                if (produces == null)
                    produces = resourceClassBuilder.resourceClass.getClazz().getAnnotation(Produces.class);
                if (produces == null)
                    produces = method.getDeclaringClass().getAnnotation(Produces.class);
                if (produces != null)
                    resourceMethodBuilder.produces(produces.value());

                Consumes consumes = method.getAnnotation(Consumes.class);
                if (consumes == null)
                    consumes = resourceClassBuilder.resourceClass.getClazz().getAnnotation(Consumes.class);
                if (consumes == null)
                    consumes = method.getDeclaringClass().getAnnotation(Consumes.class);
                if (consumes != null)
                    resourceMethodBuilder.consumes(consumes.value());
                if (method.isAnnotationPresent(DocReturn.class)) {
                    createResponse(method.getAnnotation(DocReturn.class), resourceMethodBuilder);
                } else if (method.isAnnotationPresent(DocReturns.class)) {
                    for (DocReturn ret : method.getAnnotation(DocReturns.class).value()) {
                        createResponse(ret, resourceMethodBuilder);
                    }
                } else {
                    responseFromReturn(method.getReturnType(), method.getGenericReturnType(), resourceMethodBuilder);
                }
            }
            Path methodPath = method.getAnnotation(Path.class);
            if (methodPath != null) {
                resourceLocatorBuilder.path(methodPath.value());
            } else {
                resourceLocatorBuilder.path("");
            }
            for (int i = 0; i < resourceLocatorBuilder.locator.params.length; i++) {
                resourceLocatorBuilder.param(i).fromAnnotations();
            }
            resourceLocatorBuilder.buildMethod();
        }
        Logger.debug("{0} method preprocessing finished", implementation.toGenericString());
    }

    static void responseFromReturn(Class<?> returnClass, Type parameterized, ResourceMethodBuilder resourceMethodBuilder) {
        if (returnClass.getCanonicalName().equals("void")) {
            emptyResponse(resourceMethodBuilder);
        } else if (returnClass.equals(Response.class)) {
            resourceMethodBuilder.response(200, new String[] {}, new String[] {}, Object.class, Object.class, null, null, null);
        } else if (returnClass.equals(GenericEntity.class)) {
            if (parameterized.equals(GenericEntity.class)) {
                emptyResponse(resourceMethodBuilder);
            } else if (parameterized instanceof ParameterizedType) {
                ParameterizedType paramType = (ParameterizedType) parameterized;
                if (paramType.getActualTypeArguments()[0] instanceof Class) {
                    resourceMethodBuilder.response(200, new String[] {}, new String[] {}, (Class) paramType.getActualTypeArguments()[0],
                            paramType.getActualTypeArguments()[0], null, null, null);
                } else if (paramType.getActualTypeArguments()[0] instanceof ParameterizedType) {
                    resourceMethodBuilder.response(200, new String[] {}, new String[] {},
                            (Class) ((ParameterizedType) paramType.getActualTypeArguments()[0]).getRawType(), paramType.getActualTypeArguments()[0],
                            null, null, null);
                }
            }
        } else {
            resourceMethodBuilder.response(200, new String[] {}, new String[] {}, returnClass, parameterized, null, null, null);
        }
    }

    static void emptyResponse(ResourceMethodBuilder resourceMethodBuilder) {
        resourceMethodBuilder.response(204, new String[] {}, new String[] {}, null, null, null, null, null);
    }

    static void createResponse(DocReturn ret, ResourceMethodBuilder resourceMethodBuilder) {
        Type parameterizedType = null;
        DocReturn.Structure structure = ret.structure();
        if (structure == DocReturn.Structure.OBJECT) {
            parameterizedType = ret.type();
            resourceMethodBuilder.response(ret.http(), ret.headers(), ret.cookies(), ret.type(), ret.type(), ret.structure(), ret.description(),
                    ret.typeDescription());
        } else if (structure == DocReturn.Structure.ARRAY) {
            parameterizedType = ParameterizedTypeImpl.make(List.class, new Type[] { ret.type() }, null);
            resourceMethodBuilder.response(ret.http(), ret.headers(), ret.cookies(), List.class, parameterizedType, ret.structure(),
                    ret.description(), ret.typeDescription());
        } else if (structure == DocReturn.Structure.MAP) {
            parameterizedType = ParameterizedTypeImpl.make(Map.class, new Type[] { String.class, ret.type() }, null);
            resourceMethodBuilder.response(ret.http(), ret.headers(), ret.cookies(), Map.class, parameterizedType, ret.structure(),
                    ret.description(), ret.typeDescription());
        }
    }

}


package io.advantageous.qbit.example.jms;

import io.advantageous.qbit.admin.ManagedServiceBuilder;
import io.advantageous.qbit.annotation.RequestMapping;
import io.advantageous.qbit.annotation.http.GET;
import io.advantageous.qbit.annotation.http.PUT;
import io.advantageous.qbit.jms.JmsException;
import io.advantageous.qbit.queue.Queue;
import io.advantageous.qbit.queue.ReceiveQueue;
import io.advantageous.qbit.reactive.Reactor;
import io.advantageous.qbit.reactive.ReactorBuilder;
import io.advantageous.qbit.service.BaseService;
import io.advantageous.qbit.service.stats.StatsCollector;
import io.advantageous.qbit.time.Duration;
import io.advantageous.qbit.util.Timer;

import java.util.Optional;

@RequestMapping("/jms")
public class JmsConsumerService extends BaseService {

    private Optional<Queue<Todo>> queue = Optional.empty();

    private Optional<ReceiveQueue<Todo>> consumeQueue = Optional.empty();

    public JmsConsumerService(final Reactor reactor, final Timer timer, final StatsCollector statsCollector) {
        super(reactor, timer, statsCollector);
        reactor.addRepeatingTask(Duration.ONE_SECOND, () -> {
        });
    }

    /**
     * curl -X PUT http://localhost:9090/jms/todo/
     * @return true or throws an exception
     */
    @PUT(value = "/todo/", code = 202)
    public Todo get() {
        if (!consumeQueue.isPresent()) {
            initConsumeQueue();
        }

        Todo todo;

        try {
            todo = consumeQueue.get().poll();
        } catch (JmsException ex) {
            queue = Optional.empty();
            consumeQueue = Optional.empty();
            initConsumeQueue();
            todo = consumeQueue.get().poll();
        }
        return todo;
    }

    private void initConsumeQueue() {

        if (!queue.isPresent()) {
            queue = Optional.of(JmsUtil.createQueue());
        }

        queue.ifPresent(todoQueue -> {

            consumeQueue = Optional.of(todoQueue.receiveQueue());
        });

    }

    @GET("/ping")
    public boolean ping() {
        return true;
    }

    public static void main(final String... args) {

        final ManagedServiceBuilder managedServiceBuilder = ManagedServiceBuilder.managedServiceBuilder().setRootURI("/");

        managedServiceBuilder.addEndpointService(new JmsConsumerService(
                ReactorBuilder.reactorBuilder().build(),
                Timer.timer(), managedServiceBuilder.getStatServiceBuilder().buildStatsCollector()));

        managedServiceBuilder.setPort(9090).getEndpointServerBuilder().build().startServer();
    }

}


/*
 * $#
 * FOS Weka
 *
 * Copyright (C) 2013 Feedzai SA
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #$
 */
package weka.classifiers;

/**
 * Utility classes to deal with the Weka API.
 *
 * @author Ricardo Ferreira (ricardo.ferreira@feedzai.com)
 * @since 1.0.4
 */
public final class RandomForestUtils {

    /**
     * Retrieves the array of classifiers in a bagging.
     *
     * @param bagging The bag for which to retrieve the classifiers.
     * @return The array of classifiers in a bagging.
     */
    public static Classifier[] getBaggingClassifiers(IteratedSingleClassifierEnhancer bagging) {
        return bagging.m_Classifiers;
    }

    /**
     * Sets the classifiers in a bagging.
     *
     * @param bagging The bag for which to set the classifiers.
     * @throws Exception If it fails to set the classifiers.
     */
    public static void setupBaggingClassifiers(IteratedSingleClassifierEnhancer bagging) throws Exception {
        bagging.m_Classifiers = Classifier.makeCopies(bagging.m_Classifier, bagging.m_NumIterations);
    }
}


package br.com.starcode.parccser;

import java.util.ArrayList;
import java.util.List;

import br.com.starcode.parccser.model.AttributeOperator;
import br.com.starcode.parccser.model.AttributeSelector;
import br.com.starcode.parccser.model.ClassSelector;
import br.com.starcode.parccser.model.Combinator;
import br.com.starcode.parccser.model.Context;
import br.com.starcode.parccser.model.HashSelector;
import br.com.starcode.parccser.model.NegationSelector;
import br.com.starcode.parccser.model.PseudoSelector;
import br.com.starcode.parccser.model.PseudoType;
import br.com.starcode.parccser.model.Selector;
import br.com.starcode.parccser.model.SimpleSelector;
import br.com.starcode.parccser.model.SimpleSelectorSequence;
import br.com.starcode.parccser.model.StringValue;
import br.com.starcode.parccser.model.TypeSelector;
import br.com.starcode.parccser.model.expression.Item;
import br.com.starcode.parccser.model.expression.PseudoExpression;
import br.com.starcode.parccser.model.expression.PseudoExpressionEvaluator;
import br.com.starcode.parccser.model.expression.Type;

/**
 * That's the main class for parsing CSS selectors.
 *
 * Selector list:
 * http://www.w3.org/TR/css3-selectors/#selectors
 */
public class Parser {

    protected String content;
    protected int pos;
    protected int len;
    protected Character current;
    protected ParserListener parserListener;

    public static List<Selector> parse(String selector, ParserListener parserListener) throws ParserException {
    	return new Parser(selector, parserListener).interpret();
    }

    public static List<Selector> parse(String selector) throws ParserException {
    	return new Parser(selector, new EmptyParserListener()).interpret();
    }

    protected Parser(String selector, ParserListener parserListener) {
        if (selector == null || selector.trim().isEmpty()) {
            throw new IllegalArgumentException("Selector cannot be null or empty!");
        }
        if (parserListener == null) {
            throw new IllegalArgumentException("ParserListener cannot be null!");
        }
        this.parserListener = parserListener;
        this.content = selector;
        this.pos = -1;
        this.len = selector.length();
    }

    /**
     * Starts parsing the selector
     * @throws ParserException
     */
    public List<Selector> interpret() throws ParserException {
        next();
        return groups();
    }

    protected Character next() {
        if (pos < len) {
            pos++;
        }
        if (pos < len) {
            return current = content.charAt(pos);
        }
        return current = null;
    }

    protected boolean end() {
        return pos >= len;
    }

    /**
     * selectors_group
     *  : selector [ COMMA S* selector ]*
     */
    protected List<Selector> groups() throws ParserException {

        List<Selector> groups = new ArrayList<Selector>();
        int groupNumber = 0;
        while (!end()) {
            ignoreWhitespaces();
            parserListener.beginGroup(groupNumber, pos);
            Selector selector = selector();
            groups.add(selector);
            parserListener.endGroup(selector);
            groupNumber++;
            ignoreWhitespaces();
            if (!end() && current != ',') {
                throw new ParserException("There is something left in the selector at position " + pos);
            }
            next();
        }
        return groups;

    }

    /**
     * selector
     *  : simple_selector_sequence [ combinator simple_selector_sequence ]*
     *  ;
     *
     *  combinator
     *  : PLUS S* | GREATER S* | TILDE S* | S+
     */
    protected Selector selector() throws ParserException {
        int initialPosition = pos;
        StringBuilder sb = new StringBuilder();
        List<SimpleSelectorSequence> simpleSelectors = new ArrayList<SimpleSelectorSequence>();
        List<Combinator> combinators = new ArrayList<Combinator>();
        int lastChar = pos;
        while (!end()) {
        	//finds combinator, but not in the first iteration
        	Combinator combinator = null;
        	if (!simpleSelectors.isEmpty()) {
        		//stores if it has spaces until the next token
                boolean hasWhitespace = false;
                if (!end() && Character.isWhitespace(current)) {
                    hasWhitespace = true;
                    ignoreWhitespaces();
                }
                if (!end()) {
                    //implements "combinator" rule
                    if (current == '+') combinator = Combinator.ADJASCENT_SIBLING;
                    else if (current == '>') combinator = Combinator.CHILD;
                    else if (current == '~') combinator = Combinator.GENERAL_SIBLING;
                    //if hasn't any but spaces
                    else if (hasWhitespace) combinator = Combinator.DESCENDANT;
                    //is it the end?
                    if (combinator == null || current == ',') {
                        break;
                    }
                    //don't advance because spaces were just advanced
                    if (combinator != Combinator.DESCENDANT) {
                    	sb.append(current);
                        next();
                    } else {
                    	sb.append(' ');
                    }
                    ignoreWhitespaces();
                    if (end()) {
                        throw new ParserException("Unexpected end of selector at position " + pos);
                    }
                } else {
                    break;
                }
                combinators.add(combinator);
        	}
        	//get next sequence
            SimpleSelectorSequence simpleSelectorSequence = simpleSelectorSequence();
            sb.append(simpleSelectorSequence);
            //sends combinator here (the first case it's null)
            simpleSelectors.add(simpleSelectorSequence);
            parserListener.selectorSequence(simpleSelectorSequence, combinator);
            lastChar = pos;
        }
        return new Selector(simpleSelectors, combinators, new Context(content, sb.toString(), initialPosition, lastChar));
    }

    /**
     * simple_selector_sequence
     *  : [ type_selector | universal ]
     *    [ HASH | class | attrib | pseudo | negation ]*
     *  | [ HASH | class | attrib | pseudo | negation ]+
     *
     *  type_selector
     *  : [ namespace_prefix ]? element_name
     *
     * namespace_prefix ** not implemented
     *  : [ IDENT | '*' ]? '|'
     *
     * element_name
     *  : IDENT
     *
     * universal
     *  : [ namespace_prefix ]? '*'
     *
     * class
     *  : '.' IDENT
     */
    protected SimpleSelectorSequence simpleSelectorSequence() throws ParserException {

        List<SimpleSelector> simpleSelectorList = new ArrayList<SimpleSelector>();
        StringBuilder sb = new StringBuilder();
        boolean hasSimpleSelector = false;
        int initialPos = pos;
        if (current == '*') {
            //universal selector
            sb.append(current);
            hasSimpleSelector = true;
            TypeSelector ts = new TypeSelector("*", new Context(content, "*", initialPos, pos + 1));
            parserListener.typeSelector(ts);
            simpleSelectorList.add(ts);
            next();
        } else if (Character.isLetter(current)) {
            //type selector
            String type = identifier();
            sb.append(type);
            hasSimpleSelector = true;
            TypeSelector ts = new TypeSelector(type, new Context(content, type, initialPos, pos));
            parserListener.typeSelector(ts);
            simpleSelectorList.add(ts);
        }

        int selectorCount = 0;
        boolean hasPseudoElement = false;
        while (!end()) {
        	int initialLoopPos = pos;
            if (current == '.') {
                //class selector
                sb.append(current);
                next();
                String className = !end() ? identifier() : null;
                if (className == null || className.isEmpty()) {
                    throw new ParserException("Expected className at position " + pos);
                }
                sb.append(className);
                ClassSelector cs = new ClassSelector(className, new Context(content, "." + className, initialLoopPos, pos));
                simpleSelectorList.add(cs);
                parserListener.classSelector(cs);
            } else if (current == '#') {
                //hash selector
                sb.append(current);
                next();
                String id = !end() ? identifier() : null;
                if (id == null || id.isEmpty()) {
                    throw new ParserException("Expected id at position " + pos);
                }
                HashSelector hs = new HashSelector(id, new Context(content, "#" + id, initialLoopPos, pos));
                simpleSelectorList.add(hs);
                parserListener.idSelector(hs);
                sb.append(id);
            } else if (current == '[') {
                //attribute selectors
                sb.append(current);
                next();
                AttributeSelector as = attribute();
                simpleSelectorList.add(as);
                parserListener.attributeSelector(as);
                sb.append(as);
                sb.append(']');
            } else if (current == ':') {
                //pseudo-element, pseudo-class or negation
                sb.append(current);
                next();
                if (end()) {
                    throw new ParserException("Expected pseudo-element or pseudo-class at " + pos);
                }
                boolean doubleColon = false;
                if (current == ':') {
                    doubleColon = true;
                    sb.append(current);
                    next();
                }
                String ident = !end() ? identifier() : null;
                if (ident == null || ident.isEmpty()) {
                    throw new ParserException("Expected identifier at position " + pos);
                }
                boolean special = isPseudoSpecialCase(ident);
                if (special || doubleColon) {
                    //pseudo-element (double colon or special cases)
                    //allow just one
                    if (hasPseudoElement) {
                        throw new ParserException("Only one pseudo-element is allowed for each simple selector and a second one was found at position " + pos);
                    }
                    PseudoSelector ps = pseudo(ident, PseudoType.PSEUDO_ELEMENT, doubleColon);
                    simpleSelectorList.add(ps);
                    parserListener.pseudoSelector(ps);
                    sb.append(ps);
                    hasPseudoElement = true;
                } else if ("not".equalsIgnoreCase(ident)) {
                    //negation
                    NegationSelector n = negation(selectorCount);
                    simpleSelectorList.add(n);
                    sb.append(ident);
                    sb.append('(');
                    sb.append(n);
                    sb.append(')');
                } else {
                    //pseudo-class
                    PseudoSelector ps = pseudo(ident, PseudoType.PSEUDO_CLASS, false);
                    simpleSelectorList.add(ps);
                    parserListener.pseudoSelector(ps);
                    sb.append(ps);
                }
            } else {
                break;
            }
            selectorCount++;
        }
        if (!hasSimpleSelector && selectorCount == 0) {
            throw new ParserException("No real selector found at position " + pos);
        }

        return new SimpleSelectorSequence(simpleSelectorList, new Context(content, sb.toString(), initialPos, pos));

    }

    /**
     * attrib
     *  : '[' S* [ namespace_prefix ]? IDENT S*
     *        [ [ PREFIXMATCH |
     *            SUFFIXMATCH |
     *            SUBSTRINGMATCH |
     *            '=' |
     *            INCLUDES |
     *            DASHMATCH ] S* [ IDENT | STRING ] S*
     *        ]? ']'
     */
    protected AttributeSelector attribute() throws ParserException {

    	int initialPos = pos;
        StringBuilder sb = new StringBuilder();
        ignoreWhitespaces();
        String name = !end() ? identifier() : null;
        if (name == null || name.isEmpty()) {
            throw new ParserException("Expected attribute name at position " + pos);
        }
        sb.append(name);
        ignoreWhitespaces();
        if (end()) {
            throw new ParserException("Unexpected end of selector selector at position " + pos);
        }
        AttributeOperator operator = null;
        if (current == '=') {
            operator = AttributeOperator.EQUALS;
            next();
        } else if (current != ']') {
            if (current == '~') operator = AttributeOperator.INCLUDES;
            else if (current == '|') operator = AttributeOperator.DASH_MATCH;
            else if (current == '^') operator = AttributeOperator.PREFIX_MATCH;
            else if (current == '$') operator = AttributeOperator.SUFFIX_MATCH;
            else if (current == '*') operator = AttributeOperator.SUBSTRING_MATCH;
            else throw new ParserException("Invalid operator ('" + current + "') at position " + pos);
            next();
            if (end() || current != '=') {
                throw new ParserException("Expected '=' sign at position " + pos);
            }
            next();
        }

        ignoreWhitespaces();
        if (end()) {
            throw new ParserException("Unexpected end of attribute selector at position " + pos);
        }

        //value
        StringValue value = null;
        if (operator != null) {
            sb.append(operator.getSign());
            if (current == '\'' || current == '"') {
                char quote = current;
                value = string();
                sb.append(quote);
                sb.append(value.getContext().toString());
                sb.append(quote);
            } else {
            	int identPos = pos;
                String ident = identifier();
                value = new StringValue(new Context(content, ident, identPos, pos), ident);
                sb.append(value);
            }
        }

        ignoreWhitespaces();

        //end of attribute
        if (end() || current != ']') {
            throw new ParserException("Token ']' expected at position " + pos);
        }
        int endPos = pos;
        next();

        return new AttributeSelector(name, operator, value, new Context(content, sb.toString(), initialPos, endPos));

    }

    /**
     * string    {string1}|{string2}
     * string1   \"([^\n\r\f\\"]|\\{nl}|{nonascii}|{escape})*\"
     * string2   \'([^\n\r\f\\']|\\{nl}|{nonascii}|{escape})*\'
     * nonascii  [^\0-\177]
     * escape    {unicode}|\\[^\n\r\f0-9a-f]
     * unicode   \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
     * nl        \n|\r\n|\r|\f
     */
    protected StringValue string() throws ParserException {
        Character openQuote = current;
        int initialPosition = pos;
        boolean escape = false;
        next();
        StringBuilder value = new StringBuilder();
        StringBuilder string = new StringBuilder();
        while (!end()) {
            if (escape) {
                //TODO implement UNICODE
                if (current == openQuote || current == '\\') {
                    value.append(current);
                    string.append(current);
                    escape = false;
                } else {
                    throw new ParserException("Invalid escape character at position " + pos);
                }
            } else {
            	if (current == openQuote) break;
                string.append(current);
                if (current == '\\') {
                    escape = true;
                } else {
                    value.append(current);
                }
            }
            next();
        }

        if (current != openQuote) {
            throw new ParserException("String not closed!");
        }
        next();
        return new StringValue(new Context(content, string.toString(), initialPosition, pos), value.toString());

    }

    /**
     * num       [0-9]+|[0-9]*\.[0-9]+
     */
    protected String number() throws ParserException {
        StringBuilder sb = new StringBuilder();
        sb.append(current);
        next();
        boolean hasDot = false;
        while (!end() && (Character.isDigit(current) || current == '.')) {
            if (current == '.') {
                if (hasDot) {
                    throw new ParserException("Unexpected '.' at position " + pos);
                }
                hasDot = true;
            }
            sb.append(current);
            next();
        }
        return sb.toString();
    }

    /**
     * negation
     *  : NOT S* negation_arg S* ')'
     *
     * negation_arg
     *  : type_selector | universal | HASH | class | attrib | pseudo
     *
     *  TODO include "not" or not? It would be necessary to use parameters
     */
    protected NegationSelector negation(int number) throws ParserException {

        StringBuilder sb = new StringBuilder();
        ignoreWhitespaces();
        if (end() || current != '(') {
            throw new ParserException("Expected '(' at position " + pos);
        }
        //sb.append(current);
        int parenthesisPos = pos;
        next();
        ignoreWhitespaces();
        if (end()) {
            throw new ParserException("Selector expected at position " + pos);
        }
        int initialPos = pos;
        SimpleSelector simpleSelector = null;
        if (current == '*') {
            //universal selector
            sb.append(current);
            TypeSelector ts = new TypeSelector("*", new Context(content, "*", initialPos, pos + 1));
            parserListener.negationTypeSelector(ts);
            simpleSelector = ts;
            next();
        } else if (Character.isLetter(current)) {
            //type selector
            String type = identifier();
            sb.append(type);
            TypeSelector ts = new TypeSelector(type, new Context(content, type, initialPos, pos));
            parserListener.negationTypeSelector(ts);
            simpleSelector = ts;
        } else if (current == '.') {
            //class selector
            sb.append(current);
            next();
            String className = !end() ? identifier() : null;
            if (className == null || className.isEmpty()) {
                throw new ParserException("Expected className at position " + pos);
            }
            ClassSelector cs = new ClassSelector(className, new Context(content, "." + className, initialPos, pos));
            simpleSelector = cs;
            parserListener.negationClassSelector(cs);
            sb.append(className);
        } else if (current == '#') {
            //hash selector
            sb.append(current);
            next();
            String id = !end() ? identifier() : null;
            if (id == null || id.isEmpty()) {
                throw new ParserException("Expected id at position " + pos);
            }
            HashSelector hs = new HashSelector(id, new Context(content, "#" + id, initialPos, pos));
            simpleSelector = hs;
            parserListener.negationIdSelector(hs);
            sb.append(id);
        } else if (current == '[') {
            //attribute selectors
            sb.append(current);
            next();
            AttributeSelector as = attribute();
            simpleSelector = as;
            parserListener.negationAttributeSelector(as);
            sb.append(as.getContext());
            sb.append(']');
        } else if (current == ':') {
            //pseudo-class only
            sb.append(current);
            next();
            if (end()) {
                throw new ParserException("Expected pseudo-element or pseudo-class at " + pos);
            }
            boolean doubleColon = false;
            if (current == ':') {
                doubleColon = true;
                sb.append(current);
                next();
            }
            String ident = !end() ? identifier() : null;
            if (ident == null || ident.isEmpty()) {
                throw new ParserException("Expected identifier at position " + pos);
            }
            if ("not".equalsIgnoreCase(ident) || isPseudoSpecialCase(ident) || doubleColon) {
                throw new ParserException("Expected pseudo-class (starting with ':', except ':not', ':first-line', ':first-letter', ':before' and ':after') at position " + pos);
            }
            PseudoSelector ps = pseudo(ident, PseudoType.PSEUDO_CLASS, doubleColon);
            simpleSelector = ps;
            parserListener.negationPseudoSelector(ps);
            sb.append(ps.getContext());
        } else {
            throw new ParserException("Selector expected at position " + pos);
        }

        ignoreWhitespaces();
        if (end() || current != ')') {
            throw new ParserException("Expected ')' at position " + pos);
        }
        //sb.append(current);
        next();

        return new NegationSelector(simpleSelector, new Context(content, sb.toString(), parenthesisPos, pos));

    }

    /**
     * pseudo
     *  // '::' starts a pseudo-element, ':' a pseudo-class
     *  // Exceptions: :first-line, :first-letter, :before and :after.
     *  // Note that pseudo-elements are restricted to one per selector and
     *  // occur only in the last simple_selector_sequence.
     *  : ':' ':'? [ IDENT | functional_pseudo ]
     *  ;
     *
     * functional_pseudo
     *  : FUNCTION S* expression ')'
     */
    protected PseudoSelector pseudo(String ident, PseudoType type, boolean doubleColon) throws ParserException {

    	int initialPos = pos;
        StringBuilder sb = new StringBuilder();
        sb.append(ident);
        PseudoExpression expression = null;
        if (!end() && current == '(') {
            sb.append(current);
            next();
            ignoreWhitespaces();
            if (end()) {
                throw new ParserException("Expected expression at position " + pos);
            }

            //expression
            expression = expression();
            sb.append(expression.getContext());

            //close parenthesis
            ignoreWhitespaces();
            if (end() || current != ')') {
                throw new ParserException("Expected ')' at position " + pos);
            }
            sb.append(current);
            next();
        }
        return new PseudoSelector(ident, type, doubleColon, expression, new Context(content, sb.toString(), initialPos, pos));

    }

    /**
     * expression
     *  // In CSS3, the expressions are identifiers, strings,
     *  // or of the form "an+b"
     *  : [ [ PLUS | '-' | DIMENSION | NUMBER | STRING | IDENT ] S* ]+
     *
     *  {num}{ident}     return DIMENSION;
     *  {num}            return NUMBER;
     */
    protected PseudoExpression expression() throws ParserException {

        List<Item> items = new ArrayList<Item>();
        StringBuilder sb = new StringBuilder();
        int initialPos = pos;

        if (current == '\'' || current == '"') {
        	//string
        	Character quote = current;
            StringValue s = string();
            sb.append(quote);
            sb.append(s.getContext().toString());
            sb.append(quote);
            items.add(new Item(Type.STRING, s.getActualValue()));

            return new PseudoExpression(items, null, s.getActualValue(), new Context(content, sb.toString(), initialPos, pos));
        } else {

        	while (!end()) {
	            if (current == '+') {
	                items.add(new Item(Type.SIGNAL, "+"));
	                sb.append(current);
	                next();
	            } else if (current == '-') {
	                items.add(new Item(Type.SIGNAL, "-"));
	                sb.append(current);
	                next();
	            } else if (Character.isLetter(current)) {
	                //identifier
	                String ident = simpleIdentifier();
	                sb.append(ident);
	                items.add(new Item(Type.IDENTIFIER, ident));
	            } else if (Character.isDigit(current)) {
	                //number or dimension
	                String number = number();
	                sb.append(number);
	                items.add(new Item(Type.NUMBER, number));
	            } else {
	                break;
	            }
	            ignoreWhitespaces();
	        }

        }
        return new PseudoExpression(items, PseudoExpressionEvaluator.evaluate(items), sb.toString(), new Context(content, sb.toString(), initialPos, pos));

    }

    /**
     * ident     [-]?{nmstart}{nmchar}*
     * name      {nmchar}+
     * nmstart   [_a-z]|{nonascii}|{escape}
     * nonascii  [^\0-\177]
     * unicode   \\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?
     * escape    {unicode}|\\[^\n\r\f0-9a-f]
     * nmchar    [_a-z0-9-]|{nonascii}|{escape}
     * num       [0-9]+|[0-9]*\.[0-9]+
     */
    protected String identifier() throws ParserException {
        //TODO implement unicode, escape, [-], nonascii
        StringBuilder sb = new StringBuilder();

        //validates first character
        if (!end() && !Character.isLetter(current) && current != '_') {
            return "";
        }
        while (!end() && (Character.isLetterOrDigit(current) || current == '-' || current == '_')) {
            sb.append(current);
            next();
        }
        return sb.toString();
    }

    protected String simpleIdentifier() throws ParserException {
        StringBuilder sb = new StringBuilder();
        while (!end() && (Character.isLetter(current))) {
            sb.append(current);
            next();
        }
        return sb.toString();
    }

    /**
     * pseudo (Special cases)
     *  // '::' starts a pseudo-element, ':' a pseudo-class
     *  // Exceptions: :first-line, :first-letter, :before and :after.
     */
    protected boolean isPseudoSpecialCase(String identifier) {
        return "first-line".equalsIgnoreCase(identifier) ||
                "first-letter".equalsIgnoreCase(identifier) ||
                "before".equalsIgnoreCase(identifier) ||
                "after".equalsIgnoreCase(identifier);
    }

    protected void ignoreWhitespaces() throws ParserException {
        if (!end() && Character.isWhitespace(current)) {
            while (!end() && Character.isWhitespace(current)) {
                next();
            }
        }
    }

}


/*
 * $#
 * FOS Weka
 *
 * Copyright (C) 2013 Feedzai SA
 *
 * This program 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.
 *
 * This program 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 this program.  If not, see
 * <http://www.gnu.org/licenses/gpl-3.0.html>.
 * #$
 */
package com.feedzai.fos.impl.weka.utils;

import com.feedzai.fos.api.FOSException;
import com.feedzai.fos.api.ModelDescriptor;
import com.feedzai.fos.common.validation.NotNull;
import com.feedzai.fos.impl.weka.utils.pmml.PMMLConsumers;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.SerializationUtils;

import java.io.*;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;

/**
 * Deep clonner to copy objects via serialization (thead safe!)
 *
 * @author Marco Jorge (marco.jorge@feedzai.com)
 */
public class Cloner<T extends Serializable> {
    private byte[] serializedObject;

    /**
     * Creates a new clonner from the given serialized object.
     *
     * @param serializedObject the serialized object that will cloned
     */
    public Cloner(byte[] serializedObject) {
        checkNotNull(serializedObject, "The serialized object cannot be null");

        this.serializedObject = serializedObject;
    }

    /**
     * Creates a clonner from the given object.
     *
     * @param serializable the object to clone
     * @throws IOException when there were problems serializing the object
     */
    public Cloner(T serializable) throws IOException {
        checkNotNull(serializable, "The serialized object cannot be null");

        ObjectOutputStream oos = null;
        ByteArrayOutputStream baos = null;

        try {
            baos = new ByteArrayOutputStream();
            oos = new ObjectOutputStream(baos);
            oos.writeObject(serializable);
            oos.flush();

            this.serializedObject = baos.toByteArray();
        } finally {
            IOUtils.closeQuietly(baos);
            IOUtils.closeQuietly(oos);
        }
    }

    /**
     * Creates a clonner by reading a serialized object from file.
     *
     * @param descriptor A {@link com.feedzai.fos.api.ModelDescriptor} with the information about the classifier.
     * @throws IOException when there were problems reading the file
     */
    public Cloner(ModelDescriptor descriptor) throws IOException {
        checkNotNull(descriptor.getModelFilePath(), "Source file cannot be null");

        File file = new File(descriptor.getModelFilePath());

        checkArgument(file.exists(), "Source file '"+ file.getAbsolutePath() + "' must exist");

        switch (descriptor.getFormat()) {
            case BINARY:
                this.serializedObject = FileUtils.readFileToByteArray(file);
                break;
            case PMML:
                try {
                    this.serializedObject = SerializationUtils.serialize(PMMLConsumers.consume(file));
                } catch (FOSException e) {
                    throw new RuntimeException("Failed to consume PMML file.", e);
                }
                break;
        }
    }

    /**
     * Gets a fresh clone of the object.
     *
     * @return a fresh clone
     * @throws IOException            when there were problems serializing the object
     * @throws ClassNotFoundException when the serialized objects's class was not found
     */
    @NotNull
    public T get() throws IOException, ClassNotFoundException {
        /* cannot be pre-instantiated to enable thread concurrency*/
        ByteArrayInputStream byteArrayInputStream = null;
        ObjectInputStream objectInputStream = null;
        try {
            byteArrayInputStream = new ByteArrayInputStream(this.serializedObject);
            objectInputStream = new ObjectInputStream(byteArrayInputStream);

            return (T) objectInputStream.readObject();
        } finally {
            IOUtils.closeQuietly(byteArrayInputStream);
            IOUtils.closeQuietly(objectInputStream);
        }
    }

    /**
     * Returns a copy of the serialized object.
     *
     * @return a copy (this object is immutable)
     */
    @NotNull
    public byte[] getSerialized() {
        byte[] result = new byte[serializedObject.length];
        System.arraycopy(serializedObject, 0, result, 0, serializedObject.length);

        return result;
    }

    /**
     * Writes the serialized object to file.
     *
     * @param file the file to write to (will be overwritten)
     * @throws IOException when the file could not be written
     */
    public void write(File file) throws IOException {
        checkNotNull(file, "Output file cannot be null");

        FileUtils.writeByteArrayToFile(file, serializedObject);
    }
}