/*
 * This file is part of Flow Noise, licensed under the MIT License (MIT).
 *
 * Copyright (c) 2013 Flow Powered <https://flowpowered.com/>
 * Original libnoise C++ library by Jason Bevins <http://libnoise.sourceforge.net/>
 * jlibnoise Java port by Garrett Fleenor <https://github.com/RoyAwesome/jlibnoise>
 * Flow Noise is re-licensed with permission from jlibnoise author.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package com.flowpowered.noise.module.modifier;

import com.flowpowered.noise.exception.NoModuleException;
import com.flowpowered.noise.module.Module;

/**
 * A modifier module to map a value from one range to another.
 * For example: 0.5 (-1 < x < 1) -> 0.75 (0 < x < 1)
 */
public class Range extends Module {

    public static final double DEFAULT_CURRENT_LOWER_BOUND = -1f;
    public static final double DEFAULT_CURRENT_UPPER_BOUND = 1f;
    public static final double DEFAULT_NEW_LOWER_BOUND = 0f;
    public static final double DEFAULT_NEW_UPPER_BOUND = 1f;

    /* Current lower bound */
    private double currentLowerBound = DEFAULT_CURRENT_LOWER_BOUND;
    /* Current upper bound */
    private double currentUpperBound = DEFAULT_CURRENT_UPPER_BOUND;
    /* New lower bound */
    private double newLowerBound = DEFAULT_NEW_LOWER_BOUND;
    /* New Upper Bound */
    private double newUpperBound = DEFAULT_NEW_UPPER_BOUND;
    /* Cache variables */
    private double scale;
    private double bias;

    public Range() {
        super(1);
    }

    public double getCurrentLowerBound() {
        return currentLowerBound;
    }

    public double getCurrentUpperBound() {
        return currentUpperBound;
    }

    public double getNewLowerBound() {
        return newLowerBound;
    }

    public double getNewUpperBound() {
        return newUpperBound;
    }

    /*
     * Calculate the scale and biased to be a applied during Range#getValue(int x, int y, int z)
     * Should be called when the bounds are modified
     */
    private void recalculateScaleBias() {
    	scale = (getNewUpperBound() - getNewLowerBound()) /
        		(getCurrentUpperBound() - getCurrentLowerBound());
    	bias = getNewLowerBound() - getCurrentLowerBound() * scale;
    }

    /**
     * Configure bounds for range module
     * @param currentLower current lower bound
     * @param currentUpper current upper bound
     * @param newLower new lower bound
     * @param newUpper new upper bound
     */
    public void setBounds(double currentLower, double currentUpper, double newLower,
    		double newUpper) {
    	if (currentLower == currentUpper) {
    		throw new IllegalArgumentException("currentLower must not equal currentUpper. Both are " + currentUpper);
    	}
    	if (newLower == newUpper) {
    		throw new IllegalArgumentException("newLowerBound must not equal newUpperBound. Both are " + newUpper);
    	}
    	currentLowerBound = currentLower;
    	currentUpperBound = currentUpper;
    	newLowerBound = newLower;
    	newUpperBound = newUpper;
    	recalculateScaleBias();
    }

    @Override
    public int getSourceModuleCount() {
        return 1;
    }

    @Override
    public double getValue(double x, double y, double z) {
        if (sourceModule == null) {
            throw new NoModuleException();
        }
        if (sourceModule[0] == null) {
            throw new NoModuleException();
        }

        double oldVal = sourceModule[0].getValue(x, y, z);
        return oldVal * scale + bias;
    }

}


package org.zalando.stups.fullstop;

import com.amazonaws.services.cloudtrail.processinglibrary.exceptions.CallbackException;
import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.EventsProcessor;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEvent;
import com.netflix.hystrix.exception.HystrixRuntimeException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.plugin.core.PluginRegistry;
import org.springframework.stereotype.Component;
import org.springframework.web.client.HttpServerErrorException;
import org.zalando.stups.fullstop.plugin.FullstopPlugin;

import java.util.List;

/**
 * Simple {@link EventsProcessor} that delegates to {@link FullstopPlugin}s that can procces the {@link CloudTrailEvent}.
 */
@Component
public class PluginEventsProcessor implements EventsProcessor {

    private final Logger log = LoggerFactory.getLogger(PluginEventsProcessor.class);

    private final PluginRegistry<FullstopPlugin, CloudTrailEvent> fullstopPluginRegistry;

    @Autowired
    public PluginEventsProcessor(final PluginRegistry<FullstopPlugin, CloudTrailEvent> fullstopPluginRegistry) {
        this.fullstopPluginRegistry = fullstopPluginRegistry;
    }

    @Override
    public void process(final List<CloudTrailEvent> events) throws CallbackException {
        events.forEach(this::doProcess);
    }

    /**
     * Processes an single event by looping available plugins.
     *
     * @see #doProcess(CloudTrailEvent, FullstopPlugin)
     */
    protected void doProcess(final CloudTrailEvent event) {
        for (final FullstopPlugin plugin : getPluginsForEvent(event)) {
            doProcess(event, plugin);
        }
    }

