/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils.concurrent;

import javascalautils.Option;
import javascalautils.ThrowableFunction1;
import javascalautils.Try;
import javascalautils.Validator;

import java.util.List;
import java.util.NoSuchElementException;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Predicate;

import static javascalautils.Option.None;
import static javascalautils.OptionCompanion.Some;
import static javascalautils.TryCompanion.*;

/**
 * The future implementation.
 *
 * @author Peter Nerg
 * @since 1.2
 */
final class FutureImpl<T> implements Future<T> {
    /** Will contain the result once {@link #complete(Try)} is invoked. */
    private Option<Try<T>> response = None();

    /** The success/failure/complete handlers set by the user. */
    private final List<EventHandler> eventHandlers = new CopyOnWriteArrayList<>();

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#isCompleted()
     */
    @Override
    public boolean isCompleted() {
        return response.isDefined();
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#value()
     */
    @Override
    public Option<Try<T>> value() {
        return response;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#onFailure(java.util.function.Consumer)
     */
    @Override
    public void onFailure(Consumer<Throwable> consumer) {
        Validator.requireNonNull(consumer, "Null is not a valid consumer");
        // register a complete handler and ignore any Success responses
        onComplete(result -> {
            // transform Failure to a Success with the Throwable
            // should it be a Success it will be transformed into a Failure and forEach will do nothing
            result.failed().forEach(consumer);
        });
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#onSuccess(java.util.function.Consumer)
     */
    @Override
    public void onSuccess(Consumer<T> consumer) {
        Validator.requireNonNull(consumer, "Null is not a valid consumer");
        // register a complete handler and ignore any Failure responses
        onComplete(result -> {
            // should it be a Failure the forEach will do nothing
            result.forEach(consumer);
        });
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#onComplete(java.util.function.Consumer)
     */
    @Override
    public void onComplete(Consumer<Try<T>> c) {
        Validator.requireNonNull(c, "Null is not a valid consumer");
        eventHandlers.add(new EventHandler(c));
        // invoke all new handlers in case this Future is already completed
        notifyHandlers();
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#forEach(java.util.function.Consumer)
     */
    @Override
    public void forEach(Consumer<T> c) {
        onSuccess(c);
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#map(java.util.function.Function)
     */
    @Override
    public <R> Future<R> map(ThrowableFunction1<T, R> function) {
        // the onFailure function just passed the error as-is without transformation
        return transform(function, t -> t);
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#flatMap(java.util.function.Function)
     */
    @Override
    public <R> Future<R> flatMap(ThrowableFunction1<T, Future<R>> function) {
        Validator.requireNonNull(function, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl<R> future = new FutureImpl<>();

        // install success handler that will map the result before applying it
        onSuccess(value -> {
            // use the provided function to create a mapped future
            //it might actually fail due to the provided function throws an exception, hence we wrap it with a Try
            Try<Future<R>> mapped = Try(() -> function.apply(value));
            //add a recover function to the Try that fails this future with the error, else we complete this Future with the mapped Future
            mapped.recover(t -> Future.failed(t)).forEach(f -> f.onComplete(v -> future.complete(v)));
        });

        // install failure handler that just passes the failure/result through
        onFailure(t -> future.failure(t));

        return future;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#filter(java.util.function.Predicate)
     */
    @Override
    public Future<T> filter(Predicate<T> predicate) {
        Validator.requireNonNull(predicate, "Null is not a valid predicate");
        // Create new future expected to hold the value of the mapped type
        FutureImpl<T> future = new FutureImpl<>();
        // install success handler that will filter the result before applying it
        onSuccess(value -> {
            if (predicate.test(value)) {
                future.success(value);
            } else {
                future.failure(new NoSuchElementException("The predicate failed on value [" + value + "]"));
            }
        });
        // install failure handler that just passes the result through
        onFailure(t -> future.failure(t));
        return future;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#transform(java.util.function.Function, java.util.function.Function)
     */
    @Override
    public <R> Future<R> transform(ThrowableFunction1<T, R> onSuccess, ThrowableFunction1<Throwable, Throwable> onFailure) {
        Validator.requireNonNull(onSuccess, "Null is not a valid function");
        Validator.requireNonNull(onFailure, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl<R> future = new FutureImpl<>();
        // install success handler that will map the result before applying it
        onSuccess(value -> future.complete(Try(() -> onSuccess.apply(value))));
        // install failure handler that will map the error before applying it
        onFailure(t -> Try(() -> onFailure.apply(t)).recover(ex -> ex).forEach(ex -> future.failure(ex)));
        return future;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#recover(java.util.function.Function)
     */
    @Override
    public Future<T> recover(ThrowableFunction1<Throwable, T> recoverFunction) {
        Validator.requireNonNull(recoverFunction, "Null is not a valid function");
        // Create new future expected to hold the value of the mapped type
        FutureImpl<T> future = new FutureImpl<>();

        // installs a handler that will automatically recover the result/Try should it be needed
        onComplete(t -> future.complete(t.recover(recoverFunction)));

        return future;
    }

    /*
     * (non-Javadoc)
     *
     * @see javascalautils.concurrent.Future#result(long, java.util.concurrent.TimeUnit)
     */
    @Override
    public T result(long duration, TimeUnit timeUnit) throws Throwable, TimeoutException {
        // The response is now set to Some
        // return the value of the response (Try), should it be a Failure the exception is raised
        return ready(duration, timeUnit).response.get().get();
    }

    /* (non-Javadoc)
     * @see javascalautils.concurrent.Future#ready(long, java.util.concurrent.TimeUnit)
     */
    @Override
    public FutureImpl<T> ready(long duration, TimeUnit timeUnit) throws TimeoutException, InterruptedException {
        Validator.requireNonNull(timeUnit, "Null is not a valid time unit");
        CountDownLatch latch = new CountDownLatch(1);

        // install a handler that releases the count down latch when notified with a result.
        // the actual result/response is of no interest as we anyways have access to it internally in this class/instance.
        onComplete(t -> latch.countDown());

        // block for either the time to pass or the Future gets completed
        if (!latch.await(duration, timeUnit)) {
            throw new TimeoutException("Timeout waiting ["+duration+"] ["+timeUnit+"] for Future to complete");
        }

        //The future is now complete, return ourselves
        return this;
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.2
     */
    @Override
    public String toString() {
        return "Future:" + response;
    }

    /**
     * Completes this Future with a result.

     * Invoked by the Promise owning this instance.
     *
     * @param result The result to complete with
     * @return Returns itself
     */
    Future<T> complete(Try<T> result) {
        // save the result
        this.response = Some(result);

        // notify all potential handlers
        notifyHandlers();
        return this;
    }

    /**
     * Internal report success to this future.

     *
     * @param value
     *            The response value
     */
    private void success(T value) {
        complete(Success(value));
    }

    /**
     * Internal report failure to this future.

     *
     * @param throwable
     *            The failure Throwable
     */
    private void failure(Throwable throwable) {
        complete(Failure(throwable));
    }

    /**
     * Invoke all handlers with the value of this Future.

     * The response may or may not exist at this point.

     * A filter is applied to make sure we only notify handlers that have not been notified before.
     */
    private <R> void notifyHandlers() {
        // the response may or may not exist at this point
        response.forEach(t -> eventHandlers.stream().forEach(h -> h.notify(t)));
    }

    /**
     * Internal holder for the success/failure/complete handlers provided by the user.

     * Used primarily to keep track on if a particular handler already has been notified.

     * This is to make sure the same handler won't be notified more than once.
     *
     * @author Peter Nerg
     */
    private final class EventHandler {
        private final AtomicBoolean notified = new AtomicBoolean(false);
        private final Consumer<Try<T>> consumer;

        private EventHandler(Consumer<Try<T>> consumer) {
            this.consumer = consumer;
        }

        /**
         * Notifies the response to the handler.

         * Invoking this more than once makes no difference.
         *
         * @param response The result to the listener with
         */
        private void notify(Try<T> response) {
            if (notified.compareAndSet(false, true)) {
                consumer.accept(response);
            }
        }
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.EitherCompanion.Left;
import static javascalautils.EitherCompanion.Right;

/**
 * Represents an {@link Option} holding a value.

 * The instance of {@link Some} is guaranteed to keep a non-null value object.

 * Null values are not allowed as it implies an instance of {@link None}.

 * One can consider {@link Some} as a collection of size 1.

 *

 * Either use the implementation directly:

 *
Option<String> option = new Some<>("Peter is Great!");

 * or use the factory/apply method from Option:

 *
Option<String> option = Option.apply("Peter is Great!");
 For examples on usage refer to the documentation for {@link Option}
 * .
 *
 * @author Peter Nerg
 * @since 1.0
 * @param <T>
 *            The type of the value represented by this instance
 */
public final class Some<T> extends SingleItemContainer<T> implements Option<T>, Serializable {
    private static final long serialVersionUID = -17186529545151493L;
    private final T value;

    /**
     * Creates an instance for the provided value.

     * Null objects are not allowed and will render an exception.
     *
     * @param value
     *            The value represented by this Some
     * @since 1.0
     */
    public Some(T value) {
        this.value = Validator.requireNonNull(value, "Null values are not allowed for Some");
    }

    /**
     * Returns true if the predicate matches the value.
     *
     * @since 1.0
     */
    @Override
    public boolean exists(Predicate<T> p) {
        return p.test(value);
    }

    /**
     * Always returns the value for this instance.

     * Guaranteed to return a non-null value.
     *
     * @since 1.0
     */
    @Override
    public T get() {
        return value;
    }

    /**
     * Always returns the value for this instance.
     *
     * @since 1.0
     */
    @Override
    public T getOrElse(Supplier<T> s) {
        return value;
    }

    /**
     * Returns an {@link Option} containing the value of applying the given function to the current value.
     *
     * @since 1.0
     */
    @Override
    public <R> Option<R> map(ThrowableFunction1<T, R> f) {
        return flatMap(v -> Option.apply(f.apply(v)));
    }

    /**
     * Returns an Option consisting of the result of applying the given function to the current value.
     *
     * @since 1.2
     */
    @Override
    public <R> Option<R> flatMap(ThrowableFunction1<T, Option<R>> function) {
        try {
            return function.apply(value);
        }
        catch (Throwable ex) {
            throw new BrokenFunctionException("Caught exception while applying function", ex);
        }
    }

    /**
     * Always returns this.
     *
     * @since 1.0
     */
    @Override
    public Option<T> orElse(Supplier<Option<T>> s) {
        return this;
    }

    /**
     * Returns a {@link Left} containing the value of this instance.
     *
     * @since 1.4
     */
    @Override
    public <R> Either<T, R> toLeft(Supplier<R> right) {
        return Left(value);
    }

    /**
     * Returns a {@link Right} containing the value of this instance.
     *
     * @since 1.4
     */
    @Override
    public <L> Either<L, T> toRight(Supplier<L> left) {
        return Right(value);
    }

    /**
     * Returns true if the other object is {@link Some} containing a value that equals the value of this {@link Some}, else false.
     *
     * @since 1.0
     */
    @Override
    public boolean equals(Object other) {
        // get is safe to invoke as it must be a Some and shall therefore always have a value
        return (other instanceof Some) && ((Option<?>) other).exists(o -> o.equals(value));
    }

    /**
     * Returns the hashCode based on the value of this instance.
     *
     * @since 1.0
     */
    @Override
    public int hashCode() {
        // no need for null checks on the value, as it per definition cannot be null
        return 31 + value.hashCode();
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.0
     */
    @Override
    public String toString() {
        return "Some:" + value;
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils.concurrent;

import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Thread factory for providing meaningful names to the created threads.

 * All threads created through this factory will be given a unique identifier based on a sequence counter.

 * E.g. providing the name <tt>MyThread</tt> will yield threads called <tt>MyThread-n</tt>, where n is the sequence counter for created threads.

 * The counter starts at 1, thus the naming series will be threadName-[1-n]
 *
 * @author Peter Nerg
 * @since 1.2
 */
public final class NamedSequenceThreadFactory implements ThreadFactory {
    /** Counter for creating unique thread names. */
    private final AtomicLong counter = new AtomicLong(1);

    /** The name of the threads. */
    private final String threadName;

    /**
     * Create the factory.
     *
     * @param threadName
     *            The name of the threads to be created.
     * @since 1.2
     */
    public NamedSequenceThreadFactory(String threadName) {
        this.threadName = threadName;
    }

    /**
     * Creates a new thread using the provided name and sequence counter.

     * E.g. threadName-nnn
     *
     * @since 1.2
     */
    @Override
    public Thread newThread(Runnable runnable) {
        return new Thread(runnable, threadName + "-" + counter.getAndIncrement());
    }

}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package javascalautils;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.EitherCompanion.Right;

/**
 * This is a right-biased wrapper for an instance of {@link Either}.
 *
 * @author Peter Nerg
 * @since 1.1
 */
public class RightProjection<L, R> extends Projection<R> implements Serializable {
    private static final long serialVersionUID = 4251047373391313192L;
    private final Either<L, R> either;

    /**
     * Creates an instance of the projection.
     *
     * @param either
     *            The instance to wrap.
     * @since 1.1
     */
    RightProjection(Either<L, R> either) {
        this.either = either;
    }

    /**
     * Returns false if {@link Left} or returns the result of applying the predicate to the {@link Right} value.
     *
     * @param predicate
     *            The predicate to apply
     * @return If this is a {@link Right} and the predicate matches the value
     * @since 1.1
     */
    public boolean exists(Predicate<R> predicate) {
        return either.isRight() && predicate.test(orNull());
    }

    /**
     * Returns the value if this is a {@link Right} else the value provided by the supplier.
     *
     * @param supplier
     *            The supplier
     * @return The value
     * @since 1.1
     */
    @Override
    public R getOrElse(Supplier<R> supplier) {
        return either.fold(v -> supplier.get(), v -> v);
    }

    /**
     * Returns a {@link Some} wrapping the {@link Either} if it's a {@link Right} and the value of the {@link Right} matches the predicate, else {@link None} .
     *
     * @param predicate
     *            The predicate to apply
     * @return The resulting Option of the filter operation
     * @since 1.1
     */
    public Option<Either<L, R>> filter(Predicate<R> predicate) {
        return Option.apply(exists(predicate) ? either : null);
    }

    /**
     * Returns true if {@link Left} or returns the result of applying the predicate to the {@link Right} value.
     *
     * @param predicate
     *            The predicate to apply
     * @return If it is a match
     * @since 1.1
     */
    public boolean forAll(Predicate<R> predicate) {
        return !either.isRight() || predicate.test(orNull());
    }

    /**
     * If this projection contains a {@link Right} then a new {@link Right} is returned containing the value from the original {@link Right} mapped via the
     * provided function, else the contained Either is returned as is.
     *
     * @param <T>
     *            The type to return as the new {@link Right}
     * @param function
     *            The function
     * @return Mapped Either
     * @since 1.1
     */
    @SuppressWarnings("unchecked")
    public <T> Either<L, T> map(Function<R, T> function) {
        return either.isRight() ? Right(function.apply(get())) : (Either<L, T>) either;
    }

    /**
     * If this projection contains a {@link Right} then a new {@link Right} is returned containing the value from the original {@link Right} mapped via the
     * provided function, else the contained Either is returned as is.
     *
     * @param <T>
     *            The type to return as the new {@link Right}
     * @param function
     *            The function
     * @return Mapped Either
     * @since 1.2
     */
    @SuppressWarnings("unchecked")
    public <T> Either<L, T> flatMap(Function<R, Right<L, T>> function) {
        return either.isRight() ? function.apply(get()) : (Either<L, T>) either;
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils;

import java.io.Serializable;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.TryCompanion.Success;

/**
 * Represents the failure implementation of {@link Try}.

 * Acts as a carrier for the result/throwable of a unsuccessful computation.

 * For examples on usage refer to the documentation for {@link Try}.
 *
 * @author Peter Nerg
 * @since 1.0
 * @param <T>
 *            The type of the exception represented by this instance
 */
public final class Failure<T> extends EmptyContainer<T> implements Try<T>, Serializable {

    private static final long serialVersionUID = -3153874073796965428L;
    private final Throwable throwable;

    /**
     * Creates a Failure for the provided Throwable.

     * Null values are not allowed since a 'failure' needs a reason.
     *
     * @param throwable
     *            The throwable
     * @since 1.0
     */
    public Failure(Throwable throwable) {
        this.throwable = Validator.requireNonNull(throwable, "Null values are not allowed for Failure");
    }

    /**
     * Always returns false.
     *
     * @since 1.0
     */
    @Override
    public boolean isSuccess() {
        return false;
    }

    /**
     * Always returns the value provided by the supplier.

     * As per definition this is a failure without any data to return.
     *
     * @since 1.0
     */
    @Override
    public T getOrElse(Supplier<T> supplier) {
        return supplier.get();
    }

    /**
     * Always returns the value provided by the supplier.

     * As per definition this is a failure without any data to return.
     *
     * @since 1.0
     */
    @Override
    public Try<T> orElse(Supplier<Try<T>> supplier) {
        return supplier.get();
    }

    /**
     * Always throws the {@link Throwable} this instance represents.

     * As per definition this is a failure without any data to return.
     *
     * @since 1.0
     */
    @Override
    public T get() throws Throwable {
        throw throwable;
    }

    /**
     * Returns a {@link Success} with the {@link Throwable} this instance represents.
     *
     * @since 1.0
     */
    @Override
    public Try<Throwable> failed() {
        return Success(throwable);
    }

    /**
     * Always returns this.

     * As per definition this is a failure without any data to map.
     *
     * @since 1.0
     */
    @SuppressWarnings("unchecked")
    @Override
    public <R> Try<R> map(ThrowableFunction1<T, R> function) {
        return (Try<R>) this;
    }

    /**
     * Always returns this.

     * As per definition this is a failure without any data to map.
     *
     * @since 1.2
     */
    @Override
    public <R> Try<R> flatMap(ThrowableFunction1<T, Try<R>> function) {
        return map(null);
    }

    /**
     * Always returns this.

     * As per definition this is a failure without any data to filter.
     *
     * @since 1.4
     */
    @Override
    public Try<T> filter(Predicate<T> predicate) {
        return this;
    }

    /**
     * Applies the provided function to the Throwable of this {@link Failure} and returns a {@link Success} with the result.
     *
     * @since 1.4
     */
    @Override
    public Try<T> recover(ThrowableFunction1<Throwable, T> function) {
        return recoverWith(t -> Success(function.apply(t)));
    }

    /**
     * Applies the provided function to the Throwable of this {@link Failure} and returns the result.
     *
     * @since 1.4
     */
    @Override
    public Try<T> recoverWith(ThrowableFunction1<Throwable, Try<T>> function) {
        try {
            return function.apply(throwable);
        }
        catch(Throwable t) {
            return new Failure<>(t);
        }
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.0
     */
    @Override
    public String toString() {
        return "Failure:" + throwable.getMessage();
    }

}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package javascalautils;

/**
 * Acts as a Scala type companion object for {@link Try}/{@link Success}/{@link Failure}.

 * The primary purpose is to get the Scala feel of instantiating classes.

 * In Scala you can define a companion object for a class, acting as a static reference/singleton for that class allowing you do define factory methods.

 * One use case is to define methods with the same name as the class and let these methods invoke the constructor thus creating a nice way to create instances
 * without using the word "new".

 * This can be achieved in java by statically importing a method and then using it.

 * The limitation is that classes may not have method with the same name as the class itself hence new companion classes have to be created.

 * To be able to use it in a neat concise way one needs to statically import the method.

 *
 *

 * import static javascalautils.TryCompanion.Failure;
 * import static javascalautils.TryCompanion.Success;
 * import static javascalautils.TryCompanion.Try;
 *
 * Try<Integer> t = Try(() -> 9 / 3);
 * Try<String> ts = Success("Peter was here");
 * Try<String> tf = Failure(new Exception("Bad mojo!"));
 *
 * //code that performs a side effect but has not return type can be written like this
 * Try<Unit> t = Try(() -> {
 *    database.delete(someId);
 * });
 *
 *

 *
 *

 *
 * @author Peter Nerg
 * @since 1.3
 */
public final class TryCompanion {

    private TryCompanion() {
    }

    /**
     * Creates an instance of {@link Try} with a {@link Unit} return type.

     * The purpose is to allow the user to invoke a side-effecting function that may succeed/fail but has now return type.

     * E.g. deleting something from a database may not have an interesting return type. One is only interested of the outcome, {@link Success}/{@link Failure}.
     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.TryCompanion.Try;
     *
     * Try<Unit> t = Try(() -> {
     *    database.delete(someId);
     * });
     *

     *

     *
     * @param function
     *            The function to render either the value T or raise an exception.
     * @return The resulting Try instance wrapping either {@link Unit} or an exception
     * @since 1.9
     */
    public static Try<Unit> Try(VoidFunction0 function) {
    	return Try.apply(() -> {
    		function.apply();
    		return Unit.Instance;
    	});
    }

    /**
     * Creates an instance of {@link Try} wrapping the result of the provided function.

     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.TryCompanion.Try;
     *
     * Try<Integer> t = Try(() -> 9 / 3);
     *

     *
     *

     *
     * @param <T>
     *            The type for the Try
     * @param function
     *            The function to render either the value T or raise an exception.
     * @return The resulting Try instance wrapping what the function resulted in
     * @see Try#apply(ThrowableFunction0)
     * @since 1.3
     */
    public static <T> Try<T> Try(ThrowableFunction0<T> function) {
        return Try.apply(function);
    }

    /**
     * Creates an instance of {@link Failure} wrapping the provided throwable.

     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.TryCompanion.Failure;
     *
     * Try<String> tf = Failure(new Exception("Bad mojo!"));
     *

     *
     *

     *
     * @param <T>
     *            The type for the Try
     * @param throwable
     *            The throwable to wrap
     * @return The failure instance
     * @see Failure#Failure(Throwable)
     * @since 1.3
     */
    public static <T> Failure<T> Failure(Throwable throwable) {
        return new Failure<>(throwable);
    }

    /**
     * Creates an instance of {@link Success} wrapping the provided value.

     * Best used in conjunction with statically importing this method.
     *
     *

     *

     * import static javascalautils.TryCompanion.Success;
     *
     * Try<String> ts = Success("Peter was here");
     *

     *
     *

     *
     * @param <T>
     *            The type for the Try
     * @param value
     *            The value to wrap
     * @return The failure instance
     * @see Success#Success(Object)
     * @since 1.3
     */
    public static <T> Success<T> Success(T value) {
        return new Success<>(value);
    }

}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package javascalautils.concurrent;

import javascalautils.Failure;
import javascalautils.Success;
import javascalautils.Try;

import static javascalautils.TryCompanion.Try;

/**
 * Class with utilities for reflective operations.

 * This class is not intended for public usage, it's a library internal helper.

 *
 * @author Peter Nerg
 * @since 1.4
 */
final class ReflectionUtil {

    /**
     * Inhibitive constructor.
     */
    private ReflectionUtil() {
    }

    /**
     * Attempts to create an instance of the provided class.

     * This expects the class to be public and have a public (default) constructor with no arguments.
     *
     * @param <T>
     *            The expected to be returned by this method
     * @param className
     *            The name of the class
     * @return The result, either {@link Success Success(T)} with the instance or {@link Failure Failure(Throwable)} in case of failure
     * @since 1.4
     */
    @SuppressWarnings("unchecked")
    static <T> Try<T> newInstance(String className) {
        return Try(() -> (T) Class.forName(className).newInstance());
    }

}


/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils.concurrent;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;

import static javascalautils.concurrent.PromiseCompanion.Promise;

/**
 * Implements the executor interface.

 * Internally uses a Java concurrent executor to provide the thread execution mechanism.
 *
 * @author Peter Nerg
 * @since 1.2
 */
final class ExecutorImpl implements Executor {

    /** The internal Java concurrent executor to perform the actual work. */
    private final java.util.concurrent.Executor executor;

    /**
     * Creates an instance using the provided SDK {@link java.util.concurrent.Executor} for thread management.
     *
     * @param executor The underlying executor to use
     * @since 1.2
     */
    ExecutorImpl(java.util.concurrent.Executor executor) {
        this.executor = executor;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#execute(com.ericsson.concurrent.Executable)
     */
    @Override
    public <T> Future<T> execute(final Executable<T> executable) {
        final Promise<T> promise = Promise();
        return execute(promise, () -> {
            try {
                executable.execute(promise);
                // if the implementation didn't respond to the Promise we mark it as a failure.
                if (!promise.isCompleted()) {
                    promise.failure(new IllegalStateException("No response was provided by the Promise"));
                }
            } catch (Exception ex) {
                promise.failure(ex);
            }
        });
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#execute(java.util.concurrent.Callable)
     */
    @Override
    public <T> Future<T> execute(final Callable<T> callable) {
        final Promise<T> promise = Promise();
        return execute(promise, () -> {
            try {
                promise.success(callable.call());
            } catch (Exception ex) {
                promise.failure(ex);
            }
        });
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#executeAll(com.ericsson.concurrent.Executable<T>[])
     */
    @Override
    public <T> List<Future<T>> executeAll(@SuppressWarnings("unchecked") Executable<T>... executables) {
        List<Future<T>> responses = new ArrayList<>();
        for (Executable<T> executable : executables) {
            responses.add(execute(executable));
        }
        return responses;
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#executeAll(java.util.concurrent.Callable<T>[])
     */
    @Override
    public <T> List<Future<T>> executeAll(@SuppressWarnings("unchecked") Callable<T>... callables) {
        List<Future<T>> responses = new ArrayList<>();
        for (Callable<T> callable : callables) {
            responses.add(execute(callable));
        }
        return responses;
    }

    /**
     * Executes the provided {@link Runnable} on the internal executor.

     * Any issues related to the internal executor are reported to the provided {@link Promise}
     *
     * @param promise
     *            The promise to fulfill once the work is finished
     * @param runnable
     *            The runnable to execute in the executor
     * @return The Future holding the response-to-be
     */
    private <T> Future<T> execute(Promise<T> promise, Runnable runnable) {
        try {
            executor.execute(runnable);
        } catch (RejectedExecutionException ex) {
            // could be rejected due to resource starvation, report a failure
            promise.failure(ex);
        }
        return promise.future();
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#shutdown()
     */
    @Override
    public void shutdown() {
        if (executor instanceof ExecutorService) {
            ((ExecutorService) executor).shutdown();
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see com.ericsson.concurrent.Executor#awaitTermination(long, java.util.concurrent.TimeUnit)
     */
    @Override
    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        if (executor instanceof ExecutorService) {
            return ((ExecutorService) executor).awaitTermination(timeout, unit);
        }
        return true;
    }
}


/**
 * Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package javascalautils;

import java.io.Serializable;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
import java.util.function.Supplier;

import static javascalautils.TryCompanion.Try;

/**
 * Represents the successful implementation of {@link Try}.

 * Acts as a carrier for the result of a successful computation.

 * For examples on usage refer to the documentation for {@link Try}.
 *
 * @author Peter Nerg
 * @since 1.0
 * @param <T>
 *            The type of the value represented by this instance
 */
public final class Success<T> extends SingleItemContainer<T> implements Try<T>, Serializable {

    private static final long serialVersionUID = -5588727176438040926L;
    private final T value;

    /**
     * Creates a 'successful' instance with the provided value.

     * Null values are allowed.
     *
     * @param value
     *            The value represented by this instance
     * @since 1.0
     */
    public Success(T value) {
        this.value = value;
    }

    /**
     * Always returns true.
     *
     * @since 1.0
     */
    @Override
    public boolean isSuccess() {
        return true;
    }

    /**
     * Returns the value provided in the constructor.

     * I.e. the provided supplier is never use.
     *
     * @since 1.0
     */
    @Override
    public T getOrElse(Supplier<T> supplier) {
        return value;
    }

    /**
     * Always returns this.

     * I.e. the provided supplier is never use.
     *
     * @since 1.0
     */
    @Override
    public Try<T> orElse(Supplier<Try<T>> supplier) {
        return this;
    }

    /**
     * Returns the value held by this instance.

     * Never throws an exception as this is a success
     *
     * @since 1.0
     */
    @Override
    public T get() {
        return value;
    }

    /**
     * Returns a {@link Failure} with an {@link UnsupportedOperationException}.

     * Since this is a success there is no failure to map to a success i.e. this is a illegal operation.
     *
     * @since 1.0
     */
    @Override
    public Try<Throwable> failed() {
        return new Failure<>(new UnsupportedOperationException("Cannot make a Failure of a Success"));
    }

    /**
     * Applies the value to the function and returns the {@link Try} representing the mapped value.
     *
     * @since 1.0
     */
    @Override
    public <R> Try<R> map(ThrowableFunction1<T, R> function) {
        return Try(() -> function.apply(value));
    }

    /**
     * Applies the value to the function and returns the {@link Try} generated by the function.
     *
     * @since 1.2
     */
    @Override
    public <R> Try<R> flatMap(ThrowableFunction1<T, Try<R>> function) {
        try {
            return function.apply(value);
        }
        catch(Throwable t) {
            return new Failure<>(t);
        }
    }

    /**
     * Applies the predicate to the value of this instance, if it matches this is returned else a {@link Failure}.
     *
     * @since 1.4
     */
    @Override
    public Try<T> filter(Predicate<T> predicate) {
        return predicate.test(value) ? this : new Failure<>(new NoSuchElementException("Predicate did not match value, now empty result"));
    }

    /**
     * Always returns this .

     * As per definition this is a success and will not need to be recovered.
     *
     * @since 1.4
     */
    @Override
    public Try<T> recover(ThrowableFunction1<Throwable, T> function) {
        return this;
    }

    /**
     * Always returns this .

     * As per definition this is a success and will not need to be recovered.
     *
     * @since 1.4
     */
    @Override
    public Try<T> recoverWith(ThrowableFunction1<Throwable, Try<T>> function) {
        return this;
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.0
     */
    @Override
    public String toString() {
        return "Success:" + (value != null ? value.toString() : "NULL VALUE");
    }
}


/**
 *  Copyright 2015 Peter Nerg
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package javascalautils;

import java.io.Serializable;
import java.util.function.Function;

import static javascalautils.EitherCompanion.Left;

/**
 * Represents the {@link Right} side of an {@link Either}.
 *
 * @author Peter Nerg
 * @since 1.1
 * @param <L>
 *            The type for the {@link Left} side (not used for this class)
 * @param <R>
 *            The type for the {@link Right} side
 */
public final class Right<L, R> implements Either<L, R>, Serializable {

    private static final long serialVersionUID = -2119790544433508346L;
    private final R value;

    /**
     * Creates an instance wrapping the provide value.
     *
     * @param value
     *            The value wrapped by this instance
     * @since 1.1
     */
    public Right(R value) {
        this.value = value;
    }

    /**
     * Applies the provided func_right to the wrapped value and returns the result.
     *
     * @since 1.1
     */
    @Override
    public <T> T fold(Function<L, T> func_left, Function<R, T> func_right) {
        return func_right.apply(value);
    }

    /**
     * Always returns true.
     *
     * @since 1.1
     */
    @Override
    public boolean isRight() {
        return true;
    }

    /**
     * Returns a {@link Left} containing the value for this instance.
     *
     * @since 1.1
     */
    @Override
    public Either<R, L> swap() {
        return Left(value);
    }

    /**
     * Returns a String representation of the instance.
     *
     * @since 1.1
     */
    @Override
    public String toString() {
        return "Right:" + value;
    }
}


package io.tracee.contextlogger.contextprovider.jaxws;

import io.tracee.contextlogger.MessagePrefixProvider;
import io.tracee.contextlogger.TraceeContextLogger;
import io.tracee.contextlogger.api.internal.MessageLogLevel;
import io.tracee.contextlogger.contextprovider.core.CoreImplicitContextProviders;
import io.tracee.contextlogger.contextprovider.jaxws.contextprovider.JaxWsWrapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.xml.namespace.QName;
import javax.xml.soap.SOAPMessage;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.handler.soap.SOAPHandler;
import javax.xml.ws.handler.soap.SOAPMessageContext;
import java.io.ByteArrayOutputStream;
import java.nio.charset.Charset;
import java.util.Set;

/**
 * Abstract base class for detecting JAX-WS related uncaught exceptions and outputting of contextual information.
 */
public abstract class AbstractTraceeErrorLoggingHandler implements SOAPHandler<SOAPMessageContext> {

	private static final Logger logger = LoggerFactory.getLogger(AbstractTraceeErrorLoggingHandler.class);

	protected static final ThreadLocal<String> THREAD_LOCAL_SOAP_MESSAGE_STR = new ThreadLocal<String>();

	/**
	 * The constructor.
	 */
	AbstractTraceeErrorLoggingHandler() {
	}

	@Override
	public final boolean handleMessage(final SOAPMessageContext context) {
		if (this.isOutgoing(context)) {
			this.handleOutgoing(context);
		} else {
			this.handleIncoming(context);
		}
		return true;
	}

	@Override
	public final boolean handleFault(SOAPMessageContext context) {

		// Must pipe out Soap envelope
		SOAPMessage soapMessage = context.getMessage();

		TraceeContextLogger
				.create()
				.enforceOrder()
				.apply()
				.logWithPrefixedMessage(MessagePrefixProvider.provideLogMessagePrefix(MessageLogLevel.ERROR, "JAX-WS"), CoreImplicitContextProviders.COMMON,
						CoreImplicitContextProviders.TRACEE, JaxWsWrapper.wrap(THREAD_LOCAL_SOAP_MESSAGE_STR.get(), convertSoapMessageAsString(soapMessage)));

		return true;
	}

	@Override
	public void close(MessageContext context) {
		// cleanup thread local request soap message
		THREAD_LOCAL_SOAP_MESSAGE_STR.remove();
	}

	@Override
	public final Set<QName> getHeaders() {
		return null;
	}

	/**
	 * Converts a SOAPMessage instance to string representation.
	 */
	String convertSoapMessageAsString(SOAPMessage soapMessage) {
		if (soapMessage == null) {
			return "null";
		}
		try {
			ByteArrayOutputStream os = new ByteArrayOutputStream();
			soapMessage.writeTo(os);
			return new String(os.toByteArray(), determineMessageEncoding(soapMessage));
		} catch (Exception e) {
			logger.error("Couldn't create string representation of soapMessage: " + soapMessage.toString());
			return "ERROR";
		}
	}

	Charset determineMessageEncoding(SOAPMessage soapMessage) {
		try {
			final Object encProp = soapMessage.getProperty(SOAPMessage.CHARACTER_SET_ENCODING);
			if (encProp != null) {
				return Charset.forName(String.valueOf(encProp));
			}
			return Charset.forName("UTF-8");
		} catch (Exception e) {
			return Charset.forName("UTF-8");
		}
	}

	protected void storeMessageInThreadLocal(SOAPMessageContext context) {

		// Save soap request message in thread local storage for error logging
		SOAPMessage soapMessage = context.getMessage();
		if (soapMessage != null) {
			String soapMessageAsString = convertSoapMessageAsString(soapMessage);
			THREAD_LOCAL_SOAP_MESSAGE_STR.set(soapMessageAsString);
		}
	}

	protected abstract void handleIncoming(SOAPMessageContext context);

	protected abstract void handleOutgoing(SOAPMessageContext context);

	/**
	 * Checks whether is is an incoming or outgoing call.
	 *
	 * @param messageContext the message context
	 * @return true if is outgoing call, otherwise false
	 */
	private boolean isOutgoing(MessageContext messageContext) {
		Object outboundBoolean = messageContext.get(MessageContext.MESSAGE_OUTBOUND_PROPERTY);
		return outboundBoolean != null && (Boolean) outboundBoolean;
	}

}


package io.tracee.contextlogger.impl;

import io.tracee.contextlogger.contextprovider.api.TraceeContextProviderMethod;
import io.tracee.contextlogger.profile.ProfileSettings;
import io.tracee.contextlogger.utility.GetterUtilities;

import java.lang.reflect.Method;

/**
 * Wrapper class for methods and their {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProviderMethod} annotations.
 * Used for sorting (by {@link MethodAnnotationPairComparator}) and other tasks.
 */
public class MethodAnnotationPair {

    private final Class baseType;
    private final TraceeContextProviderMethod annotation;
    private final Method method;

    public MethodAnnotationPair(final Class baseType, final Method method, final TraceeContextProviderMethod annotation) {
        this.baseType = baseType;
        this.method = method;
        this.annotation = annotation;
    }

    /**
     * Determines if a method should be processed. This will either be if no
     * {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProviderMethod} annotation
     * is present or if an empty property name is defined in the annotation or if passed profileSettings are null
     * or if the property name is disabled in the {@link io.tracee.contextlogger.profile.ProfileSettings}.
     *
     * @param profileSettings The profile sttings object which should be used to check against.
     * @return true if the result of the method should be processed, otherwise false.
     */
    public boolean shouldBeProcessed(final ProfileSettings profileSettings) {

        if (profileSettings != null) {

            String propertyName = GetterUtilities.getFullQualifiedFieldName(baseType, method);
            Boolean shouldBeProcessed = profileSettings.getPropertyValue(propertyName);
            if (shouldBeProcessed == null) {
                propertyName = baseType.getCanonicalName() + "." + method.getName();
                shouldBeProcessed = profileSettings.getPropertyValue(propertyName);
            }
            if (shouldBeProcessed == null && annotation != null) {
                shouldBeProcessed = annotation.enabledPerDefault();
            }

            if (shouldBeProcessed != null) {
                return shouldBeProcessed;
            }
        }

        return true;

    }

    public TraceeContextProviderMethod getAnnotation() {
        return annotation;
    }

    public final Method getMethod() {
        return method;
    }

    public String getPropertyName() {

        return method.getDeclaringClass().getCanonicalName() + "." + method.getName();

    }

}


package io.tracee.contextlogger.outputgenerator.predicates;

import io.tracee.contextlogger.contextprovider.api.ImplicitContextData;
import io.tracee.contextlogger.contextprovider.api.TraceeContextProvider;
import io.tracee.contextlogger.contextprovider.api.WrappedContextData;
import io.tracee.contextlogger.utility.TraceeContextLogAnnotationUtilities;

/**
 * Determines if the instance is a tracee context provider.
 */
public class IsTraceeContextProviderPredicate implements OutputElementTypePredicate {

    private static final IsTraceeContextProviderPredicate instance = new IsTraceeContextProviderPredicate();

    @Override
    public boolean apply(final Object instance) {
        return hasTraceeContextProviderAnnotation(instance) && isExtendingTraceeContextProvider(instance);
    }

    /**
     * Checks whether the passed instance type is annotated with the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation.
     *
     * @param instance the instance to check
     * @return true, if the passed instance's type is annotated with the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation,
     *         otherwise false
     */
    protected boolean hasTraceeContextProviderAnnotation(final Object instance) {

        if (instance == null) {
            return false;
        }

        TraceeContextProvider annotation = TraceeContextLogAnnotationUtilities.getAnnotationFromType(instance);
        return annotation != null;

    }

    /**
     * Checks whether the passed instance extends the {@link ImplicitContextData} or
     * {@link io.tracee.contextlogger.contextprovider.api.WrappedContextData}types.
     *
     * @param instance the instance to check
     * @return true, if the passed instance's type extends the ImplicitContextData or WrappedContextData class
     */
    protected boolean isExtendingTraceeContextProvider(final Object instance) {

        if (instance == null) {
            return false;
        }

        return ImplicitContextData.class.isAssignableFrom(instance.getClass()) || WrappedContextData.class.isAssignableFrom(instance.getClass());

    }

    public static IsTraceeContextProviderPredicate getInstance() {
        return instance;
    }

}


package io.tracee.contextlogger.utility;

import java.util.List;

/**
 * An utility class for typesafe casting of instances to {@link java.util.List}.
 * Created by Tobias Gindler on 21.03.14.
 */
public final class ListUtilities {

    private ListUtilities() {
    }

    /**
     * Checks if passed instance is of type {@link java.util.List} and contains elements of the passed type.
     * Warning: Only the first element of the list will be checked
     *
     * @param instance
     * @param type
     * @return
     */
    public static boolean isListOfType(Object instance, Class type) {

        if (instance == null || type == null) {
            return false;
        }

        if (List.class.isAssignableFrom(instance.getClass())) {

            // check whether the list is empty or the first instance is of passed type
            List list = (List) instance;
            boolean isEmpty = list.size() == 0;

            for (Object element : list) {
                if (element != null && !type.isAssignableFrom(element.getClass())) {
                    return false;
                }
            }

            return true;

        }

        return false;
    }

    /**
     * Check if the passed instance is a {@link java.util.List} and contains only elements of the passed element types.
     * In that case it casts the passed instance to a List of the passed element type.
     *
     * @param instance
     * @return The instance cast to a List of the passed element type if the passed instance is a list and only contains
     * element of the passed element type, otherwise it return null.
     */
    public static <T> List<T> getListOfType(Object instance, Class<T> elementType) {

        if (instance == null || elementType == null || !isListOfType(instance, elementType)) {
            return null;
        }

        try {
            return (List<T>) instance;
        } catch (ClassCastException e) {
            return null;
        }

    }

}


package io.tracee.contextlogger.contextprovider.api;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import javax.tools.Diagnostic;
import javax.tools.FileObject;
import javax.tools.StandardLocation;
import java.io.IOException;
import java.io.Writer;
import java.lang.annotation.Annotation;
import java.util.HashMap;
import java.util.Map;

/**
 * Base class for all Processors.
 */
public abstract class TraceeContextLoggerAbstractProcessor extends AbstractProcessor {

	private static Map<String, FileObjectWrapper> traceeProfileProperties = new HashMap<String, FileObjectWrapper>();

	protected Messager messager;
	protected Types typeUtils;
	protected Elements elementUtils;
	protected Filer filer;

	public static class FileObjectWrapper {
		private final FileObject fileObject;
		private final Writer foWriter;

		public FileObjectWrapper(FileObject fileObject) throws IOException {
			this.fileObject = fileObject;
			this.foWriter = fileObject.openWriter();
		}

		public void append(String content) throws IOException {
			foWriter.append(content);
			foWriter.flush();
		}
	}

	@Override
	public synchronized void init(ProcessingEnvironment processingEnv) {
		super.init(processingEnv);

		// create local references
		messager = processingEnv.getMessager();
		typeUtils = processingEnv.getTypeUtils();
		filer = processingEnv.getFiler();
		elementUtils = processingEnv.getElementUtils();

	}

	@Override
	public SourceVersion getSupportedSourceVersion() {
		return SourceVersion.latestSupported();
	}

	protected void error(Element e, String message, Object... args) {
		messager.printMessage(Diagnostic.Kind.ERROR, String.format(message, args), e);
	}

	protected void info(Element e, String message, Object... args) {
		messager.printMessage(Diagnostic.Kind.NOTE, String.format(message, args), e);
	}

	/**
	 * Central method to get cached FileObjectWrapper. Creates new FileObjectWrapper if it can't be found
	 */
	protected static synchronized FileObjectWrapper getOrCreateProfileProperties(final Filer filer, String fileName) throws IOException {

		FileObjectWrapper fileObject = traceeProfileProperties.get(fileName);

		if (fileObject == null) {

			fileObject = new FileObjectWrapper(filer.createResource(StandardLocation.SOURCE_OUTPUT, "", fileName, null));
			traceeProfileProperties.put(fileName, fileObject);

		}
		return fileObject;

	}

    protected <T extends Annotation> boolean isTypeAnnotatedWithAnnotation(TypeElement element, Class<T> annotation) {

        T contextProviderAnnotation = (T) element.getAnnotation(annotation);
        return contextProviderAnnotation != null;

    }

	protected static void clearCache() {
		traceeProfileProperties.clear();
	}

}


package io.tracee.contextlogger.contextprovider.api;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import java.util.List;

/**
 * Abstract base class for method based annotations like {@link Flatten} or  {@link TraceeContextProviderMethod}.
 */
public abstract class TraceeContextLoggerAbstractMethodAnnotationProcessor extends TraceeContextLoggerAbstractProcessor {

    /**
     * Checks if passed element is a method declared as public, not abstract and not static
     *
     * @param element the element to check
     * @return true if passed element is public, not abstract and not static method
     */
    protected boolean isValidMethod(Element element) {

        // must be of type class
        if (element.getKind() != ElementKind.METHOD) {
            error(element, "Element %s annotated with annotation %s must be a method", element.getSimpleName(),
                    TraceeContextProviderMethod.class.getSimpleName());
            return false;
        }

        // must be public
        if (!element.getModifiers().contains(Modifier.PUBLIC)) {
            error(element, "Method %s annotated with annotation %s must be public", element.getSimpleName(),
                    TraceeContextProviderMethod.class.getSimpleName());
            return false;
        }

        // must not abstract
        if (element.getModifiers().contains(Modifier.ABSTRACT)) {
            error(element, "Method %s annotated with annotation %s must not be abstract", element.getSimpleName(),
                    TraceeContextProviderMethod.class.getSimpleName());
            return false;
        }

        // must not be static
        if (element.getModifiers().contains(Modifier.STATIC)) {
            error(element, "Method %s annotated with annotation %s must not be static", element.getSimpleName(),
                    TraceeContextProviderMethod.class.getSimpleName());
            return false;
        }

        return true;
    }

    /**
     * Checks if passed element has a non void return type and takes no parameters
     *
     * @param executableElement
     * @return true if passed element has a non void return type and takes no parameters
     */
    protected boolean isGetterMethod(ExecutableElement executableElement) {

        // must have a return value
        TypeMirror returnTypeMirror = executableElement.getReturnType();

        if (returnTypeMirror.getKind().equals(TypeKind.VOID)) {
            error(executableElement, "method %s must have a non void return type", executableElement.getSimpleName().toString());
            return false;
        }

        // check if method takes no parameters
        List parameters = executableElement.getParameters();
        if (parameters != null && parameters.size() > 0) {
            error(executableElement, "method %s must have no parameters ", executableElement.getSimpleName().toString());
            return false;
        }

        return true;
    }

    /**
     * Checks if passed elements parent is type element and annotated with {@link TraceeContextProvider}.
     *
     * @param element
     * @return
     */
    protected boolean isParentAnnotatedWithTraceeContextProvider(final Element element) {

        Element parentElement = element.getEnclosingElement();
        if (parentElement == null || parentElement.getKind() != ElementKind.CLASS
                || !isTypeAnnotatedWithTraceeContextProvider((TypeElement) parentElement)) {

            error(element, "Enclosing type of method %s annotated with annotation %s is not annotated with annotation %s.", element.getSimpleName(),
                    Flatten.class.getSimpleName(), TraceeContextProvider.class.getSimpleName());
            return false;

        }

        return true;
    }

    protected boolean isTypeAnnotatedWithTraceeContextProvider(TypeElement element) {

        return isTypeAnnotatedWithAnnotation(element, TraceeContextProvider.class);

    }
}


package io.tracee.contextlogger.profile;

import io.tracee.contextlogger.contextprovider.ContextProviderServiceLoader;
import io.tracee.contextlogger.contextprovider.api.Profile;
import io.tracee.contextlogger.outputgenerator.api.TraceeContextStringRepresentationBuilder;

import java.util.Properties;

/**
 * This class combines profile settings with manual overrides. Manual overrides supersede profile settings.
 */
public class ProfileSettings {

	Properties profileProperties = null;

	private TraceeContextStringRepresentationBuilder toTraceeContextStringRepresentationBuilder = null;

	/**
	 * Main Constructor.
	 *
	 * @param toTraceeContextStringRepresentationBuilder a map that defines manual overrides for profile settings.
	 */
	public ProfileSettings(TraceeContextStringRepresentationBuilder toTraceeContextStringRepresentationBuilder) {
		this(toTraceeContextStringRepresentationBuilder != null && toTraceeContextStringRepresentationBuilder.getProfile() != null
				? toTraceeContextStringRepresentationBuilder.getProfile() : ProfileLookup.getCurrentProfile());

		this.toTraceeContextStringRepresentationBuilder = toTraceeContextStringRepresentationBuilder;

	}

	/**
	 * Constructor without manual overrides for convenience.
	 *
	 * @param profile the profile to use
	 */
	public ProfileSettings(Profile profile) {
		this.profileProperties = ContextProviderServiceLoader.getProfileSettings(profile);
	}

	public TraceeContextStringRepresentationBuilder getToTraceeContextStringRepresentationBuilder() {
		return toTraceeContextStringRepresentationBuilder;
	}

	/**
	 * Checks whether the property for the passed key is enabled or not.
	 *
	 * @param propertyKey the property key to check
	 * @return true, if the property is enabled in profile or manual overrides and not disabled in manual overrides, otherwise false
	 */
	public Boolean getPropertyValue(final String propertyKey) {

		if (propertyKey == null) {
			return null;
		}

		// check system property override
		if (toTraceeContextStringRepresentationBuilder != null && toTraceeContextStringRepresentationBuilder.getManualContextOverrides() != null) {
			Boolean manualOverrideCheck = toTraceeContextStringRepresentationBuilder.getManualContextOverrides().get(propertyKey);
			if (manualOverrideCheck != null) {
				return manualOverrideCheck;
			}
		}

		// check profile properties
		if (profileProperties != null) {
			String value = profileProperties.getProperty(propertyKey);
			if (value != null) {
				return Boolean.valueOf(value);
			}

		}

		return null;
	}

}


package io.tracee.contextlogger.contextprovider.api;

import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;

/**
 * Abstract base class for type based annotations like {@link TraceeContextProvider}.
 */
public abstract class TraceeContextLoggerAbstractTypeAnnotationProcessor extends TraceeContextLoggerAbstractProcessor {

	protected boolean isValidClass(Element element) {

		// must be of type class
		if (element.getKind() != ElementKind.CLASS) {
			error(element, "Element %s annotated with annotation %s must be a class", element.getSimpleName(),
					TraceeContextProviderMethod.class.getSimpleName());
			return false;
		}

		// must be public
		if (!element.getModifiers().contains(Modifier.PUBLIC)) {
			error(element, "Element %s annotated with annotation %s must be declared as public", element.getSimpleName(),
					TraceeContextProviderMethod.class.getSimpleName());
			return false;
		}

		// must not abstract
		if (element.getModifiers().contains(Modifier.ABSTRACT)) {
			error(element, "Element %s annotated with annotation %s must not be a abstract", element.getSimpleName(),
					TraceeContextProviderMethod.class.getSimpleName());
			return false;
		}

		return true;
	}

	protected boolean checkIfTypeElementIsAssignableToType(TypeElement typeElement, Class type) {

		return typeUtils.isAssignable(typeElement.asType(), elementUtils.getTypeElement(type.getCanonicalName()).asType());

	}

    /**
     * Checks if class has noargs constructor or default noargs constructor.
     *
     * @param typeElement the type element to check.
     * @return true if element has noarg constructor or no constructor at all
     */
    protected boolean checkIfClassHasNoargsConstructor(TypeElement typeElement) {

        // check if annotated class has noargs constructor
        boolean foundConstructor = false;
        boolean foundNoargsConstructor = false;

        for (Element child : typeElement.getEnclosedElements()) {
            if (ElementKind.CONSTRUCTOR.equals(child.getKind())) {
                foundConstructor = true;
                ExecutableElement constructor = (ExecutableElement) child;
                if (constructor.getParameters().size() == 0) {
                    foundNoargsConstructor = true;
                    break;
                }
            }
        }
        return !(foundConstructor && !foundNoargsConstructor);

    }

}


package io.tracee.contextlogger.utility;

import io.tracee.contextlogger.contextprovider.api.Flatten;
import io.tracee.contextlogger.contextprovider.api.TraceeContextProvider;
import io.tracee.contextlogger.contextprovider.api.TraceeContextProviderMethod;
import io.tracee.contextlogger.impl.MethodAnnotationPair;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * Utility functions for getting Annotations from Annotated classes and methods.
 * Created by Tobias Gindler, holisticon AG on 14.03.14.
 */
public final class TraceeContextLogAnnotationUtilities {

    @SuppressWarnings("unused")
    private TraceeContextLogAnnotationUtilities() {
        // hide constructor
    }

    /**
     * gets the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation of passed instance.
     *
     * @param instance the instance to get the annotation from
     * @return the {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProvider} annotation if present, otherwise null
     */
    public static TraceeContextProvider getAnnotationFromType(final Object instance) {
        return getAnnotationFromType(instance, TraceeContextProvider.class);
    }

    /**
     * Gets an annotation of passed instance.
     *
     * @param instance he instance to get the annotation from
     * @param <T>      the annotations class to get
     * @return the annotation if present, otherwise null
     */
    public static <T extends Annotation> T getAnnotationFromType(final Object instance, Class<T> annotation) {
        if (instance == null || annotation == null) {
            return null;
        }
        return instance.getClass().getAnnotation(annotation);
    }

    /**
     * Extracts all {@link io.tracee.contextlogger.contextprovider.api.TraceeContextProviderMethod} annotated methods of the passed instance.
     *
     * @param instance the instance to check for
     * @return a list containing all annotated methods of an instance
     */
    public static List<MethodAnnotationPair> getAnnotatedMethodsOfInstance(final Object instance) {

        List<MethodAnnotationPair> result = new ArrayList<MethodAnnotationPair>();

        for (Method method : instance.getClass().getDeclaredMethods()) {

            if (checkIsPublic(method) && checkMethodHasNoParameters(method) && checkMethodHasNonVoidReturnType(method)) {

                TraceeContextProviderMethod annotation = method.getAnnotation(TraceeContextProviderMethod.class);

                // check if method has no parameters and is annotated
                if (annotation != null) {
                    result.add(new MethodAnnotationPair(instance.getClass(), method, annotation));
                }

            }

        }

        return result;

    }

    /**
     * Checks whether the passsed method has no parameters.
     *
     * @param method the method to check
     * @return true if the method has no parameters, otherwise false
     */
    public static boolean checkMethodHasNoParameters(final Method method) {

        return method == null || method.getParameterTypes().length == 0;

    }

    /**
     * Checks whether the passsed method has a non void return value.
     *
     * @param method the method to check
     * @return true if the method has a non void return type, otherwise false
     */
    public static boolean checkMethodHasNonVoidReturnType(final Method method) {

        if (method == null) {
            return false;
        }
        try {

            return !(Void.TYPE == method.getReturnType());

        } catch (Exception e) {
            return false;
        }

    }

    public static boolean checkIsPublic(final Method method) {
        return method != null && Modifier.isPublic(method.getModifiers());
    }

    public static boolean isFlatable(final Method method) {

        return method != null && method.getAnnotation(Flatten.class) != null;

    }

}


package io.tracee.contextlogger.outputgenerator.utility;

import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.Set;

import io.tracee.contextlogger.utility.reflection.IsGetterMethodPredicate;

/**
 * Utility class containing common functions to process beans.
 */
public class BeanUtility {

    /**
     * Gets all getter methods recursively (for all superclasses).
     *
     * @param type the type to get the methods for
     * @return the available getter methods
     */
    public static Set<Method> getGetterMethodsRecursively(final Class type) {

        Set<Method> getterMethods = new HashSet<Method>();

        if (type != null) {

            Method[] methods = null;

            try {
                methods = type.getDeclaredMethods();
            }
            catch (SecurityException e) {
                // use fallback - checks against fields might not work
                methods = type.getMethods();
            }

            for (Method method : methods) {

                if (IsGetterMethodPredicate.check(type, method)) {
                    getterMethods.add(method);
                }

            }

            // process supertype
            getterMethods.addAll(getGetterMethodsRecursively(type.getSuperclass()));

        }

        return getterMethods;
    }

}


package io.tracee.contextlogger.outputgenerator.writer.function;

import io.tracee.contextlogger.contextprovider.api.TraceeContextProvider;
import io.tracee.contextlogger.outputgenerator.outputelements.OutputElement;
import io.tracee.contextlogger.outputgenerator.outputelements.OutputElementType;
import io.tracee.contextlogger.outputgenerator.writer.api.OutputStyle;
import io.tracee.contextlogger.utility.TraceeContextLogAnnotationUtilities;

/**
 * Function that provides a type definition. Used for complex and collection types.
 */
public class TypeProviderFunction {

    public static final String OUTPUT_STRING_TYPE = "<TYPE>";

    private static final TypeProviderFunction INSTANCE = new TypeProviderFunction();

    /**
     * Write the type string to the Stringbuilder
     *
     * @param stringBuilder the stringbuilder to write to
     * @param outputStyle   the output style to use
     * @param outputElement the output element to write the type string for.
     * @return true, if any output was written to the stringbuilder, otherwise false.
     */
    public boolean apply(final StringBuilder stringBuilder, final OutputStyle outputStyle, final OutputElement outputElement) {

        boolean result = false;

        if (outputElement != null) {

            if (OutputElementType.COLLECTION.equals(outputElement.getOutputElementType())) {
                result = handleCollectionType(stringBuilder, outputStyle, outputElement);
            } else if (OutputElementType.COMPLEX.equals(outputElement.getOutputElementType())) {

                if (TraceeContextLogAnnotationUtilities.getAnnotationFromType(outputElement.getEncapsulatedInstance()) != null) {
                    result = handleTraceeContextprovider(stringBuilder, outputStyle, outputElement);
                } else {
                    result = handleComplexType(stringBuilder, outputStyle, outputElement);
                }

            }

        }

        return result;
    }

    boolean handleCollectionType(final StringBuilder stringBuilder, final OutputStyle outputStyle, final OutputElement outputElement) {

        String tmpTypeString = OUTPUT_STRING_TYPE + ":" + outputElement.getOutputElementsBaseType().getSimpleName();
        if (outputElement.getIsAsMarkedAsMultipleReferenced()) {
            tmpTypeString = tmpTypeString + "@" + outputElement.getIdentityHashCode();
        }

        stringBuilder.append(outputStyle.openingAtomicType());
        stringBuilder.append(outputStyle.escapeString(tmpTypeString));
        stringBuilder.append(outputStyle.closingAtomicType());

        return true;
    }

    boolean handleComplexType(final StringBuilder stringBuilder, final OutputStyle outputStyle, final OutputElement outputElement) {
        createComplexOutput(stringBuilder, outputStyle, outputElement, outputElement.getOutputElementsBaseType().getSimpleName());
        return true;
    }

    boolean handleTraceeContextprovider(final StringBuilder stringBuilder, final OutputStyle outputStyle, final OutputElement outputElement) {

        String typeString = outputElement.getOutputElementsBaseType().getSimpleName();

        TraceeContextProvider traceeContextProviderAnnotation = TraceeContextLogAnnotationUtilities.getAnnotationFromType(outputElement
                .getEncapsulatedInstance());
        if (traceeContextProviderAnnotation != null) {
            if (traceeContextProviderAnnotation.suppressTypeInOutput()) {
                return false;
            }
            typeString = traceeContextProviderAnnotation.displayName();
        }

        createComplexOutput(stringBuilder, outputStyle, outputElement, typeString);

        return true;
    }

    void createComplexOutput(final StringBuilder stringBuilder, final OutputStyle outputStyle, final OutputElement outputElement, final String typeString) {

        String tmpTypeString = typeString;
        if (outputElement.getIsAsMarkedAsMultipleReferenced()) {
            tmpTypeString = tmpTypeString + "@" + outputElement.getIdentityHashCode();
        }

        stringBuilder.append(outputStyle.complexTypeOpeningName());
        stringBuilder.append(outputStyle.escapeString(OUTPUT_STRING_TYPE));
        stringBuilder.append(outputStyle.complexTypeClosingName());
        stringBuilder.append(outputStyle.complexTypeNameValueSeparator());
        stringBuilder.append(outputStyle.openingAtomicType());
        stringBuilder.append(outputStyle.escapeString(tmpTypeString));
        stringBuilder.append(outputStyle.closingAtomicType());

    }

    public static TypeProviderFunction getInstance() {
        return INSTANCE;
    }

}


package io.tracee.contextlogger.contextprovider;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.tracee.contextlogger.contextprovider.api.ImplicitContextData;
import io.tracee.contextlogger.contextprovider.api.WrappedContextData;

/**
 * Class to store class to wrapper mappings.
 */
public final class TypeToWrapper {

    private static List<TypeToWrapper> typeToWrapperList;
    private static final Logger logger = LoggerFactory.getLogger(TypeToWrapper.class);

    private final Class wrappedInstanceType;
    private final Class wrapperType;

    public TypeToWrapper(final Class wrappedInstanceType, final Class wrapperType) {
        this.wrappedInstanceType = wrappedInstanceType;
        this.wrapperType = wrapperType;
    }

    public Class getWrappedInstanceType() {
        return wrappedInstanceType;
    }

    public Class getWrapperType() {
        return wrapperType;
    }

    public static Set<Class> getAllWrappedClasses() {

        final List<TypeToWrapper> localTypeToWrapperList = getTypeToWrapper();

        Set<Class> resultList = new HashSet<Class>();

        if (localTypeToWrapperList != null) {
            for (TypeToWrapper typeToWrapper : localTypeToWrapperList) {
                resultList.add(typeToWrapper.getWrappedInstanceType());
            }
        }

        return resultList;

    }

    public static List<TypeToWrapper> getTypeToWrapper() {
        if (typeToWrapperList == null) {
            typeToWrapperList = getAvailableWrappers();
        }
        return typeToWrapperList;
    }

    /**
     * Gets a list with all wrapper classes.
     *
     * @return a list with all wrapper classes
     */
    public static Set<Class> findWrapperClasses() {

        final List<TypeToWrapper> localTypeToWrapperList = getTypeToWrapper();

        Set<Class> resultList = new HashSet<Class>();

        if (localTypeToWrapperList != null) {
            for (TypeToWrapper typeToWrapper : localTypeToWrapperList) {
                resultList.add(typeToWrapper.getWrapperType());
            }
        }

        return resultList;

    }

    /**
     * Generic function to get a implicit data provider classes from resource files.
     *
     * @return a set that contains all context provider type the were found
     */
    public static Set<ImplicitContextData> getImplicitContextDataProviders() {
        final Set<ImplicitContextData> result = new HashSet<ImplicitContextData>();

        for (Class clazz : ContextProviderServiceLoader.getServiceLocator().getImplicitContextProvider()) {

            try {
                if (ImplicitContextData.class.isAssignableFrom(clazz)) {
                    ImplicitContextData instance = (ImplicitContextData)(clazz.newInstance());
                    result.add(instance);
                }
            }
            catch (Throwable e) {
                // to be ignored
            }

        }

        return result;
    }

    /**
     * Method to get all available wrappers.
     *
     * @return all wrapping between wrapper classes and their wrapped types.
     */
    public static List<TypeToWrapper> getAvailableWrappers() {

        final List<TypeToWrapper> result = new ArrayList<TypeToWrapper>();

        for (Class clazz : ContextProviderServiceLoader.getServiceLocator().getContextProvider()) {

            try {
                if (WrappedContextData.class.isAssignableFrom(clazz)) {
                    // try to create instance to get the wrapped type
                    final WrappedContextData instance = (WrappedContextData)clazz.newInstance();
                    result.add(new TypeToWrapper(instance.getWrappedType(), clazz));
                }
            }
            catch (Throwable e) {
                // to be ignored
            }

        }

        return result;
    }

    private static void logError(final String message, final Throwable e) {
        logger.error(message, e);
    }

    private static void logWarn(final String message) {
        logger.warn(message);
    }
}


package io.tracee.contextlogger;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.tracee.contextlogger.connector.Connector;
import io.tracee.contextlogger.connector.ConnectorOutputProvider;
import io.tracee.contextlogger.connector.LogConnector;
import io.tracee.contextlogger.connector.WellKnownConnectorClassNames;

/**
 * Class to pipe messages to all configured connectors.
 * Created by Tobias Gindler, holisticon AG on 26.03.14.
 */
class ConnectorFactory {

    // Connector settings
    private static final Pattern KEY_MATCHER_PATTERN = Pattern.compile(TraceeContextLoggerConstants.SYSTEM_PROPERTY_CONTEXT_LOGGER_CONNECTOR_KEY_PATTERN);
    private static final String CONNECTOR_PROPERTY_GRABBER_PATTERN = TraceeContextLoggerConstants.SYSTEM_PROPERTY_CONNECTOR_PREFIX.replaceAll("\\.", "\\.")
            + "%s\\.(.*)";
    private static final Logger LOGGER = LoggerFactory.getLogger(TraceeContextLogger.class);
    private static final Map<String, String> WELL_KNOW_CONNECTOR_MAPPINGS = new HashMap<String, String>();

    static {
        WELL_KNOW_CONNECTOR_MAPPINGS.put("HttpConnector", WellKnownConnectorClassNames.HTTP_CONNECTOR);
        WELL_KNOW_CONNECTOR_MAPPINGS.put(LogConnector.class.getName(), LogConnector.class.getCanonicalName());
    }

    // Connector
    private final Map<String, Connector> connectorMap = new HashMap<String, Connector>();

    ConnectorFactory() {
        initConnectors();
    }

    /**
     * Initializes all available connectors.
     */
    private void initConnectors() {

        // first get all connector configuration Names
        Set<String> connectorConfigurationNames = this.getConnectorConfigurationNames();

        for (String connectorConfigurationName : connectorConfigurationNames) {

            Connector connector = this.createConnector(connectorConfigurationName);

            if (connector != null) {

                this.connectorMap.put(connectorConfigurationName, connector);

            }

        }

        // Add mandatory logger
        if (!isConnectorConfigured(LogConnector.class)) {
            Connector logConnector = new LogConnector();
            this.connectorMap.put("LOGGER", logConnector);
        }

    }

    /**
     * Send error report to all initialized connector instances.
     *
     * @param connectorOutputProvider the context data provider used for output
     */
    final void sendErrorReportToConnectors(ConnectorOutputProvider connectorOutputProvider) {

        for (Connector connector : this.connectorMap.values()) {

            connector.sendErrorReport(connectorOutputProvider);

        }
    }

    /**
     * Extracts all names for connector configurations from System properties.
     *
     * @return a Set containing all connector configuration names
     */
    final Set<String> getConnectorConfigurationNames() {

        Set<String> connectorNames = new HashSet<String>();

        Enumeration<Object> keyEnumeration = getSystemProperties().keys();
        while (keyEnumeration.hasMoreElements()) {
            String key = keyEnumeration.nextElement().toString();

            // check if property key has tracee connector format
            Matcher matcher = KEY_MATCHER_PATTERN.matcher(key);
            if (matcher.matches() && matcher.groupCount() > 0) {

                connectorNames.add(matcher.group(1));

            }

        }

        return connectorNames;

    }

    /**
     * Collects all properties for a given connector configuration name and writes them to a Map.
     *
     * @param connectorName the name of the connector configuration
     * @return a Map containing all properties for a connector configuration name
     */
    final Map<String, String> getPropertiesForConnectorConfigurationName(final String connectorName) {

        final Map<String, String> propertyMap = new HashMap<String, String>();

        final String patternString = String.format(CONNECTOR_PROPERTY_GRABBER_PATTERN, connectorName);
        final Pattern propertyGrabPattern = Pattern.compile(patternString);

        final Set<Map.Entry<Object, Object>> entries = getSystemProperties().entrySet();

        for (Map.Entry<Object, Object> entry : entries) {
            final String key = entry.getKey().toString();
            final Object value = entry.getValue();

            // check if property key has tracee connector format
            final Matcher matcher = propertyGrabPattern.matcher(key);
            if (value != null && matcher.matches() && matcher.groupCount() > 0) {

                final String propertyName = matcher.group(1);

                propertyMap.put(propertyName, value.toString());

            }

        }

        return propertyMap;

    }

    /**
     * Tries to create a Connector for a given connector configuration name.
     *
     * @param connectorConfigurationName the name of the connector configuration
     * @return the connector if it could be created and initialized without error, otherwise null
     */
    final Connector createConnector(final String connectorConfigurationName) {

        Map<String, String> propertyMap = this.getPropertiesForConnectorConfigurationName(connectorConfigurationName);
        String type = propertyMap.get(TraceeContextLoggerConstants.SYSTEM_PROPERTY_CONTEXT_LOGGER_CONNECTOR_TYPE);

        // get canonical class name for well known connectors
        if (WELL_KNOW_CONNECTOR_MAPPINGS.containsKey(type)) {
            type = WELL_KNOW_CONNECTOR_MAPPINGS.get(type);
        }

        try {

            // try to create connector instance
            Connector connector = (Connector)Class.forName(type).newInstance();

            // now try to call init method
            connector.init(propertyMap);

            return connector;

        }
        catch (Exception e) {
            LOGGER.error("An error occurred while creating connector with name '" + connectorConfigurationName + "' of type '" + type + "'", e);
        }

        return null;
    }

    /**
     * Checks whether the LogConnector is defined or not.
     *
     * @param connectorClass the connector to check for
     * @return true, if LogConnector is already defined, otherwise false.
     */
    private boolean isConnectorConfigured(Class connectorClass) {
        for (Connector connector : this.connectorMap.values()) {

            if (connectorClass.isInstance(connector)) {
                return true;
            }

        }

        return false;
    }

    protected Properties getSystemProperties() {
        return System.getProperties();
    }

}


package io.tracee.contextlogger.utility.reflection;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;

import io.tracee.contextlogger.utility.GetterUtilities;

/**
 * Predicated to check if passed method is a getter.
 * Getter methods should reside in the same Class as their fields and should not be overwritten.
 */
public class IsGetterMethodPredicate {

    // possible prefixes for getter methods
    public static final String[] GETTER_PREFIXES = { "get", "is", "has" };

    private static final IsGetterMethodPredicate instance = new IsGetterMethodPredicate();

    public static boolean check(Class type, Method method) {
        return instance.apply(type, method);
    }

    public boolean apply(Class type, Method method) {

        // make it null safe
        if (method == null || type == null) {
            return false;
        }

        // check if method matches naming conventions
        if (!hasGetterPrefixInMethodName(method)) {
            return false;
        }

        // check whether the getter method is public, non-static, takes no parameters and has a return type
        if (!hasNoParameters(method) || !hasReturnValue(method) || !isPublicNonStaticMethod(method)) {
            return false;
        }

        // check if corresponding field exists
        try {

            if (!correspondingFieldExists(type, method) || !hasCompatibleReturnTypes(type, method)) {

                return false;

            }

        }
        catch (SecurityException e) {
            // ignore
        }

        return true;
    }

    /**
     * Getter method names must have a specific prefix.
     *
     * @param method the method to check
     * @return true, if the method name prefix is a getter Prefix, otherwise false
     */
    boolean hasGetterPrefixInMethodName(Method method) {

        String methodName = method.getName();
        if (methodName != null) {

            for (String prefix : GETTER_PREFIXES) {

                if (methodName.startsWith(prefix)) {
                    return true;
                }
            }

        }
        return false;
    }

    /**
     * Getter methods must be public and not be static.
     *
     * @param method the method to check
     * @return true if the method is not static and public, otherwise false
     */
    boolean isPublicNonStaticMethod(final Method method) {
        int modifiers = method.getModifiers();
        return !Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers);
    }

    /**
     * Getter methods take no parameters.
     *
     * @param method the method to check
     * @return true, if the method takes no parameters, otherwise false.
     */
    boolean hasNoParameters(final Method method) {
        return method.getParameterTypes().length == 0;
    }

    /**
     * Getter methods must have return type (must not be of type Void)
     *
     * @param method the method to check
     * @return true if the method has a return type, otherwise false
     */
    boolean hasReturnValue(final Method method) {
        Class returnType = method.getReturnType();
        return !Void.TYPE.equals(returnType);
    }

    /**
     * Getter method should have a corresponding field.
     *
     * @param type the type to search the field in
     * @param method the method to check
     * @return true, if a field for passed getter method exists, otherwise false
     */
    boolean correspondingFieldExists(Class type, Method method) {
        return getCorrespondingField(type, method) != null;
    }

    /**
     * Getter method and field must have compatible fields.
     *
     * @param type the type to search the field in
     * @param method the method to check
     * @return true, if field type is assignable to methods return type
     */
    boolean hasCompatibleReturnTypes(Class type, Method method) {

        Field correspondingField = getCorrespondingField(type, method);

        return correspondingField != null && method.getReturnType().isAssignableFrom(correspondingField.getType());

    }

    /**
     * Helper method for getting a corresponsing field for a method
     *
     * @param type the type to search the field in
     * @param method the getter method to search the field for
     * @return the Field or null if the field for the passed method doesn't exists
     */
    Field getCorrespondingField(Class type, Method method) {

        try {
            return type.getDeclaredField(GetterUtilities.getFieldName(method));
        }
        catch (NoSuchFieldException e) {
            return null;
        }

    }

}


package io.tracee.contextlogger.utility;

import java.lang.reflect.Method;

/**
 * Utility class for evaluating and processing getter methods.
 */
public class GetterUtilities {

    // possible prefixes for getter methods
    public static final String[] GETTER_PREFIXES = { "get", "is", "has" };

    /**
     * Checks whether the method name starts with a getter prefix.
     *
     * @param method the method to check for
     * @return true if the method name starts with eiher one of values stored in GETTER_PREFIXES
     */
    public static boolean isGetterMethod(final Method method) {
        if (method != null) {
            return isGetterMethod(method.getName());
        }
        else {
            return false;
        }
    }

    /**
     * Checks whether the method name starts with a getter prefix.
     *
     * @param methodName the method name to check for
     * @return true if the method name starts with eiher one of values stored in GETTER_PREFIXES
     */
    public static boolean isGetterMethod(final String methodName) {

        if (methodName != null) {

            for (String prefix : GETTER_PREFIXES) {
                if (methodName.startsWith(prefix)) {
                    return true;
                }
            }

        }

        return false;
    }

    /**
     * Strips getter prefix from method name.
     *
     * @param methodName the name of the method to be processed
     * @return the stripped method name with Decapitalized first letter or null if passed method name has no getter prefix.
     */
    public static String getFieldName(final String methodName) {

        if (isGetterMethod(methodName)) {
            return decapitalizeFirstCharOfString(stripGetterPrefix(methodName));
        }

        return null;
    }

    /**
     * Strips getter prefix from method name.
     *
     * @param method the method to be processed
     * @return the stripped method name with Decapitalized first letter or null if passed method is null or if the method name has no getter prefix.
     */
    public static String getFieldName(final Method method) {
        if (method != null) {
            return getFieldName(method.getName());
        }
        else {
            return null;
        }
    }

    /**
     * Gets the full qualified name of a field or non getter method.
     * Result has following format : "Full Qualified Classname"."field name for getter methods or method name for non getter methods".
     *
     * @param method The method to be processed
     * @return
     */
    public static String getFullQualifiedFieldName(final Class type, final Method method) {

        if (method != null) {

            // get the name of the type from passed type or method
            final String typeName;
            if (type != null) {
                typeName = type.getCanonicalName();
            }
            else {
                typeName = method.getDeclaringClass().getCanonicalName();
            }

            // get field name from getter method or method name for non getter methods
            final String fieldName;
            if (isGetterMethod(method.getName())) {
                fieldName = getFieldName(method.getName());
            }
            else {
                fieldName = method.getName();
            }

            return typeName + "." + fieldName;
        }
        else {
            return null;
        }

    }

    /**
     * Capitalizes first char of an input string.
     *
     * @param input the string to be processed;
     * @return The input string with the first capitalized char. Returns empty string, if passed input String is null or empty.
     */
    static String capitalizeFirstCharOfString(final String input) {
        if (input == null || input.length() == 0) {
            return "";
        }
        else if (input.length() == 1) {
            return input.toUpperCase();
        }
        else {
            return input.substring(0, 1).toUpperCase() + input.substring(1);
        }
    }

    /**
     * Decapitalizes first char of an input string.
     *
     * @param input the string to be processed;
     * @return The input string with the first capitalized char. Returns empty string, if passed input String is null or empty.
     */
    static String decapitalizeFirstCharOfString(final String input) {
        if (input == null || input.length() == 0) {
            return "";
        }
        else if (input.length() == 1) {
            return input.toLowerCase();
        }
        else {
            return input.substring(0, 1).toLowerCase() + input.substring(1);
        }
    }

    /**
     * Strips getter prefix from input string
     *
     * @param input the input string to be processed
     * @return the string stripped by getter prefix
     */
    static String stripGetterPrefix(final String input) {

        if (input != null) {
            for (String prefix : GETTER_PREFIXES) {
                if (input.startsWith(prefix)) {
                    return input.substring(prefix.length());
                }
            }
        }

        return input;
    }

}