    /**
     * Processes a specific event on specified plugin.
     */
    protected void doProcess(final CloudTrailEvent event, final FullstopPlugin plugin) {
        try {
            plugin.processEvent(event);
        } catch (HystrixRuntimeException | HttpServerErrorException e) {
            log.warn(e.getMessage(), e);
        } catch (final Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    /**
     * Returns a list of plugins configured.
     *
     * @return list of plugins configured
     */
    protected List<FullstopPlugin> getPluginsForEvent(final CloudTrailEvent event) {
        return this.fullstopPluginRegistry.getPluginsFor(event);
    }

}


package org.zalando.stups.fullstop.events;

import com.amazonaws.regions.Region;
import com.amazonaws.regions.Regions;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEvent;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEventData;
import com.amazonaws.services.cloudtrail.processinglibrary.model.internal.UserIdentity;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.PathNotFoundException;
import net.minidev.json.JSONArray;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.zalando.stups.fullstop.violation.ViolationBuilder;

import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.collect.Lists.newArrayList;
import static com.google.common.collect.Sets.newLinkedHashSet;
import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static org.slf4j.LoggerFactory.getLogger;

/**
 * @author jbellmann
 */
public abstract class CloudTrailEventSupport {

    private static final Logger LOG = getLogger(CloudTrailEventSupport.class);

    public static final String INSTANCE_ID_JSON_PATH = "$.instancesSet.items[*].instanceId";

    public static final String INSTANCE_JSON_PATH = "$.instancesSet.items[*]";

    public static final String RUN_INSTANCE_DATE_JSON_PATH = "$.launchTime";

    public static final String SECURITY_GROUP_IDS_JSON_PATH = "$.groupSet.items[*].groupId";
    public static final String INSTANCES_SECURITY_GROUP_IDS_JSON_PATH = "$.instancesSet.items[*].groupSet.items[*].groupId";

    public static final String INSTANCE_LAUNCH_TIME = "$.instancesSet.items[*].launchTime";

    private static final String ACCOUNT_ID_OR_RECIPIENT_SHOULD_NEVER_BE_NULL = "AccountId or RecipientAccountId should never be null";

    private static final String USER_IDENTITY_SHOULD_NEVER_BE_NULL = "UserIdentity should never be null";

    private static final String CLOUD_TRAIL_EVENT_DATA_SHOULD_NEVER_BE_NULL =
            "CloudTrailEventData should never be null";

    private static final String CLOUD_TRAIL_EVENT_SHOULD_NEVER_BE_NULL = "CloudTrailEvent should never be null";

    /**
     * Extracts list of instanceIds from {@link CloudTrailEvent}s 'responseElements'.
     */
    public static List<String> getInstanceIds(final CloudTrailEvent event) {

        final CloudTrailEventData eventData = getEventData(event);

        final String responseElements = eventData.getResponseElements();
        if (isNullOrEmpty(responseElements)) {
            return newArrayList();
        }

        return read(responseElements, INSTANCE_ID_JSON_PATH);
    }

    public static String getEventId(final CloudTrailEvent event) {
        return event.getEventData().getEventId().toString();
    }

    /**
     * Extracts the 'accountId'.
     */
    public static String getAccountId(final CloudTrailEvent event) {
        final CloudTrailEventData eventData = getEventData(event);
        final UserIdentity userIdentity = checkNotNull(eventData.getUserIdentity(), USER_IDENTITY_SHOULD_NEVER_BE_NULL);
        final String value = ofNullable(userIdentity.getAccountId()).orElse(eventData.getRecipientAccountId());
        return checkNotNull(value, ACCOUNT_ID_OR_RECIPIENT_SHOULD_NEVER_BE_NULL);
    }

    private static CloudTrailEventData getEventData(CloudTrailEvent event) {
        event = checkNotNull(event, CLOUD_TRAIL_EVENT_SHOULD_NEVER_BE_NULL);

        return checkNotNull(event.getEventData(), CLOUD_TRAIL_EVENT_DATA_SHOULD_NEVER_BE_NULL);
    }

    /**
     * Reads the given 'responseElements' and extracts information based on given 'pattern'.

     * If 'responseElements' is null or empty you can handle the {@link IllegalArgumentException} raised or got an empty
     * list.
     */
    public static List<String> read(final String responseElements, final String pattern,
                                    final boolean emptyListOnNullOrEmptyResponse) {
        if (isNullOrEmpty(responseElements) && emptyListOnNullOrEmptyResponse) {
            return emptyList();
        }

        try {
            return JsonPath.read(responseElements, pattern);
        } catch (final PathNotFoundException e) {
            if (emptyListOnNullOrEmptyResponse) {
                return emptyList();
            } else {
                throw e;
            }
        }
    }

    /**
     * Reads the given 'responseElements' and extracts information based on given 'pattern'.

     * If 'responseElements' is null or empty raises {@link IllegalArgumentException}.
     */
    public static List<String> read(final String responseElements, final String pattern) {
        return read(responseElements, pattern, false);
    }

    public static List<String> read(final CloudTrailEvent cloudTrailEvent, final String pattern,
                                    final boolean emptyListOnNullOrEmptyResponse) {
        return read(getEventData(cloudTrailEvent).getResponseElements(), pattern, emptyListOnNullOrEmptyResponse);
    }

    public static List<String> getInstanceLaunchTime(CloudTrailEvent cloudTrailEvent) {
        cloudTrailEvent = checkNotNull(cloudTrailEvent, CLOUD_TRAIL_EVENT_SHOULD_NEVER_BE_NULL);

        final CloudTrailEventData eventData = getEventData(cloudTrailEvent);

        final String responseElements = eventData.getResponseElements();

        return JsonPath.read(responseElements, INSTANCE_LAUNCH_TIME);
    }

    public static Region getRegion(final CloudTrailEvent cloudTrailEvent) {
        return ofNullable(getRegionAsString(cloudTrailEvent))
                .map(Regions::fromName)
                .map(Region::getRegion)
                .orElseThrow(() -> new IllegalArgumentException("Missing awsRegion in CloudTrailEvent " + cloudTrailEvent));
    }

    public static String getRegionAsString(final CloudTrailEvent event) {
        return event.getEventData().getAwsRegion();
    }

    public static ViolationBuilder violationFor(final CloudTrailEvent cloudTrailEvent) {
        return new ViolationBuilder()
                .withEventId(getEventId(cloudTrailEvent))
                .withAccountId(getAccountId(cloudTrailEvent))
                .withRegion(getRegionAsString(cloudTrailEvent))
                .withUsername(getUsernameAsString(cloudTrailEvent));
    }

    public static String getUsernameAsString(final CloudTrailEvent cloudTrailEvent) {

        return ofNullable(cloudTrailEvent)
                .map(CloudTrailEvent::getEventData)
                .map(CloudTrailEventData::getUserIdentity)
                .map(UserIdentity::getARN)
                .orElse(null);
    }

    public static List<String> getInstances(final CloudTrailEvent event) {
        final CloudTrailEventData eventData = getEventData(event);
        final ObjectMapper mapper = new ObjectMapper();
        final List<String> instances = newArrayList();
        final String responseElements = eventData.getResponseElements();
        if (isNullOrEmpty(responseElements)) {
            return newArrayList();
        }

        final JSONArray items = JsonPath.read(responseElements, INSTANCE_JSON_PATH);
        for (final Object item : items) {
            try {
                instances.add(mapper.writeValueAsString(item));
            } catch (final JsonProcessingException e) {
                LOG.warn(e.getMessage(), e);
            }
        }

        return instances;
    }

    public static DateTime getRunInstanceTime(final String instance) {
        return new DateTime((Long) JsonPath.read(instance, RUN_INSTANCE_DATE_JSON_PATH));
    }

    public static DateTime getEventTime(CloudTrailEvent event) {

        event = checkNotNull(event, CLOUD_TRAIL_EVENT_SHOULD_NEVER_BE_NULL);

        final Date eventTime = event.getEventData().getEventTime();

        return new DateTime(eventTime);
    }

    public static List<String> readSecurityGroupIds(final CloudTrailEvent cloudTrailEvent) {
        final LinkedHashSet<String> result = newLinkedHashSet();
        result.addAll(read(cloudTrailEvent, SECURITY_GROUP_IDS_JSON_PATH, true));
        result.addAll(read(cloudTrailEvent, INSTANCES_SECURITY_GROUP_IDS_JSON_PATH, true));
        return newArrayList(result);
    }
}


package org.zalando.stups.fullstop.whitelist;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jayway.jsonpath.JsonPath;
import com.jayway.jsonpath.JsonPathException;
import org.slf4j.Logger;
import org.zalando.stups.fullstop.rule.entity.RuleEntity;
import org.zalando.stups.fullstop.violation.entity.ApplicationEntity;
import org.zalando.stups.fullstop.violation.entity.VersionEntity;
import org.zalando.stups.fullstop.violation.entity.ViolationEntity;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Predicate;

import static com.google.common.collect.Lists.newArrayList;
import static org.slf4j.LoggerFactory.getLogger;

public class WhitelistRulesEvaluator implements BiFunction<RuleEntity, ViolationEntity, Boolean> {

    private final Logger log = getLogger(getClass());
    private final ObjectMapper om = new ObjectMapper();

    /**
     * true if rule matches a violation and should be whitelisted.
     *
     * @return true if rule matches a violation
     */
    @Override
    public Boolean apply(final RuleEntity ruleEntity, final ViolationEntity violationEntity) {
        final List<Predicate<ViolationEntity>> predicates = newArrayList();

        trimOptional(ruleEntity.getAccountId())
                .map(WhitelistRulesEvaluator::accountIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getRegion())
                .map(WhitelistRulesEvaluator::regionIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getViolationTypeEntityId())
                .map(WhitelistRulesEvaluator::violationTypeIdIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getImageName())
                .map(WhitelistRulesEvaluator::imageNameMatches)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getImageOwner())
                .map(WhitelistRulesEvaluator::imageOwnerIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getApplicationId())
                .map(WhitelistRulesEvaluator::applicationIdIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getApplicationVersion())
                .map(WhitelistRulesEvaluator::applicationVersionIsEqual)
                .ifPresent(predicates::add);

        trimOptional(ruleEntity.getMetaInfoJsonPath())
                .map(this::metaInfoJsonPathExists)
                .ifPresent(predicates::add);

        final Optional<Predicate<ViolationEntity>> whiteListTest = predicates.stream().reduce(Predicate::and);

        return whiteListTest.isPresent() && whiteListTest.get().test(violationEntity);
    }

    private static Predicate<ViolationEntity> accountIsEqual(final String account) {
        return v -> account.equals(v.getAccountId());
    }

    private static Predicate<ViolationEntity> regionIsEqual(final String region) {
        return v -> region.equals(v.getRegion());
    }

    private static Predicate<ViolationEntity> violationTypeIdIsEqual(final String violationTypeId) {
        return v -> violationTypeId.equals(v.getViolationTypeEntity().getId());
    }

    private static Predicate<ViolationEntity> imageNameMatches(final String imageNamePattern) {
        return v -> {
            if (v.getMetaInfo() instanceof Map) {
                final Map map = (Map) v.getMetaInfo();
                if (map == null || map.get("ami_name") == null) {
                    return false;
                }
                final String amiName = (String) map.get("ami_name");
                return amiName.matches(imageNamePattern);
            } else {
                return false;
            }
        };
    }

    private static Predicate<ViolationEntity> imageOwnerIsEqual(final String imageOwner) {
        return v -> {
            if (v.getMetaInfo() instanceof Map) {
                final Map map = (Map) v.getMetaInfo();
                final String amiOwnerId = (String) map.get("ami_owner_id");
                return imageOwner.equals(amiOwnerId);
            } else {
                return false;
            }
        };
    }

    private static Predicate<ViolationEntity> applicationIdIsEqual(final String applicationId) {
        return v -> applicationId.equals(
                Optional.ofNullable(
                        v.getApplication()).
                        map(ApplicationEntity::getName).
                        orElse(null));
    }

    private static Predicate<ViolationEntity> applicationVersionIsEqual(final String applicationVersion) {
        return v -> applicationVersion.equals(
                Optional.ofNullable(
                        v.getApplicationVersion()).
                        map(VersionEntity::getName).
                        orElse(null));
    }

    private Predicate<ViolationEntity> metaInfoJsonPathExists(final String jsonPathDefinition) {
        return v -> {
            final JsonPath jsonPath = JsonPath.compile(jsonPathDefinition);
            final String json;
            try {
                json = om.writeValueAsString(v.getMetaInfo());
            } catch (JsonProcessingException e) {
                log.warn("Could not read violation metaInfo as JSON: " + v, e);
                return false;
            }
            try {
                final List<?> matches = jsonPath.read(json);
                return !matches.isEmpty();
            } catch (JsonPathException e) {
                return false;
            }
        };
    }

    private static Optional<String> trimOptional(final String value) {
        return Optional.ofNullable(value).map(String::trim).filter(string -> !string.isEmpty());
    }

}


package org.zalando.stups.fullstop.plugin;

import org.joda.time.DateTime;

import java.nio.file.Paths;

/**
 * @author jbellmann
 */
class PrefixBuilder {

    /**
     * Builds the prefix that will be prepended to the 'bucketname'<br/>
     * Something like '123456789/eu-west-1/2015/06/12/'
     *
     * @param accountId
     * @param region             name of region
     * @param instanceLaunchTime
     * @return
     */
    static String build(final String accountId, final String region, final DateTime instanceLaunchTime) {
        return Paths.get(
                accountId, region, instanceLaunchTime.toString("YYYY"), instanceLaunchTime.toString("MM"),
                instanceLaunchTime.toString("dd")).toString() + "/";
    }

}


package org.zalando.stups.fullstop.filereader;

import com.amazonaws.services.cloudtrail.processinglibrary.exceptions.CallbackException;
import com.amazonaws.services.cloudtrail.processinglibrary.exceptions.ProcessingLibraryException;
import com.amazonaws.services.cloudtrail.processinglibrary.impl.DefaultEventFilter;
import com.amazonaws.services.cloudtrail.processinglibrary.impl.DefaultExceptionHandler;
import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.EventFilter;
import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.EventsProcessor;
import com.amazonaws.services.cloudtrail.processinglibrary.interfaces.ExceptionHandler;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEvent;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailLog;
import com.amazonaws.services.cloudtrail.processinglibrary.progress.ProgressInfo;
import com.amazonaws.services.cloudtrail.processinglibrary.progress.ProgressState;
import com.amazonaws.services.cloudtrail.processinglibrary.progress.ProgressStatus;
import com.amazonaws.services.cloudtrail.processinglibrary.serializer.DefaultEventSerializer;
import com.amazonaws.services.cloudtrail.processinglibrary.serializer.EventSerializer;
import com.amazonaws.services.cloudtrail.processinglibrary.serializer.RawLogDeliveryEventSerializer;
import com.amazonaws.services.cloudtrail.processinglibrary.utils.EventBuffer;
import com.amazonaws.services.cloudtrail.processinglibrary.utils.LibraryUtils;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.zip.GZIPInputStream;

/**
 * So we can test a bit as long no access is possible to the s3-files directly.
 *
 * @author jbellmann
 */
public class FileEventReader {

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

    private final EventFilter eventFilter;

    private final EventsProcessor eventsProcessor;

    private final ExceptionHandler exceptionHandler;

    private final ObjectMapper mapper;

    private final boolean isEnableRawEventInfo = false;

    public FileEventReader(final EventsProcessor eventsProcessor, final EventFilter eventFilter) {
        this.eventsProcessor = eventsProcessor;
        this.eventFilter = eventFilter;
        this.mapper = new ObjectMapper();
        this.exceptionHandler = new DefaultExceptionHandler();
    }

    public FileEventReader(final EventsProcessor eventsProcessor) {
        this(eventsProcessor, new DefaultEventFilter());
    }

    public void readEvents(final File file, final CloudTrailLog ctLog) throws CallbackException {
        try {
            final GZIPInputStream gzippedInputStream = new GZIPInputStream(new FileInputStream(file));

            final EventSerializer serializer = this.getEventSerializer(gzippedInputStream, ctLog);

            this.emitEvents(serializer);
        }
        catch (IllegalArgumentException | IOException e) {
            this.exceptionHandler.handleException(
                    new ProcessingLibraryException(
                            e.getMessage(),
                            new ProgressStatus(ProgressState.parseMessage, new FakeProgressInfo())));
        }
    }

    /**
     * Get the EventSerializer based on user's configuration.
     *
     * @param inputStream the Gzipped content from CloudTrail log file
     * @param ctLog       CloudTrail log file
     * @return parser that parses CloudTrail log file
     * @throws java.io.IOException
     */
    private EventSerializer getEventSerializer(final GZIPInputStream inputStream, final CloudTrailLog ctLog)
            throws IOException {
        final EventSerializer serializer;

        if (isEnableRawEventInfo) {
            final String logFileContent = new String(LibraryUtils.toByteArray(inputStream), StandardCharsets.UTF_8);
            final JsonParser jsonParser = this.mapper.getFactory().createParser(logFileContent);
            serializer = new RawLogDeliveryEventSerializer(logFileContent, ctLog, jsonParser);
        }
        else {
            final JsonParser jsonParser = this.mapper.getFactory().createParser(inputStream);
            serializer = new DefaultEventSerializer(ctLog, jsonParser);
        }

        return serializer;
    }

    private void emitEvents(final EventSerializer serializer) throws CallbackException, IOException {
        final EventBuffer<CloudTrailEvent> eventBuffer = new EventBuffer<>(10);
        while (serializer.hasNextEvent()) {

            final CloudTrailEvent event = serializer.getNextEvent();

            if (this.eventFilter.filterEvent(event)) {
                eventBuffer.addEvent(event);

                if (eventBuffer.isBufferFull()) {
                    this.eventsProcessor.process(eventBuffer.getEvents());
                }

            }
            else {
                logger.debug("AWSCloudTrailEvent " + event + " has filtered.");
            }
        }

        // emit whatever in the buffer as last batch
        final List<CloudTrailEvent> events = eventBuffer.getEvents();
        if (!events.isEmpty()) {
            this.eventsProcessor.process(events);
        }
    }

    static class FakeProgressInfo implements ProgressInfo {

        @Override
        public boolean isSuccess() {
            return false;
        }

    }
}


package org.zalando.stups.fullstop;

import com.amazonaws.auth.AWSCredentialsProvider;
import com.amazonaws.auth.AWSCredentialsProviderChain;
import com.amazonaws.auth.DefaultAWSCredentialsProviderChain;
import com.amazonaws.services.cloudtrail.processinglibrary.configuration.ProcessingConfiguration;
import com.amazonaws.services.cloudtrail.processinglibrary.configuration.PropertiesFileConfiguration;
import com.amazonaws.services.cloudtrail.processinglibrary.model.CloudTrailEventMetadata;
import com.amazonaws.services.cloudtrail.processinglibrary.utils.LibraryUtils;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * @author jbellmann
 */
public class ExtPropertiesFileConfiguration implements ProcessingConfiguration {

    /* configuration file property names */
    public static final String ACCESS_KEY = "accessKey";

    public static final String SECRET_KEY = "secretKey";

    public static final String SQS_URL = "sqsUrl";

    public static final String SQS_REGION = "sqsRegion";

    public static final String VISIBILITY_TIMEOUT = "visibilityTimeout";

    public static final String S3_REGION = "s3Region";

    public static final String THREAD_COUNT = "threadCount";

    public static final String THREAD_TERMINATION_DELAY_SECONDS = "threadTerminationDelaySeconds";

    public static final String MAX_EVENTS_PER_EMIT = "maxEventsPerEmit";

    public static final String ENABLE_RAW_EVENT_INFO = "enableRawEventInfo";

    private static final String ERROR_CREDENTIALS_PROVIDER_NULL = "CredentialsProvider is null. Either put your "
            + "access key and secret key in the configuration file in your class path, or spcify it in the "
            + "ProcessingConfiguration object.";

    /**
     * The AWS
     * credentials provider used to obtain credentials.
     */
    private final AWSCredentialsProvider awsCredentialsProvider;

    /**
     * The SQS Queue URL used to receive events.
     *

     *
The Queue must be subscribed to AWS CloudTrail.
     */
    private String sqsUrl = null;

    /**
     * The SQS region to use.
     *

     *
If not specified, the default SQS region ( {@value ProcessingConfiguration#DEFAULT_SQS_REGION}) will be used.
     */
    private String sqsRegion = DEFAULT_SQS_REGION;

    /**
     * A period of time, in seconds, during which Amazon SQS prevents other consuming components from receiving and
     * processing messages that are currently being processed by the CloudTrail Processing Library on your behalf.
     */
    private int visibilityTimeout = DEFAULT_VISIBILITY_TIMEOUT;

    /**
     * The S3 endpoint specific to a region.
     *

     *
If not specified, the default S3 region will be used.
     */
    private String s3Region = DEFAULT_S3_REGION;

    /**
     * The number of threads used to download log files from S3 in parallel.
     *

     *
Callbacks can be invoked from any thread.
     */
    private int threadCount = DEFAULT_THREAD_COUNT;

    /**
     * The time allowed, in seconds, for threads to shut down after AWSCloudTrailEventProcessingExecutor.stop() is
     * called.
     *

     *
Any threads still running beyond this time will be forcibly terminated.
     */
    private int threadTerminationDelaySeconds = DEFAULT_THREAD_TERMINATION_DELAY_SECONDS;

    /**
     * The maximum number of AWSCloudTrailClientEvents sent to a single invocation of processEvents().
     */
    private int maxEventsPerEmit = DEFAULT_MAX_EVENTS_PER_EMIT;

    /**
     * Whether to include raw event information in {@link CloudTrailEventMetadata}.
     */
    private boolean enableRawEventInfo = DEFAULT_ENABLE_RAW_EVENT_INFO;

    public ExtPropertiesFileConfiguration(final Properties prop) {
        this(prop, new DefaultAWSCredentialsProviderChain());
    }

    /**
     * Creates a {@link PropertiesFileConfiguration} from values provided in a classpath properties file.
     *
     * @param prop               the classpath properties file to load.
     * @param credentialProvider credential provider.
     */
    public ExtPropertiesFileConfiguration(final Properties prop, final AWSCredentialsProviderChain credentialProvider) {

        this.sqsUrl = prop.getProperty(SQS_URL);
        LibraryUtils.checkArgumentNotNull(this.sqsUrl, "Cannot find SQS URL in properties file.");

        final String accessKey = prop.getProperty(ACCESS_KEY);
        final String secretKey = prop.getProperty(SECRET_KEY);

        if (accessKey != null && secretKey != null) {
            this.awsCredentialsProvider = new SimplePropertiesCredentials(prop);
        }
        else {
            this.awsCredentialsProvider = credentialProvider;
        }

        this.s3Region = prop.getProperty(S3_REGION);
        this.visibilityTimeout = this.getIntProperty(prop, VISIBILITY_TIMEOUT);

        this.sqsRegion = prop.getProperty(SQS_REGION);

        this.threadCount = this.getIntProperty(prop, THREAD_COUNT);
        this.threadTerminationDelaySeconds = this.getIntProperty(prop, THREAD_TERMINATION_DELAY_SECONDS);

        this.maxEventsPerEmit = this.getIntProperty(prop, MAX_EVENTS_PER_EMIT);
        this.enableRawEventInfo = this.getBooleanProperty(prop, ENABLE_RAW_EVENT_INFO);
    }

    public static ExtPropertiesFileConfiguration fromClasspath(final String propertiesFile) {

        // load properties from configuration properties file
        final Properties prop = ExtPropertiesFileConfiguration.loadProperty(propertiesFile);
        return new ExtPropertiesFileConfiguration(prop);
    }

    /**
     * Load properties from a classpath property file.
     *
     * @param propertiesFile the classpath properties file to read.
     * @return a Properties object
     * containing the properties set in the file.
     */
    protected static Properties loadProperty(final String propertiesFile) {
        final Properties prop = new Properties();
        try {
            final InputStream in = ExtPropertiesFileConfiguration.class.getResourceAsStream(propertiesFile);
            prop.load(in);
            in.close();
        }
        catch (final IOException e) {
            throw new IllegalStateException("Cannot load property file at " + propertiesFile, e);
        }

        return prop;
    }

    /**
     * {@inheritDoc}
     */
    public AWSCredentialsProvider getAwsCredentialsProvider() {
        return awsCredentialsProvider;
    }

    /**
     * {@inheritDoc}
     */
    public String getSqsUrl() {
        return sqsUrl;
    }

    /**
     * {@inheritDoc}
     */
    public String getSqsRegion() {
        return sqsRegion;
    }

    /**
     * {@inheritDoc}
     */
    public int getVisibilityTimeout() {
        return visibilityTimeout;
    }

    /**
     * {@inheritDoc}
     */
    public String getS3Region() {
        return s3Region;
    }

    /**
     * {@inheritDoc}
     */
    public int getThreadCount() {
        return threadCount;
    }

    /**
     * {@inheritDoc}
     */
    public int getThreadTerminationDelaySeconds() {
        return threadTerminationDelaySeconds;
    }

    /**
     * {@inheritDoc}
     */
    public int getMaxEventsPerEmit() {
        return maxEventsPerEmit;
    }

    /**
     * {@inheritDoc}
     */
    public boolean isEnableRawEventInfo() {
        return enableRawEventInfo;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void validate() {
        LibraryUtils.checkArgumentNotNull(this.getAwsCredentialsProvider(), ERROR_CREDENTIALS_PROVIDER_NULL);
        LibraryUtils.checkArgumentNotNull(this.getSqsUrl(), "SQS URL is null.");
        LibraryUtils.checkArgumentNotNull(this.getSqsRegion(), "SQS Region is null.");
        LibraryUtils.checkArgumentNotNull(this.getVisibilityTimeout(), "Visibility Timeout is null.");
        LibraryUtils.checkArgumentNotNull(this.getS3Region(), "S3 Region is null.");
        LibraryUtils.checkArgumentNotNull(this.getThreadCount(), "Thread Count is null.");
        LibraryUtils.checkArgumentNotNull(
                this.getThreadTerminationDelaySeconds(),
                "Thread Termination Delay Seconds is null.");
        LibraryUtils.checkArgumentNotNull(this.getMaxEventsPerEmit(), "Maximum Events Per Emit is null.");
        LibraryUtils.checkArgumentNotNull(this.isEnableRawEventInfo(), "Is Enable Raw Event Information is null.");
    }

    /**
     * Convert a string representation of a property to an integer type.
     *
     * @param prop the property class
     * @param name a name to evaluate in the property file.
     * @return an integer representation of the value associated with the property name.
     */
    private int getIntProperty(final Properties prop, final String name) {
        final String propertyValue = prop.getProperty(name);
        return Integer.parseInt(propertyValue);
    }

    /**
     * Convert a string representation of a property to a boolean type.
     *
     * @param prop the property class
     * @param name a name to evaluate in the property file.
     * @return a boolean representation of the value associated with the property name.
     */
    private Boolean getBooleanProperty(final Properties prop, final String name) {
        final String propertyValue = prop.getProperty(name);
        return Boolean.parseBoolean(propertyValue);
    }

}


package org.zalando.stups.fullstop.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableResourceServer;
import org.springframework.security.oauth2.config.annotation.web.configuration.ResourceServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configurers.ResourceServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.ResourceServerTokenServices;
import org.zalando.stups.oauth2.spring.security.expression.ExtendedOAuth2WebSecurityExpressionHandler;

import static org.springframework.security.config.http.SessionCreationPolicy.NEVER;

/**
 * @author  jbellmann
 */
@Configuration
@EnableResourceServer
public class OAuth2Configuration extends ResourceServerConfigurerAdapter {

    @Value("${spring.oauth2.resource.tokenInfoUri}")
    private String tokenInfoUri;

    @Override
    public void configure(final ResourceServerSecurityConfigurer resources) throws Exception {
        // add support for #oauth2.hasUidScopeAndAnyRealm() expressions
        resources
                .expressionHandler(new ExtendedOAuth2WebSecurityExpressionHandler());
    }

    /**
     * Configure scopes for specific controller/httpmethods/roles here.
     */
    @Override
    public void configure(final HttpSecurity http) throws Exception {

        http.sessionManagement().sessionCreationPolicy(NEVER)

            // configure form login
            .and().formLogin().disable()

            // configure logout
                .logout().disable()

                .authorizeRequests()
                .antMatchers(HttpMethod.OPTIONS).permitAll() // Allow preflight CORS requests from browsers
                .antMatchers("/").access("#oauth2.hasUidScopeAndAnyRealm('/employees', '/services')")
                .antMatchers("/api/**").access("#oauth2.hasUidScopeAndAnyRealm('/employees', '/services')")
                .antMatchers("/s3/**").access("#oauth2.hasUidScopeAndAnyRealm('/employees', '/services')")
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/swagger-resources").permitAll()
                .antMatchers("/api-docs").permitAll();

    }

    @Bean
    public ResourceServerTokenServices customResourceTokenServices() {
// return new TokenInfoResourceServerTokenServices(tokenInfoUri, "what_here");
        return new BearerNoneTokenInfoResourceServerTokenServices(tokenInfoUri);
    }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.random;

import io.github.benas.randombeans.EnhancedRandomBuilder;
import io.github.benas.randombeans.api.EnhancedRandom;
import org.junit.jupiter.api.extension.*;

import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static java.nio.charset.Charset.forName;
import static org.junit.platform.commons.support.AnnotationSupport.isAnnotated;

/**
 * The random beans extension provides a test with randomly generated objects, including:
 *
 *

 *
JDK types
 *
Custom types
 *
Collections
 *
Generic collections
 *
Partial population
 *

 *
 *
Usage examples:
 *
 *
Injecting random values as fields:
 *
 *

 * @ExtendWith(RandomBeansExtension.class)
 * public class MyTest {
 *
 *     @Random
 *     private String anyString;
 *
 *     @Random(excluded = {"name", "value"})
 *     private List<DomainObject> anyPartiallyPopulatedDomainObject;
 *
 *     @Random(type = DomainObject.class)
 *     private List<DomainObject> anyDomainObjects;
 *
 *     @Test
 *     public void testUsingRandomString() {
 *         // use the injected anyString
 *         // ...
 *     }
 *
 *     @Test
 *     public void testUsingRandomDomainObjects() {
 *         // use the injected anyDomainObjects
 *         // the anyDomainObjects will contain _N_ fully populated random instances of DomainObject
 *         // ...
 *     }
 *
 *     @Test
 *     public void testUsingPartiallyPopulatedDomainObject() {
 *         // use the injected anyPartiallyPopulatedDomainObject
 *         // this object's "name" and "value" members will not be populated since this has been declared with
 *         //     excluded = {"name", "value"}
 *         // ...
 *     }
 * }
 *

 *
 *
Injecting random values as parameters:
 *
 *

 * @ExtendWith(RandomBeansExtension.class)
 * public class MyTest {
 *
 *     @Test
 *     @ExtendWith(RandomBeansExtension.class)
 *     public void testUsingRandomString(@Random String anyString) {
 *         // use the provided anyString
 *         // ...
 *     }
 *
 *     @Test
 *     @ExtendWith(RandomBeansExtension.class)
 *     public void testUsingRandomDomainObjects(@Random(type = DomainObject.class) List<DomainObject> anyDomainObjects) {
 *         // use the injected anyDomainObjects
 *         // the anyDomainObjects will contain _N_ fully populated random instances of DomainObject
 *         // ...
 *     }
 *
 *     @Test
 *     @ExtendWith(RandomBeansExtension.class)
 *     public void testUsingPartiallyPopulatedDomainObject(@Random(excluded = {"name", "value"}) List<DomainObject> anyPartiallyPopulatedDomainObject) {
 *         // use the injected anyPartiallyPopulatedDomainObject
 *         // this object's "name" and "value" members will not be populated since this has been declared with
 *         //     excluded = {"name", "value"}
 *         // ...
 *     }
 * }
 *

 *
 * @see Random Beans
 * @since 1.0.0
 */
public class RandomBeansExtension implements TestInstancePostProcessor, ParameterResolver {

  private final EnhancedRandom random;

  /**
   * Create the extension with its encapsulated {@link EnhancedRandom}.
   *
   * @see Enhanced
   *     Random Configuration Parameters
   */
  public RandomBeansExtension() {
    this.random =
        EnhancedRandomBuilder.aNewEnhancedRandomBuilder()
            // maximum number of instances of a given type, above this number requests will start to
            // reuse
            // previously generated instances
            .objectPoolSize(10)

            // how deep should we go when randomising an object graph?
            .randomizationDepth(5)

            // the charset used for all String and Character values
            .charset(forName("UTF-8"))

            // min, max bounds for the generated string length
            .stringLengthRange(5, 50)

            // min, max bounds for the generated collections size
            .collectionSizeRange(1, 10)

            // if a random values is declared as an abstract or interface type then the classpath
            // will be scanned
            // for a concrete type of that abstract or interface type
            .scanClasspathForConcreteTypes(true)

            // do not override any values which are already initialised in the target type
            .overrideDefaultInitialization(false)
            .build();
  }

  /**
   * Does this extension support injection for parameters of the type described by the given {@code
   * parameterContext}?
   *
   * @param parameterContext the context for the parameter for which an argument should be resolved
   * @param extensionContext the extension context for the {@code Executable} about to be invoked
   * @return true if the given {@code parameterContext} is annotated with {@link Random}, false
   *     otherwise
   * @throws ParameterResolutionException
   */
  @Override
  public boolean supportsParameter(
      ParameterContext parameterContext, ExtensionContext extensionContext)
      throws ParameterResolutionException {
    return parameterContext.getParameter().getAnnotation(Random.class) != null;
  }

  /**
   * Provides a value for any parameter context which has passed the {@link
   * #supportsParameter(ParameterContext, ExtensionContext)} gate.
   *
   * @param parameterContext the context for the parameter for which an argument should be resolved
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @return a randomly generated object
   * @throws ParameterResolutionException
   */
  @Override
  public Object resolveParameter(
      ParameterContext parameterContext, ExtensionContext extensionContext)
      throws ParameterResolutionException {
    return resolve(
        parameterContext.getParameter().getType(),
        parameterContext.getParameter().getAnnotation(Random.class));
  }

  /**
   * Inject random values into any fields which are annotated with {@link Random}
   *
   * @param testInstance the instance to post-process
   * @param extensionContext the current extension context
   * @throws Exception
   */
  @Override
  public void postProcessTestInstance(Object testInstance, ExtensionContext extensionContext)
      throws Exception {
    for (Field field : testInstance.getClass().getDeclaredFields()) {
      if (isAnnotated(field, Random.class)) {
        Random annotation = field.getAnnotation(Random.class);
        Object randomObject = resolve(field.getType(), annotation);

        field.setAccessible(true);
        field.set(testInstance, randomObject);
      }
    }
  }

  /**
   * Maps the 'random requirements' expressed by the given {@code annotation} to invocations on
   * {@link #random}.
   *
   * @param targetType the type to be provided
   * @param annotation an instance of {@link Random} which describes how the user wishes to
   *     configure the 'random generation'
   * @return a randomly generated instance of {@code targetType}
   */
  private Object resolve(Class<?> targetType, Random annotation) {
    if (targetType.isAssignableFrom(List.class) || targetType.isAssignableFrom(Collection.class)) {
      return random
          .objects(annotation.type(), annotation.size(), annotation.excludes())
          .collect(Collectors.toList());
    } else if (targetType.isAssignableFrom(Set.class)) {
      return random
          .objects(annotation.type(), annotation.size(), annotation.excludes())
          .collect(Collectors.toSet());
    } else if (targetType.isAssignableFrom(Stream.class)) {
      return random.objects(annotation.type(), annotation.size(), annotation.excludes());
    } else {
      return random.nextObject(targetType, annotation.excludes());
    }
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.folder;

import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;

import java.io.File;
import java.io.IOException;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;

import static java.nio.file.FileVisitResult.CONTINUE;

/**
 * Encapsulates the {@link #rootFolder} within which any files or directories will be created along
 * with the operations which a tester may wish to invoke ({@link #createFile(String)}, {@link
 * #createDirectory(String)}) and post test invocations which the associated extension will invoke.
 */
@SuppressWarnings({"ResultOfMethodCallIgnored", "nls"})
public class TemporaryFolder implements CloseableResource {
  private static final String FILE_PREFIX = "junit";
  private static final String FILE_SUFFIX = ".tmp";

  /**
   * The root folder within which any files or directories will be created, on {@link #destroy()}
   * this folder and all of its contents will be silently deleted.
   */
  private final File rootFolder;

  /**
   * Package protected since a {@link TemporaryFolder}'s lifecycle is expected to be controlled by
   * its associated extension.
   */
  TemporaryFolder() {
    try {
      // do not use Files.createTempFile to create a directory
      // see https://rules.sonarsource.com/java/RSPEC-2976
      Path tempPath = Files.createTempDirectory(FILE_PREFIX);
      rootFolder = tempPath.toFile();
    } catch (IOException ex) {
      throw new TemporaryFolderException("Failed to prepare root folder!", ex);
    }
  }

  @Override
  public void close() throws Throwable {
    destroy();
  }

  /**
   * Returns the root folder. Exposing this offers some back compatability with JUnit4's {@code
   * TemporaryFolder} so test cases which are used to invoking {@code getRoot()} on a JUnit4 rule
   * can adopt the same approach with this {@code TemporaryFolder}. In addition, this may be useful
   * where you want to use the root folder itself without creating files or directories within it.
   *
   *
Note: the extension is responsible for creating/managing/destroying the root folder
   * so don't bother trying to clean it up yourself and don't expect that anything you do to it will
   * survive post-test-cleanup.
   *
   * @see Issue 8
   * @return the root folder
   */
  public File getRoot() {
    return rootFolder;
  }

  /**
   * Create a file within the temporary folder root.
   *
   * @param fileName the name of the file to be created
   * @return the newly created file instance
   * @throws IOException in case the file creation call fails
   */
  public File createFile(String fileName) throws IOException {
    Path path = Paths.get(rootFolder.getPath(), fileName);
    return Files.createFile(path).toFile();
  }

  /**
   * Create a directory within the temporary folder root.
   *
   * @param directoryName the name of the directory to be created
   * @return the directory instance
   */
  public File createDirectory(String directoryName) {
    Path path = Paths.get(rootFolder.getPath(), directoryName);
    try {
      return Files.createDirectory(path).toFile();
    } catch (IOException ex) {
      throw new TemporaryFolderException(
          String.format("Failed to create directory: '%s'", path.toString()), ex);
    }
  }

  /**
   * Deletes the {@link #rootFolder} and all of its contents. This is package protected because a
   * {@link TemporaryFolder}'s lifecycle is expected to be controlled by its associated extension.
   *
   *
Note: any exception encountered during deletion will be swallowed.
   */
  void destroy() throws IOException {
    if (rootFolder.exists()) {
      // walk the contents deleting each
      Files.walkFileTree(
          rootFolder.toPath(),
          new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attributes)
                throws IOException {
              return delete(file);
            }

            @Override
            public FileVisitResult postVisitDirectory(Path directory, IOException exception)
                throws IOException {
              return delete(directory);
            }

            @SuppressWarnings("SameReturnValue")
            private FileVisitResult delete(Path file) throws IOException {
              Files.delete(file);
              return CONTINUE;
            }
          });

      if (rootFolder.exists()) {
        // delete the parent, if it still exists
        Files.delete(rootFolder.toPath());
      }
    }
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.folder;

import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.ExtensionContext.Namespace;
import org.junit.jupiter.api.extension.ParameterContext;
import org.junit.jupiter.api.extension.ParameterResolutionException;
import org.junit.jupiter.api.extension.ParameterResolver;

/**
 * The temporary folder extension provides a test with access to temporary files and directories.
 * The temporary folder extension provides a {@link TemporaryFolder} which you can use to create a
 * temporary file or directory for use by your test. The {@link TemporaryFolder} can be injected
 * into your test or test case with any of the following approaches:
 *
 *

 *
Instance variable injection into a {@code @BeforeEach} method. The {@link TemporaryFolder}
 *       will be destroyed during {@code @AfterEach} and no exception will be thrown in cases where
 *       the deletion fails. For example:
 *

 *  private TemporaryFolder temporaryFolder;
 *
 *  @BeforeEach
 *  public void setUp(TemporaryFolder temporaryFolder) {
 *      this.temporaryFolder = temporaryFolder
 *      // ...
 *  }
 *

 *
Parameter injection into a {@code @Test} method. The {@link TemporaryFolder} will be
 *       destroyed during {@code @AfterEach} and no exception will be thrown in cases where the
 *       deletion fails. For example:
 *

 *  @Test
 *  public void testUsingTemporaryFolder(TemporaryFolder temporaryFolder) {
 *      // ...
 *  }
 *

 *
Class variable injection using a {@code @BeforeAll} method. Note: in this case all
 *       tests in the test case will share the same instance of the {@code TemporaryFolder}. The
 *       {@link TemporaryFolder} will be destroyed after any {@code @AfterAll} method completes and
 *       no exception will be thrown in cases where the deletion fails. For example:
 *

 *  private static TemporaryFolder TEMPORARY_FOLDER;
 *
 *  @BeforeAll
 *  public static void setUp(TemporaryFolder givenTemporaryFolder) {
 *      TEMPORARY_FOLDER = givenTemporaryFolder
 *      // ...
 *  }
 *

 *

 *
 *
Usage examples:
 *
 *
Injecting a {@code TemporaryFolder} in a {@code @BeforeEach} method:
 *
 *

 * @ExtendWith(TemporaryFolderExtension.class)
 * public class MyTest {
 *
 *     private TemporaryFolder temporaryFolder;
 *
 *     @BeforeEach
 *     public void setUp(TemporaryFolder temporaryFolder) {
 *         this.temporaryFolder = temporaryFolder
 *         // ...
 *     }
 *
 *     @Test
 *     public void testUsingTemporaryFile() {
 *         File file = temporaryFolder.createFile("foo.txt");
 *         // ...
 *     }
 *
 *     @Test
 *     public void testUsingTemporaryDirectory() {
 *         // use the temporary folder itself
 *         File root = temporaryFolder.getRoot();
 *
 *         // create a sub directory within the temporary folder
 *         File file = temporaryFolder.createDirectory("foo");
 *         // ...
 *     }
 * }
 *

 *
 *
Injecting a {@code TemporaryFolder} in a {@code @Test} method:
 *
 *

 * public class MyTest {
 *
 *     @Test
 *     @ExtendWith(TemporaryFolderExtension.class)
 *     public void testUsingTemporaryFile(TemporaryFolder temporaryFolder) {
 *         File file = temporaryFolder.createFile("foo.txt");
 *         // ...
 *     }
 *
 *     @Test
 *     @ExtendWith(TemporaryFolderExtension.class)
 *     public void testUsingTemporaryDirectory(TemporaryFolder temporaryFolder) {
 *         // use the temporary folder itself
 *         File root = temporaryFolder.getRoot();
 *
 *         // create a sub directory within the temporary folder
 *         File file = temporaryFolder.createDirectory("foo");
 *         // ...
 *     }
 * }
 *

 *
 * @see JUnit 4
 *     TemporaryFolder Rule
 * @since 1.0.0
 */
public class TemporaryFolderExtension implements ParameterResolver {

  private static final Namespace NAMESPACE = Namespace.create(TemporaryFolderExtension.class);

  /**
   * Does this extension support injection for parameters of the type described by the given {@code
   * parameterContext}?
   *
   * @param parameterContext the context for the parameter for which an argument should be resolved
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @return true if the given {@code parameterContext} describes a parameter of type: {@link
   *     TemporaryFolder}, false otherwise
   * @throws ParameterResolutionException
   */
  @Override
  public boolean supportsParameter(
      ParameterContext parameterContext, ExtensionContext extensionContext)
      throws ParameterResolutionException {
    return appliesTo(parameterContext.getParameter().getType());
  }

  /**
   * Provides a value for any parameter context which has passed the {@link
   * #supportsParameter(ParameterContext, ExtensionContext)} gate.
   *
   * @param parameterContext the context for the parameter for which an argument should be resolved
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @return a new {@link TemporaryFolder}
   * @throws ParameterResolutionException
   */
  @Override
  public Object resolveParameter(
      ParameterContext parameterContext, ExtensionContext extensionContext)
      throws ParameterResolutionException {
    return extensionContext
        .getStore(NAMESPACE)
        .getOrComputeIfAbsent(
            parameterContext, key -> new TemporaryFolder(), TemporaryFolder.class);
  }

  private boolean appliesTo(Class<?> clazz) {
    return clazz == TemporaryFolder.class;
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.exception;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestExecutionExceptionHandler;

import java.util.Optional;
import java.util.function.Function;
import java.util.function.Predicate;

import static io.github.glytching.junit.extension.util.ExtensionUtil.getStore;
import static org.junit.platform.commons.util.AnnotationUtils.findAnnotation;
import static org.junit.platform.commons.util.FunctionUtils.where;

/**
 * The expected exception extension allows the developer to express the expectations of an exception
 * to be thrown by the code-under-test. This extension is engaged by adding the {@link
 * ExpectedException} annotation to a test method.
 *
 *
Usage example:
 *
 *

 * public class MyTest {
 *
 *     @Test
 *     @ExpectedException(type = Exception.class, messageIs = "Boom!")
 *     public void canHandleAnException() throws Exception {
 *         // ...
 *         throw new Exception("Boom!");
 *     }
 *
 *
 *     @Test
 *     @ExpectedException(type = RuntimeException.class, messageStartsWith = "Bye")
 *     public void canHandleAnExceptionWithAMessageWhichStartsWith() {
 *         // ...
 *         throw new RuntimeException("Bye bye");
 *     }
 * }
 *

 *
 * Notes:
 *
 *

 *
Since usage of this extension implies that the developer expects an exception to be
 *       thrown the following test case will fail since it throws no exception:
 *

 *   @Test
 *   @ExpectedException(type = Throwable.class)
 *   public void failsTestForMissingException() {}
 *

 *       This is to avoid a false positive where a test is declared to expect an exception and
 *       passes even if no exception is thrown.
 *
The expected exception type will match on the given type and any subclasses of that type.
 *       In other words, the following test will pass:
 *

 *          @Test
 *          @ExpectedException(type = Throwable.class, messageIs = "Boom!")
 *          public void canHandleAThrowable() throws Throwable {
 *              throw new Exception("Boom!");
 *          }
 *

 *       This is for consistency with JUnit Jupiter, in which AssertThrows matches an
 *       exception type or any subclass of that exception type.
 *

 *
 * @see JUnit 4
 *     ExpectedException Rule
 * @since 1.0.0
 */
public class ExpectedExceptionExtension
    implements TestExecutionExceptionHandler, AfterTestExecutionCallback {

  private static final String KEY = "exceptionWasHandled";

  private final Function<Throwable, String> function;

  public ExpectedExceptionExtension() {
    this.function = Throwable::getMessage;
  }

  /**
   * Handle the supplied {@code Throwable throwable}. If the {@code extensionContext} is annotated
   * with {@link ExpectedException} and if the {@code throwable} matches the expectations expressed
   * in the {@link ExpectedException} annotation then the supplied {@code throwable} is swallowed
   * otherwise the supplied {@code throwable} is rethrown.
   *
   * @param extensionContext the current extension context
   * @param throwable the {@code Throwable} to handle
   * @throws Throwable
   */
  @Override
  public void handleTestExecutionException(ExtensionContext extensionContext, Throwable throwable)
      throws Throwable {
    Optional<ExpectedException> optional =
        findAnnotation(extensionContext.getTestMethod(), ExpectedException.class);
    if (optional.isPresent()) {

      ExpectedException annotation = optional.get();
      // see https://github.com/glytching/junit-extensions/issues/5
      if (annotation.type().isAssignableFrom(throwable.getClass())) {
        if (where(function, getPredicate(annotation)).test(throwable)) {
          getStore(extensionContext, this.getClass()).put(KEY, true);

          // swallow the exception because the caller has declared it to be expected
          return;
        }
      }
    }
    throw throwable;
  }

  /**
   * The presence of {@link ExpectedException} on a test is a clear statement by the developer that
   * some exception must be thrown by that test. Therefore, if the invocation arrives here without
   * having been evaluated by {@link #handleTestExecutionException(ExtensionContext, Throwable)} and
   * with no exception in the context then this expectation has not been met and rather than failing
   * silently (and giving a false positive) the library will explicitly fail on this condition.
   *
   * @param extensionContext
   * @throws Exception
   */
  @Override
  public void afterTestExecution(ExtensionContext extensionContext) throws Exception {
    Boolean exceptionWasHandled =
        (Boolean) getStore(extensionContext, this.getClass()).getOrComputeIfAbsent(KEY, s -> false);
    if (!exceptionWasHandled && !extensionContext.getExecutionException().isPresent()) {
      Assertions.fail("Expected an exception but no exception was thrown!");
    }
  }

  /**
   * Maps the expectations expressed in the given {@code annotation} to a {@link Predicate}.
   *
   * @param annotation encapsulates the callers' expression of what's to be deemed an acceptable
   *     exception
   * @return a predicate which can be used to assess whether an exception matches the expectations
   *     expressed in the given {@code annotation}
   */
  private Predicate<String> getPredicate(ExpectedException annotation) {
    if (has(annotation.messageStartsWith())) {
      return s -> s.startsWith(annotation.messageStartsWith());
    } else if (has(annotation.messageContains())) {
      return s -> s.contains(annotation.messageContains());
    } else if (has(annotation.messageIs())) {
      return s -> s.equals(annotation.messageIs());
    } else {
      // the default
      return s -> true;
    }
  }

  private boolean has(String incoming) {
    return incoming != null && incoming.length() > 0;
  }
}


package io.github.glytching.junit.extension.util;

import org.junit.jupiter.api.extension.ExtensionContext;

public final class ExtensionUtil {

  // this is a utility class - hide the public ctor
  private ExtensionUtil() {}

  /**
   * Creates a {@link ExtensionContext.Store} for a given {@code extensionContext}. A {@link
   * ExtensionContext.Store} is bound to an {@link ExtensionContext} so different test invocations
   * do not share the same store. For example a test invocation on {@code ClassA.testMethodA} will
   * have a different {@link ExtensionContext.Store} instance to that associated with a test
   * invocation on {@code ClassA.testMethodB} or test invocation on {@code ClassC.testMethodC}.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @return a {@link ExtensionContext.Store} for the given {@code extensionContext}
   */
  public static ExtensionContext.Store getStore(ExtensionContext extensionContext, Class clazz) {
    return extensionContext.getStore(namespace(extensionContext, clazz));
  }

  /**
   * Creates a {@link ExtensionContext.Namespace} in which extension state is stored on creation for
   * post execution destruction. Storing data in a custom namespace prevents accidental cross
   * pollination of data between extensions and between different invocations within the lifecycle
   * of a single extension.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @return a {@link ExtensionContext.Namespace} describing the scope for an extension
   */
  private static ExtensionContext.Namespace namespace(
      ExtensionContext extensionContext, Class clazz) {
    return ExtensionContext.Namespace.create(clazz, extensionContext);
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.system;

import org.junit.jupiter.api.extension.*;

import java.lang.reflect.AnnotatedElement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static io.github.glytching.junit.extension.util.ExtensionUtil.getStore;
import static org.junit.platform.commons.util.AnnotationUtils.isAnnotated;

/**
 * The system property extension sets system properties before test execution and unsets them on
 * completion. More specifically:
 *
 *

 *
If a new system property was added then it is removed after test execution completes
 *
If an existing system property was overwritten then its original value is reinstated after
 *       test execution completes
 *

 *
 *
System properties can be injected into your test or test case with either of the following
 * approaches:
 *
 *

 *
Class level annotation. Note: you cannot annotate a base test case and expect the extension
 *       to be engaged for its children. So, if you want a system property to be available for all
 *       test methods in a Class {@code X} which extends {@code Y} then you must add the annotation
 *       to Class {@code X} not to Class {@code Y}. For example:
 *

 *  @SystemProperty(name = "nameA", value = "valueA")
 *  public class MyTest {
 *      // ...
 *  }
 *

 *
Parameter injection into a {@code @Test} method. For example:
 *

 *  @Test
 *  @SystemProperty(name = "nameA", value = "valueA")
 *  public void testUsingSystemProperty() {
 *      // ...
 *  }
 *

 *

 *
 *
The {@link SystemProperty} annotation is repeatable.
 *
 *
Usage examples:
 *
 *
Declaring system properties at class level:
 *
 *

 *  @SystemProperty(name = "nameA", value = "valueA")
 *  @SystemProperty(name = "nameB", value = "valueB")
 * public class MyTest {
 *
 *     @Test
 *     public void test() {
 *         // the system properties nameA:valueA, nameB:valueB have been set
 *         // ...
 *     }
 * }
 *

 *
 *
Declaring system properties at method level:
 *
 *

 * public class MyTest {
 *
 *     @Test
 *     @SystemProperty(name = "nameA", value = "valueA")
 *     @SystemProperty(name = "nameB", value = "valueB")
 *     public void testUsingSystemProperties(TemporaryFolder temporaryFolder) {
 *         // the system properties nameA:valueA, nameB:valueB have been set
 *         // ...
 *     }
 *
 *     @Test
 *     @SystemProperty(name = "nameC", value = "valueC")
 *     public void testUsingSystemProperty(TemporaryFolder temporaryFolder) {
 *         // the system property nameC:valueC has been set
 *         // ...
 *     }
 *
 *     @Test
 *     public void testWithoutSystemProperties() {
 *         // the system properties nameA:valueA, nameB:valueB, nameC:valueC have *not* been set
 *         // ...
 *     }
 * }
 *

 *
 * @since 1.0.0
 */
public class SystemPropertyExtension
    implements AfterEachCallback, BeforeEachCallback, BeforeAllCallback, AfterAllCallback {

  private static final String KEY = "restoreContext";

  /**
   * If the current test class has a system property annotation(s) then create a {@link
   * RestoreContext} representing the annotation(s). This causes the requested system properties to
   * be set and retains a copy of pre-set values for reinstatement after test execution.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @throws Exception
   */
  @Override
  public void beforeAll(ExtensionContext extensionContext) throws Exception {
    List<SystemProperty> systemProperties =
        getSystemProperties(extensionContext.getRequiredTestClass());
    if (!systemProperties.isEmpty()) {
      RestoreContext.Builder builder = RestoreContext.createBuilder();
      for (SystemProperty systemProperty : systemProperties) {
        builder.addPropertyName(systemProperty.name());
        if (System.getProperty(systemProperty.name()) != null) {
          builder.addRestoreProperty(
              systemProperty.name(), System.getProperty(systemProperty.name()));
        }

        set(systemProperty);
      }
      writeRestoreContext(extensionContext, builder.build());
    }
  }

  /**
   * If a {@link RestoreContext} exists for the given {@code extensionContext} then restore it i.e.
   * unset any system properties which were set in {@link #beforeAll(ExtensionContext)} for this
   * {@code extensionContext} and reinstate original value, if applicable.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @throws Exception
   */
  @Override
  public void afterAll(ExtensionContext extensionContext) throws Exception {
    RestoreContext restoreContext = readRestoreContext(extensionContext);
    if (restoreContext != null) {
      restoreContext.restore();
    }
  }

  /**
   * If the current test method has a system property annotation(s) then create a {@link
   * RestoreContext} representing the annotation(s). This causes the requested system properties to
   * be set and retains a copy of pre-set values for reinstatement after test execution.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @throws Exception
   */
  @Override
  public void beforeEach(ExtensionContext extensionContext) throws Exception {
    List<SystemProperty> systemProperties =
        getSystemProperties(extensionContext.getRequiredTestMethod());
    if (!systemProperties.isEmpty()) {
      RestoreContext.Builder builder = RestoreContext.createBuilder();
      for (SystemProperty systemProperty : systemProperties) {
        builder.addPropertyName(systemProperty.name());
        if (System.getProperty(systemProperty.name()) != null) {
          builder.addRestoreProperty(
              systemProperty.name(), System.getProperty(systemProperty.name()));
        }

        set(systemProperty);
      }
      writeRestoreContext(extensionContext, builder.build());
    }
  }

  /**
   * If a {@link RestoreContext} exists for the given {@code extensionContext} then restore it i.e.
   * unset any system properties which were set in {@link #beforeEach(ExtensionContext)} for this
   * {@code extensionContext} and reinstate original value, if applicable.
   *
   * @param extensionContext the context in which the current test or container is being
   *     executed
   * @throws Exception
   */
  @Override
  public void afterEach(ExtensionContext extensionContext) throws Exception {
    RestoreContext restoreContext = readRestoreContext(extensionContext);
    if (restoreContext != null) {
      restoreContext.restore();
    }
  }

  /**
   * Get a collection of {@link SystemProperty} for the given {@code annotatedElement}. If the given
   * {@code annotatedElement} has no such annotations then an empty list is returned, if the given
   * {@code annotatedElement} is annotated with {@link SystemProperty} then a list with one element
   * is returned, if the given {@code annotatedElement} is annotated with {@link SystemProperties}
   * then a list with one element for each of the repeated {@link SystemProperty} values is
   * returned.
   *
   *
This is essentially a shortcut for logic such as: 'does this element have the {@link
   * SystemProperty} annotation, if not does it have the {@link SystemProperties}' followed by
   * gathering these annotation values.
   *
   * @param annotatedElement either a test class or a test method which may be annotated with a
   *     system property annotation
   * @return 0..* {@link SystemProperty} elements
   */
  private List<SystemProperty> getSystemProperties(AnnotatedElement annotatedElement) {
    List<SystemProperty> systemProperties = new ArrayList<>();
    if (isAnnotated(annotatedElement, SystemProperties.class)) {
      // gather the repeating system property values
      systemProperties.addAll(
          Arrays.asList(annotatedElement.getAnnotation(SystemProperties.class).value()));
    }
    if (isAnnotated(annotatedElement, SystemProperty.class)) {
      // add the single system property value
      systemProperties.add(annotatedElement.getAnnotation(SystemProperty.class));
    }
    return systemProperties;
  }

  private void set(SystemProperty systemProperty) {
    System.setProperty(systemProperty.name(), systemProperty.value());
  }

  private void writeRestoreContext(
      ExtensionContext extensionContext, RestoreContext restoreContext) {
    getStore(extensionContext, this.getClass()).getOrComputeIfAbsent(KEY, key -> restoreContext);
  }

  private RestoreContext readRestoreContext(ExtensionContext extensionContext) {
    return getStore(extensionContext, this.getClass()).get(KEY, RestoreContext.class);
  }
}


/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.github.glytching.junit.extension.system;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * A context object which encapsulates what the system property extension did. This allows us to
 * reverse any changes made by the extension after test execution completes. For example:
 *
 *

 *
If a new system property was added then we remove it
 *
If an existing system property was overwritten then we reinstate its original value
 *

 */
final class RestoreContext {
  private final Set<String> propertyNames;
  private final Map<String, String> restoreProperties;

  /**
   * Created using the {@link Builder}.
   *
   * @param propertyNames
   * @param restoreProperties
   */
  private RestoreContext(Set<String> propertyNames, Map<String, String> restoreProperties) {
    this.propertyNames = new HashSet<>(propertyNames);
    this.restoreProperties = new HashMap<>(restoreProperties);
  }

  public static Builder createBuilder() {
    return new Builder();
  }

  /**
   * Reverse the system property 'sets' performed on behalf of this restore context.
   *
   *
For each entry in {@link #propertyNames}, if {@link #restoreProperties} contains an entry
   * then reset the system property with the value from {@link #restoreProperties} otherwise just
   * remove the system property for that property name.
   */
  public void restore() {
    for (String propertyName : propertyNames) {
      if (restoreProperties.containsKey(propertyName)) {
        // reinstate the original value
        System.setProperty(propertyName, restoreProperties.get(propertyName));
      } else {
        // remove the (previously unset) property
        System.clearProperty(propertyName);
      }
    }
  }

  /**
   * Simple builder implementation allowing a {@link RestoreContext} to be built up as we walk
   * through system property configuration.
   */
  final static class Builder {
    private final Set<String> properties;
    private final Map<String, String> restoreProperties;

    private Builder() {
      properties = new HashSet<>();
      restoreProperties = new HashMap<>();
    }

    void addPropertyName(String propertyName) {
      properties.add(propertyName);
    }

    void addRestoreProperty(String propertyName, String propertyValue) {
      restoreProperties.put(propertyName, propertyValue);
    }

    RestoreContext build() {
      return new RestoreContext(properties, restoreProperties);
    }
  }
}


/*
 * Copyright 2010-2018 The jdependency developers.
 *
 * 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 org.vafer.jdependency.utils;

import java.util.Iterator;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
import java.io.IOException;

/**
 * internal - do not use
 */

final public class StreamUtils {

    // public static <T> Stream<T> asStream(Iterator<T> sourceIterator) {
    //     return asStream(sourceIterator, false);
    // }

    // public static <T> Stream<T> asStream(Iterator<T> sourceIterator, boolean parallel) {
    //     Iterable<T> iterable = () -> sourceIterator;
    //     return StreamSupport.stream(iterable.spliterator(), parallel);
    // }

    public static Stream<JarEntry> asStream( final JarInputStream pInputStream ) {
        return StreamSupport.stream(Spliterators.spliteratorUnknownSize(
            new Iterator<JarEntry>() {

                JarEntry entry = null;

                public boolean hasNext() {
                    try {
                        if (entry == null) {
                            entry = pInputStream.getNextJarEntry();
                        }
                        return entry != null;
                    } catch(IOException e) {
                        throw new RuntimeException(e);
                    }
                }

                public JarEntry next() {
                    try {
                        JarEntry result = entry != null
                          ? entry
                          : pInputStream.getNextJarEntry();
                        entry = null;
                        return result;
                    } catch(IOException e) {
                        throw new RuntimeException(e);
                    }
                }

            }, Spliterator.IMMUTABLE), false);
    }
}


/*
 * Copyright 2010-2018 The jdependency developers.
 *
 * 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 org.vafer.jdependency.utils;

import java.io.IOException;
import java.io.InputStream;
import java.io.File;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullOutputStream;
import org.objectweb.asm.ClassReader;
import org.vafer.jdependency.asm.DependenciesClassAdapter;

/**
 * internal - do not use
 */
final public class DependencyUtils {

    /*
    public static Set<String> getDependenciesOfJar( final InputStream pInputStream ) throws IOException {

        final JarInputStream inputStream = new JarInputStream(pInputStream);
        final NullOutputStream nullStream = new NullOutputStream();
        final Set<String> dependencies = new HashSet<String>();

        try {
            while (true) {
                final JarEntry entry = inputStream.getNextJarEntry();

                if (entry == null) {
                    break;
                }

                if (entry.isDirectory()) {
                    // ignore directory entries
                    IOUtils.copy(inputStream, nullStream);
                    continue;
                }

                final String name = entry.getName();

                if (name.endsWith(".class")) {
                    final DependenciesClassAdapter v = new DependenciesClassAdapter();
                    new ClassReader( inputStream ).accept( v, 0 );
                    dependencies.addAll(v.getDependencies());
                } else {
                    IOUtils.copy(inputStream, nullStream);
                }
            }
        } finally {
            inputStream.close();
        }

        return dependencies;
    }
    */

    public static Set<String> getDependenciesOfClass( final InputStream pInputStream ) throws IOException {
        final DependenciesClassAdapter v = new DependenciesClassAdapter();
        new ClassReader( pInputStream ).accept( v, ClassReader.EXPAND_FRAMES );
        final Set<String> depNames = v.getDependencies();
        return depNames;
    }

    public static Set<String> getDependenciesOfClass( final Class<?> pClass ) throws IOException {
        final String resource = "/" + pClass.getName().replace('.', '/') + ".class";
        return getDependenciesOfClass(pClass.getResourceAsStream(resource));
    }

}


/**
 * Copyright (C) 2015 ogaclejapan <ogaclejapan@gmail.com>
 *
 * 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 com.ogaclejapan.rx.binding;

import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.text.TextUtils;
import android.util.TypedValue;
import android.view.View;
import android.webkit.WebView;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.Map;

public class RxActions {

  //__/__/__/__/__/__/__/__/__/__/
  // AbsListView
  //__/__/__/__/__/__/__/__/__/__/

  private RxActions() {
    //No instances.
  }

  //__/__/__/__/__/__/__/__/__/__/
  // ImageView
  //__/__/__/__/__/__/__/__/__/__/

  public static <T extends AbsListView> RxAction<T, String> setFilterText() {
    return new RxAction<T, String>() {
      @Override
      public void call(T view, String filterText) {
        if (TextUtils.isEmpty(filterText)) {
          view.clearTextFilter();
        } else {
          view.setFilterText(filterText);
        }
      }
    };
  }

  public static <T extends ImageView> RxAction<T, Integer> setImageResource() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer resId) {
        view.setImageResource(resId);
      }
    };
  }

  public static <T extends ImageView> RxAction<T, Bitmap> setImageBitmap() {
    return new RxAction<T, Bitmap>() {
      @Override
      public void call(T view, Bitmap bm) {
        view.setImageBitmap(bm);
      }
    };
  }

  public static <T extends ImageView> RxAction<T, Drawable> setImageDrawable() {
    return new RxAction<T, Drawable>() {
      @Override
      public void call(T view, Drawable drawable) {
        view.setImageDrawable(drawable);
      }
    };
  }

  //__/__/__/__/__/__/__/__/__/__/
  // TextView
  //__/__/__/__/__/__/__/__/__/__/

  public static <T extends ImageView> RxAction<T, Integer> setImageLevel() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer level) {
        view.setImageLevel(level);
      }
    };
  }

  public static <T extends TextView> RxAction<T, CharSequence> setText() {
    return new RxAction<T, CharSequence>() {
      @Override
      public void call(T view, CharSequence s) {
        view.setText(s);
      }
    };
  }

  public static <T extends TextView> RxAction<T, Integer> setTextFromResId() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer resId) {
        view.setText(resId);
      }
    };
  }

  public static <T extends TextView> RxAction<T, Integer> setTextColor() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer color) {
        view.setTextColor(color);
      }
    };
  }

  //__/__/__/__/__/__/__/__/__/__/
  // View
  //__/__/__/__/__/__/__/__/__/__/

  public static <T extends TextView> RxAction<T, Float> setTextSize() {
    return new RxAction<T, Float>() {
      @Override
      public void call(T view, Float size) {
        view.setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
      }
    };
  }

  public static <T extends View> RxAction<T, Boolean> setVisibility() {
    return setVisibility(View.GONE);
  }

  public static <T extends View> RxAction<T, Boolean> setVisibility(final int hiddenVisibility) {
    return new RxAction<T, Boolean>() {
      @Override
      public void call(T view, Boolean visible) {
        final int visibility = (visible) ? View.VISIBLE : hiddenVisibility;
        if (view.getVisibility() != visibility) {
          view.setVisibility(visibility);
        }
      }
    };
  }

  public static <T extends View> RxAction<T, Boolean> setSelected() {
    return new RxAction<T, Boolean>() {
      @Override
      public void call(T view, Boolean selected) {
        view.setSelected(selected);
      }
    };
  }

  public static <T extends View> RxAction<T, Boolean> setEnabled() {
    return new RxAction<T, Boolean>() {
      @Override
      public void call(T view, Boolean enabled) {
        view.setEnabled(enabled);
      }
    };
  }

  public static <T extends View> RxAction<T, Integer> setBackgroundResource() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer resId) {
        view.setBackgroundResource(resId);
      }
    };
  }

  //__/__/__/__/__/__/__/__/__/__/
  // WebView
  //__/__/__/__/__/__/__/__/__/__/

  public static <T extends View> RxAction<T, Integer> setBackgroundColor() {
    return new RxAction<T, Integer>() {
      @Override
      public void call(T view, Integer color) {
        view.setBackgroundColor(color);
      }
    };
  }

  public static <T extends WebView> RxAction<T, String> loadUrl() {
    return new RxAction<T, String>() {
      @Override
      public void call(T webview, String url) {
        webview.loadUrl(url);
      }
    };
  }

  public static <T extends WebView> RxAction<T, String> loadUrl(
      final Map<String, String> addtionalHttpHeaders) {
    return new RxAction<T, String>() {
      @Override
      public void call(T webview, String url) {
        webview.loadUrl(url, addtionalHttpHeaders);
      }
    };
  }

  public static <T extends WebView> RxAction<T, String> loadData(final String mimeType,
      final String enconding) {
    return new RxAction<T, String>() {
      @Override
      public void call(T webview, String data) {
        webview.loadData(data, mimeType, enconding);
      }
    };
  }

  public static <T extends WebView> RxAction<T, String> loadDataWithBaseURL() {
    return loadDataWithBaseURL("file:///android_asset/", "text/html", "utf-8", null);
  }

  public static <T extends WebView> RxAction<T, String> loadDataWithBaseURL(
      final String baseUrl) {
    return loadDataWithBaseURL(baseUrl, "text/html", "utf-8", null);
  }

  public static <T extends WebView> RxAction<T, String> loadDataWithBaseURL(final String baseUrl,
      final String mimeType, final String enconding, final String failUrl) {
    return new RxAction<T, String>() {
      @Override
      public void call(final T webview, final String data) {
        webview.loadDataWithBaseURL(baseUrl, data, mimeType, enconding, failUrl);
      }
    };
  }

}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2015, Red Hat, Inc., and individual contributors as indicated
 * by the @authors tag.
 *
 * 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 org.jboss.util.collection;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.io.Serializable;

/**
 * LazyMap.
 * It's serializable if the elements are serializable.
 *
 * @param <K> the key type
 * @param <V> the value type
 * @author Adrian Brock
 * @version $Revision$
 */
public class LazyMap<K, V> implements Map<K, V>, Serializable
{
   /** The serialVersionUID */
   private static final long serialVersionUID = 1L;
   /** The delegate map */
   private Map<K, V> delegate = Collections.emptyMap();

   /**
    * Create the map implementation
    *
    * @return the map
    */
   private Map<K, V> createImplementation()
   {
      if (delegate instanceof HashMap == false)
         return new HashMap<K, V>(delegate);
      return delegate;
   }

   public void clear()
   {
      delegate = Collections.emptyMap();
   }

   public boolean containsKey(Object key)
   {
      return delegate.containsKey(key);
   }

   public boolean containsValue(Object value)
   {
      return delegate.containsValue(value);
   }

   public Set<Entry<K, V>> entrySet()
   {
      return delegate.entrySet();
   }

   public V get(Object key)
   {
      return delegate.get(key);
   }

   public boolean isEmpty()
   {
      return delegate.isEmpty();
   }

   public Set<K> keySet()
   {
      return delegate.keySet();
   }

   public V put(K key, V value)
   {
      if (delegate.isEmpty())
      {
         delegate = Collections.singletonMap(key, value);
         return null;
      }
      else
      {
         delegate = createImplementation();
         return delegate.put(key, value);
      }
   }

   public void putAll(Map<? extends K, ? extends V> t)
   {
      delegate = createImplementation();
      delegate.putAll(t);
   }

   public V remove(Object key)
   {
      delegate = createImplementation();
      return delegate.remove(key);
   }

   public int size()
   {
      return delegate.size();
   }

   public Collection<V> values()
   {
      return delegate.values();
   }

   @Override
   public String toString()
   {
      return delegate.toString();
   }
}