text:
/*
 * 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.facebook.presto.operator;

import com.facebook.presto.ExceededMemoryLimitException;
import com.facebook.presto.RowPagesBuilder;
import com.facebook.presto.memory.LocalMemoryContext;
import com.facebook.presto.operator.HashBuilderOperator.HashBuilderOperatorFactory;
import com.facebook.presto.operator.ValuesOperator.ValuesOperatorFactory;
import com.facebook.presto.operator.exchange.LocalExchange;
import com.facebook.presto.operator.exchange.LocalExchange.LocalExchangeSinkFactory;
import com.facebook.presto.operator.exchange.LocalExchangeSinkOperator.LocalExchangeSinkOperatorFactory;
import com.facebook.presto.operator.exchange.LocalExchangeSourceOperator.LocalExchangeSourceOperatorFactory;
import com.facebook.presto.spi.Page;
import com.facebook.presto.spi.block.Block;
import com.facebook.presto.spi.type.Type;
import com.facebook.presto.spiller.GenericPartitioningSpillerFactory;
import com.facebook.presto.spiller.PartitioningSpillerFactory;
import com.facebook.presto.spiller.SingleStreamSpiller;
import com.facebook.presto.spiller.SingleStreamSpillerFactory;
import com.facebook.presto.sql.gen.JoinFilterFunctionCompiler.JoinFilterFunctionFactory;
import com.facebook.presto.sql.gen.JoinProbeCompiler;
import com.facebook.presto.sql.planner.plan.PlanNodeId;
import com.facebook.presto.testing.MaterializedResult;
import com.facebook.presto.testing.TestingTaskContext;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;
import com.google.common.primitives.Ints;
import com.google.common.util.concurrent.ListenableFuture;
import io.airlift.units.DataSize;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.OptionalInt;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.stream.IntStream;

import static com.facebook.presto.RowPagesBuilder.rowPagesBuilder;
import static com.facebook.presto.SessionTestUtils.TEST_SESSION;
import static com.facebook.presto.operator.OperatorAssertion.assertOperatorEquals;
import static com.facebook.presto.operator.OperatorAssertion.dropChannel;
import static com.facebook.presto.operator.OperatorAssertion.toPages;
import static com.facebook.presto.operator.OperatorAssertion.toPagesPartial;
import static com.facebook.presto.operator.OperatorAssertion.without;
import static com.facebook.presto.spi.type.BigintType.BIGINT;
import static com.facebook.presto.spi.type.VarcharType.VARCHAR;
import static com.facebook.presto.sql.planner.SystemPartitioningHandle.FIXED_HASH_DISTRIBUTION;
import static com.facebook.presto.testing.assertions.Assert.assertEquals;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.Iterators.singletonIterator;
import static com.google.common.collect.Iterators.unmodifiableIterator;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static io.airlift.concurrent.MoreFutures.getFutureValue;
import static io.airlift.concurrent.Threads.daemonThreadsNamed;
import static io.airlift.testing.Assertions.assertEqualsIgnoreOrder;
import static io.airlift.units.DataSize.Unit.BYTE;
import static java.util.Arrays.asList;
import static java.util.Collections.nCopies;
import static java.util.Collections.singletonList;
import static java.util.Objects.requireNonNull;
import static java.util.concurrent.Executors.newCachedThreadPool;

@Test(singleThreaded = true)
public class TestHashJoinOperator
{
    private static final int PARTITION_COUNT = 4;
    private static final LookupJoinOperators LOOKUP_JOIN_OPERATORS = new LookupJoinOperators(new JoinProbeCompiler());
    private static final SingleStreamSpillerFactory SINGLE_STREAM_SPILLER_FACTORY = new DummySpillerFactory();
    private static final PartitioningSpillerFactory PARTITIONING_SPILLER_FACTORY = new GenericPartitioningSpillerFactory(SINGLE_STREAM_SPILLER_FACTORY);
    private ExecutorService executor;

    @BeforeMethod
    public void setUp()
    {
        executor = newCachedThreadPool(daemonThreadsNamed("test-%s"));
    }

    @AfterMethod(alwaysRun = true)
    public void tearDown()
    {
        executor.shutdownNow();
    }

    @DataProvider(name = "hashEnabledValues")
    public static Object[][] hashEnabledValuesProvider()
    {
        return new Object[][] {
                {true, true, true},
                {true, true, false},
                {true, false, true},
                {true, false, false},
                {false, true, true},
                {false, true, false},
                {false, false, true},
                {false, false, false}};
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testInnerJoin(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT))
                .addSequencePage(10, 20, 30, 40);
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT));
        List<Page> probeInput = probePages
                .addSequencePage(1000, 0, 1000, 2000)
                .build();
        OperatorFactory joinOperatorFactory = innerJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probePages.getTypesWithoutHash(), buildPages.getTypesWithoutHash()))
                .row("20", 1020L, 2020L, "20", 30L, 40L)
                .row("21", 1021L, 2021L, "21", 31L, 41L)
                .row("22", 1022L, 2022L, "22", 32L, 42L)
                .row("23", 1023L, 2023L, "23", 33L, 43L)
                .row("24", 1024L, 2024L, "24", 34L, 44L)
                .row("25", 1025L, 2025L, "25", 35L, 45L)
                .row("26", 1026L, 2026L, "26", 36L, 46L)
                .row("27", 1027L, 2027L, "27", 37L, 47L)
                .row("28", 1028L, 2028L, "28", 38L, 48L)
                .row("29", 1029L, 2029L, "29", 39L, 49L)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "testInnerJoinWithSpillDataProvider")
    public void testInnerJoinWithSpill(boolean probeHashEnabled, List<WhenSpill> whenSpill)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        RowPagesBuilder buildPages = rowPagesBuilder(ImmutableList.of(VARCHAR, BIGINT))
                .addSequencePage(4, 20, 200)
                .addSequencePage(4, 30, 300)
                .addSequencePage(4, 40, 400);

        BuildSideSetup buildSideSetup = setupBuildSide(true, taskContext, Ints.asList(0), buildPages, Optional.empty(), true);
        List<Driver> buildDrivers = buildSideSetup.buildDrivers;
        int buildOperatorCount = buildDrivers.size();
        checkState(buildOperatorCount == whenSpill.size());
        LookupSourceFactory lookupSourceFactory = buildSideSetup.lookupSourceFactory;

        // probe
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT))
                .addSequencePage(30, 0, 123_000)
                .addSequencePage(10, 30, 123_000);

        try (OperatorFactory joinOperatorFactory = innerJoinOperatorFactory(lookupSourceFactory, probePages);
                Operator joinOperator = joinOperatorFactory.createOperator(taskContext.addPipelineContext(0, true, true).addDriverContext())) {
            // build LookupSource

            ListenableFuture<LookupSourceProvider> lookupSourceProvider = buildSideSetup.lookupSourceFactory.createLookupSourceProvider();
            List<Boolean> revoked = new ArrayList<>(nCopies(buildOperatorCount, false));
            while (!lookupSourceProvider.isDone()) {
                for (int i = 0; i < buildOperatorCount; i++) {
                    buildDrivers.get(i).process();
                    Operator buildOperator = buildSideSetup.buildOperators.get(i);
                    if (whenSpill.get(i) == WhenSpill.DURING_BUILD && buildOperator.getOperatorContext().getReservedRevocableBytes() > 0) {
                        checkState(!lookupSourceProvider.isDone(), "Too late, LookupSource already done");
                        revokeMemory(buildOperator);
                        revoked.set(i, true);
                    }
                }
            }
            getFutureValue(lookupSourceProvider).close();
            assertEquals(revoked, whenSpill.stream().map(WhenSpill.DURING_BUILD::equals).collect(toImmutableList()), "Some operators not spilled before LookupSource built");

            for (int i = 0; i < buildOperatorCount; i++) {
                if (whenSpill.get(i) == WhenSpill.AFTER_BUILD) {
                    revokeMemory(buildSideSetup.buildOperators.get(i));
                }
            }

            for (Driver buildDriver : buildDrivers) {
                runDriver(executor, buildDriver);
            }

            // process join
            Iterator<Page> input = probePages.build().iterator();
            List<Page> actualPages = new ArrayList<>();
            actualPages.addAll(toPagesPartial(joinOperator, singletonIterator(input.next())));

            for (int i = 0; i < buildOperatorCount; i++) {
                if (whenSpill.get(i) == WhenSpill.DURING_USAGE) {
                    checkState(input.hasNext(), "spill requested DURING_USAGE, but input is kind of exhausted");
                    scheduleRevokeMemory(buildSideSetup.buildOperators.get(i));
                }
            }

            actualPages.addAll(toPages(joinOperator, input));

            // expected
            MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probePages.getTypesWithoutHash(), buildPages.getTypesWithoutHash()))
                    .row("20", 123_020L, "20", 200L)
                    .row("21", 123_021L, "21", 201L)
                    .row("22", 123_022L, "22", 202L)
                    .row("23", 123_023L, "23", 203L)
                    .row("30", 123_000L, "30", 300L)
                    .row("31", 123_001L, "31", 301L)
                    .row("32", 123_002L, "32", 302L)
                    .row("33", 123_003L, "33", 303L)
                    .build();

            List<Type> types = joinOperator.getTypes();
            if (probePages.getHashChannel().isPresent()) {
                List<Integer> hashChannels = ImmutableList.of(probePages.getHashChannel().get());
                actualPages = dropChannel(actualPages, hashChannels);
                types = without(types, hashChannels);
            }

            MaterializedResult actual = OperatorAssertion.toMaterializedResult(joinOperator.getOperatorContext().getSession(), types, actualPages);
            assertEqualsIgnoreOrder(actual.getMaterializedRows(), expected.getMaterializedRows());
        }
    }

    private enum WhenSpill
    {
        DURING_BUILD, AFTER_BUILD, DURING_USAGE, NEVER
    }

    @DataProvider
    public Object[][] testInnerJoinWithSpillDataProvider()
    {
        List<Object[]> result = new ArrayList<>();
        for (boolean probeHashEnabled : ImmutableList.of(false, true)) {
            for (WhenSpill whenSpill : WhenSpill.values()) {
                // spill all
                result.add(new Object[] {probeHashEnabled, nCopies(PARTITION_COUNT, whenSpill)});

                if (whenSpill != WhenSpill.NEVER) {
                    // spill one
                    result.add(new Object[] {probeHashEnabled, concat(singletonList(whenSpill), nCopies(PARTITION_COUNT - 1, WhenSpill.NEVER))});
                }
            }

            result.add(new Object[] {probeHashEnabled, concat(asList(WhenSpill.DURING_BUILD, WhenSpill.AFTER_BUILD), nCopies(PARTITION_COUNT - 2, WhenSpill.NEVER))});
            result.add(new Object[] {probeHashEnabled, concat(asList(WhenSpill.DURING_BUILD, WhenSpill.DURING_USAGE), nCopies(PARTITION_COUNT - 2, WhenSpill.NEVER))});
        }

        return result.stream().toArray(Object[][]::new);
    }

    private static void revokeMemory(Operator operator)
    {
        getFutureValue(operator.startMemoryRevoke());
        operator.finishMemoryRevoke();
    }

    private static void scheduleRevokeMemory(Operator operator)
    {
        operator.getOperatorContext().requestMemoryRevoking();
    }

    private static <T> List<T> concat(List<T> initialElements, List<T> moreElements)
    {
        return ImmutableList.copyOf(Iterables.concat(initialElements, moreElements));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testInnerJoinWithNullProbe(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), buildTypes)
                .row("a")
                .row("b")
                .row("c");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b")
                .build();
        OperatorFactory joinOperatorFactory = innerJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypesWithoutHash()))
                .row("a", "a")
                .row("a", "a")
                .row("b", "b")
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testInnerJoinWithNullBuild(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), buildTypes)
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = innerJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row("a", "a")
                .row("b", "b")
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testInnerJoinWithNullOnBothSides(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), buildTypes)
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row((String) null)
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = innerJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row("a", "a")
                .row("b", "b")
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testProbeOuterJoin(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT))
                .addSequencePage(10, 20, 30, 40);
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .addSequencePage(15, 20, 1020, 2020)
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("20", 1020L, 2020L, "20", 30L, 40L)
                .row("21", 1021L, 2021L, "21", 31L, 41L)
                .row("22", 1022L, 2022L, "22", 32L, 42L)
                .row("23", 1023L, 2023L, "23", 33L, 43L)
                .row("24", 1024L, 2024L, "24", 34L, 44L)
                .row("25", 1025L, 2025L, "25", 35L, 45L)
                .row("26", 1026L, 2026L, "26", 36L, 46L)
                .row("27", 1027L, 2027L, "27", 37L, 47L)
                .row("28", 1028L, 2028L, "28", 38L, 48L)
                .row("29", 1029L, 2029L, "29", 39L, 49L)
                .row("30", 1030L, 2030L, null, null, null)
                .row("31", 1031L, 2031L, null, null, null)
                .row("32", 1032L, 2032L, null, null, null)
                .row("33", 1033L, 2033L, null, null, null)
                .row("34", 1034L, 2034L, null, null, null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testProbeOuterJoinWithFilterFunction(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction((
                (leftPosition, leftBlocks, rightPosition, rightBlocks) -> BIGINT.getLong(rightBlocks[1], rightPosition) >= 1025));

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT))
                .addSequencePage(10, 20, 30, 40);
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.of(filterFunction));

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR, BIGINT, BIGINT);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .addSequencePage(15, 20, 1020, 2020)
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("20", 1020L, 2020L, null, null, null)
                .row("21", 1021L, 2021L, null, null, null)
                .row("22", 1022L, 2022L, null, null, null)
                .row("23", 1023L, 2023L, null, null, null)
                .row("24", 1024L, 2024L, null, null, null)
                .row("25", 1025L, 2025L, "25", 35L, 45L)
                .row("26", 1026L, 2026L, "26", 36L, 46L)
                .row("27", 1027L, 2027L, "27", 37L, 47L)
                .row("28", 1028L, 2028L, "28", 38L, 48L)
                .row("29", 1029L, 2029L, "29", 39L, 49L)
                .row("30", 1030L, 2030L, null, null, null)
                .row("31", 1031L, 2031L, null, null, null)
                .row("32", 1032L, 2032L, null, null, null)
                .row("33", 1033L, 2033L, null, null, null)
                .row("34", 1034L, 2034L, null, null, null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullProbe(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), buildTypes)
                .row("a")
                .row("b")
                .row("c");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row(null, null)
                .row(null, null)
                .row("a", "a")
                .row("b", "b")
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullProbeAndFilterFunction(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction((
                (leftPosition, leftBlocks, rightPosition, rightBlocks) -> VARCHAR.getSlice(rightBlocks[0], rightPosition).toStringAscii().equals("a")));

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), buildTypes)
                .row("a")
                .row("b")
                .row("c");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.of(filterFunction));

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row(null, null)
                .row(null, null)
                .row("a", "a")
                .row("b", null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullBuild(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR))
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row("a", "a")
                .row("b", "b")
                .row("c", null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullBuildAndFilterFunction(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction((
                (leftPosition, leftBlocks, rightPosition, rightBlocks) ->
                        ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightBlocks[0], rightPosition).toStringAscii())));

        // build
        List<Type> buildTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR))
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.of(filterFunction));

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildTypes))
                .row("a", "a")
                .row("a", "a")
                .row("b", null)
                .row("c", null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullOnBothSides(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        // build
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR))
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row((String) null)
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypesWithoutHash()))
                .row("a", "a")
                .row("a", "a")
                .row("b", "b")
                .row(null, null)
                .row("c", null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(dataProvider = "hashEnabledValues")
    public void testOuterJoinWithNullOnBothSidesAndFilterFunction(boolean parallelBuild, boolean probeHashEnabled, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = createTaskContext();

        InternalJoinFilterFunction filterFunction = new TestInternalJoinFilterFunction((
                (leftPosition, leftBlocks, rightPosition, rightBlocks) ->
                        ImmutableSet.of("a", "c").contains(VARCHAR.getSlice(rightBlocks[0], rightPosition).toStringAscii())));

        // build
        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR))
                .row("a")
                .row((String) null)
                .row((String) null)
                .row("a")
                .row("b");
        LookupSourceFactory lookupSourceFactory = buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.of(filterFunction));

        // probe
        List<Type> probeTypes = ImmutableList.of(VARCHAR);
        RowPagesBuilder probePages = rowPagesBuilder(probeHashEnabled, Ints.asList(0), probeTypes);
        List<Page> probeInput = probePages
                .row("a")
                .row("b")
                .row((String) null)
                .row("c")
                .build();
        OperatorFactory joinOperatorFactory = probeOuterJoinOperatorFactory(lookupSourceFactory, probePages);

        // expected
        MaterializedResult expected = MaterializedResult.resultBuilder(taskContext.getSession(), concat(probeTypes, buildPages.getTypesWithoutHash()))
                .row("a", "a")
                .row("a", "a")
                .row("b", null)
                .row(null, null)
                .row("c", null)
                .build();

        assertOperatorEquals(joinOperatorFactory, taskContext.addPipelineContext(0, true, true).addDriverContext(), probeInput, expected, true, getHashChannels(probePages, buildPages));
    }

    @Test(expectedExceptions = ExceededMemoryLimitException.class, expectedExceptionsMessageRegExp = "Query exceeded local memory limit of.*", dataProvider = "testMemoryLimitProvider")
    public void testMemoryLimit(boolean parallelBuild, boolean buildHashEnabled)
            throws Exception
    {
        TaskContext taskContext = TestingTaskContext.createTaskContext(executor, TEST_SESSION, new DataSize(100, BYTE));

        RowPagesBuilder buildPages = rowPagesBuilder(buildHashEnabled, Ints.asList(0), ImmutableList.of(VARCHAR, BIGINT, BIGINT))
                .addSequencePage(10, 20, 30, 40);
        buildHash(parallelBuild, taskContext, Ints.asList(0), buildPages, Optional.empty());
    }

    @DataProvider
    public static Object[][] testMemoryLimitProvider()
    {
        return new Object[][] {
                {true, true},
                {true, false},
                {false, true},
                {false, false}};
    }

    private TaskContext createTaskContext()
    {
        return TestingTaskContext.createTaskContext(executor, TEST_SESSION);
    }

    private static List<Integer> getHashChannels(RowPagesBuilder probe, RowPagesBuilder build)
    {
        ImmutableList.Builder<Integer> hashChannels = ImmutableList.builder();
        if (probe.getHashChannel().isPresent()) {
            hashChannels.add(probe.getHashChannel().get());
        }
        if (build.getHashChannel().isPresent()) {
            hashChannels.add(probe.getTypes().size() + build.getHashChannel().get());
        }
        return hashChannels.build();
    }

    private OperatorFactory probeOuterJoinOperatorFactory(LookupSourceFactory lookupSourceFactory, RowPagesBuilder probePages)
    {
        return LOOKUP_JOIN_OPERATORS.probeOuterJoin(
                0,
                new PlanNodeId("test"),
                lookupSourceFactory,
                probePages.getTypes(),
                Ints.asList(0),
                probePages.getHashChannel(),
                Optional.empty(),
                OptionalInt.of(1),
                PARTITIONING_SPILLER_FACTORY);
    }

    private OperatorFactory innerJoinOperatorFactory(LookupSourceFactory lookupSourceFactory, RowPagesBuilder probePages)
    {
        return LOOKUP_JOIN_OPERATORS.innerJoin(
                0,
                new PlanNodeId("test"),
                lookupSourceFactory,
                probePages.getTypes(),
                Ints.asList(0),
                probePages.getHashChannel(),
                Optional.empty(),
                OptionalInt.of(1),
                PARTITIONING_SPILLER_FACTORY);
    }

    private BuildSideSetup setupBuildSide(
            boolean parallelBuild,
            TaskContext taskContext,
            List<Integer> hashChannels,
            RowPagesBuilder buildPages,
            Optional<InternalJoinFilterFunction> filterFunction,
            boolean spillEnabled)
    {
        Optional<JoinFilterFunctionFactory> filterFunctionFactory = filterFunction
                .map(function -> (session, addresses, channels) -> new StandardJoinFilterFunction(function, addresses, channels, Optional.empty()));

        int partitionCount = parallelBuild ? PARTITION_COUNT : 1;
        LocalExchange localExchange = new LocalExchange(FIXED_HASH_DISTRIBUTION, partitionCount, buildPages.getTypes(), hashChannels, buildPages.getHashChannel());
        LocalExchangeSinkFactory sinkFactory = localExchange.createSinkFactory();
        sinkFactory.noMoreSinkFactories();

        // collect input data into the partitioned exchange
        DriverContext collectDriverContext = taskContext.addPipelineContext(0, true, true).addDriverContext();
        ValuesOperatorFactory valuesOperatorFactory = new ValuesOperatorFactory(0, new PlanNodeId("values"), buildPages.getTypes(), buildPages.build());
        LocalExchangeSinkOperatorFactory sinkOperatorFactory = new LocalExchangeSinkOperatorFactory(1, new PlanNodeId("sink"), sinkFactory, Function.identity());
        Driver sourceDriver = new Driver(collectDriverContext,
                valuesOperatorFactory.createOperator(collectDriverContext),
                sinkOperatorFactory.createOperator(collectDriverContext));
        valuesOperatorFactory.close();
        sinkOperatorFactory.close();

        while (!sourceDriver.isFinished()) {
            sourceDriver.process();
        }

        // build hash tables
        LocalExchangeSourceOperatorFactory sourceOperatorFactory = new LocalExchangeSourceOperatorFactory(0, new PlanNodeId("source"), localExchange);
        HashBuilderOperatorFactory buildOperatorFactory = new HashBuilderOperatorFactory(
                1,
                new PlanNodeId("build"),
                buildPages.getTypes(),
                rangeList(buildPages.getTypes().size()),
                ImmutableMap.of(),
                hashChannels,
                buildPages.getHashChannel(),
                false,
                filterFunctionFactory,
                100,
                partitionCount,
                new PagesIndex.TestingFactory(),
                spillEnabled,
                SINGLE_STREAM_SPILLER_FACTORY);
        PipelineContext buildPipeline = taskContext.addPipelineContext(1, true, true);

        List<Driver> buildDrivers = new ArrayList<>();
        List<Operator> buildOperators = new ArrayList<>();
        for (int i = 0; i < partitionCount; i++) {
            DriverContext buildDriverContext = buildPipeline.addDriverContext();
            Operator buildOperator = buildOperatorFactory.createOperator(buildDriverContext);
            Driver driver = new Driver(
                    buildDriverContext,
                    sourceOperatorFactory.createOperator(buildDriverContext),
                    buildOperator);
            buildDrivers.add(driver);
            buildOperators.add(buildOperator);
        }

        return new BuildSideSetup(buildOperatorFactory.getLookupSourceFactory(), buildDrivers, buildOperators);
    }

    private LookupSourceFactory buildHash(
            boolean parallelBuild,
            TaskContext taskContext,
            List<Integer> hashChannels,
            RowPagesBuilder buildPages,
            Optional<InternalJoinFilterFunction> filterFunction)
    {
        BuildSideSetup buildSideSetup = setupBuildSide(parallelBuild, taskContext, hashChannels, buildPages, filterFunction, false);
        buildLookupSource(buildSideSetup);
        return buildSideSetup.lookupSourceFactory;
    }

    private void buildLookupSource(BuildSideSetup buildSideSetup)
    {
        requireNonNull(buildSideSetup, "buildSideSetup is null");

        Future<LookupSourceProvider> lookupSourceProvider = buildSideSetup.lookupSourceFactory.createLookupSourceProvider();
        List<Driver> buildDrivers = buildSideSetup.buildDrivers;

        while (!lookupSourceProvider.isDone()) {
            for (Driver buildDriver : buildDrivers) {
                buildDriver.process();
            }
        }
        getFutureValue(lookupSourceProvider).close();

        for (Driver buildDriver : buildDrivers) {
            runDriver(executor, buildDriver);
        }
    }

    /**
     * Runs Driver until it is finished (or executor shut down).
     */
    private static void runDriver(ExecutorService executor, Driver driver)
    {
        executor.execute(() -> {
            if (!driver.isFinished()) {
                driver.process();
                runDriver(executor, driver);
            }
        });
    }

    private static List<Integer> rangeList(int endExclusive)
    {
        return IntStream.range(0, endExclusive)
                .boxed()
                .collect(toImmutableList());
    }

    private static class BuildSideSetup
    {
        private final LookupSourceFactory lookupSourceFactory;
        private final List<Driver> buildDrivers;
        private final List<Operator> buildOperators;

        public BuildSideSetup(LookupSourceFactory lookupSourceFactory, List<Driver> buildDrivers, List<Operator> buildOperators)
        {
            checkArgument(buildDrivers.size() == buildOperators.size());
            this.lookupSourceFactory = requireNonNull(lookupSourceFactory, "lookupSourceFactory is null");
            this.buildDrivers = ImmutableList.copyOf(buildDrivers);
            this.buildOperators = ImmutableList.copyOf(buildOperators);
        }
    }

    private static class TestInternalJoinFilterFunction
            implements InternalJoinFilterFunction
    {
        public interface Lambda
        {
            boolean filter(int leftPosition, Block[] leftBlocks, int rightPosition, Block[] rightBlocks);
        }

        private final Lambda lambda;

        private TestInternalJoinFilterFunction(Lambda lambda)
        {
            this.lambda = lambda;
        }

        @Override
        public boolean filter(int leftPosition, Block[] leftBlocks, int rightPosition, Block[] rightBlocks)
        {
            return lambda.filter(leftPosition, leftBlocks, rightPosition, rightBlocks);
        }
    }

    private static class DummySpillerFactory
            implements SingleStreamSpillerFactory
    {
        @Override
        public SingleStreamSpiller create(List<Type> types, SpillContext spillContext, LocalMemoryContext memoryContext)
        {
            return new SingleStreamSpiller()
            {
                private boolean writing = true;
                private final List<Page> spills = new ArrayList<>();

                @Override
                public ListenableFuture<?> spill(Iterator<Page> pageIterator)
                {
                    checkState(writing, "writing already finished");
                    Iterators.addAll(spills, pageIterator);
                    return immediateFuture(null);
                }

                @Override
                public Iterator<Page> getSpilledPages()
                {
                    writing = false;
                    return unmodifiableIterator(spills.iterator());
                }

                @Override
                public long getSpilledPagesInMemorySize()
                {
                    return spills.stream()
                            .mapToLong(Page::getSizeInBytes)
                            .sum();
                }

                @Override
                public ListenableFuture<List<Page>> getAllSpilledPages()
                {
                    writing = false;
                    return immediateFuture(ImmutableList.copyOf(spills));
                }

                @Override
                public void close()
                {
                    writing = false;
                }
            };
        }
    }
}


text:
<?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;

class CreateTournamentsTable extends Migration {

	/**
	 * Run the migrations.
	 *
	 * @return void
	 */
	public function up()
	{
		Schema::create('tournaments', function(Blueprint $table)
		{
			$table->increments('id');
			$table->integer('game_id');
			$table->integer('min_teams');
			$table->integer('max_teams');
			$table->date('start_date');
			$table->date('end_date');
			$table->string('name', 80)->unique();
			$table->string('description');
			$table->timestamps();
		});
	}

	/**
	 * Reverse the migrations.
	 *
	 * @return void
	 */
	public function down()
	{
		Schema::drop('tournaments');
	}

}

redpajama_set_name: RedPajamaGithub


text:
//
//  DejalActivityView.m
//  Dejal Open Source
//
//  Created by David Sinclair on 2009-07-26.
//  Copyright (c) 2009-2013 Dejal Systems, LLC. All rights reserved.
//
//  Redistribution and use in source and binary forms, with or without modification,
//  are permitted provided that the following conditions are met:
//
//  - Redistributions of source code must retain the above copyright notice,
//    this list of conditions and the following disclaimer.
//
//  - Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.
//
//  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
//  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
//  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
//  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
//  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
//  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
//  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
//  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
//  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
//  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
//  Credit: inspired by Matt Gallagher's LoadingView blog post:
//  http://cocoawithlove.com/2009/04/showing-message-over-iphone-keyboard.html
//

#import "DejalActivityView.h"
#import <QuartzCore/QuartzCore.h>

@interface DejalActivityView ()

@property (nonatomic, strong) UIView *originalView;
@property (nonatomic, strong) UIView *borderView;
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator;
@property (nonatomic, strong) UILabel *activityLabel;

@end

// ----------------------------------------------------------------------------------------
// MARK: -
// ----------------------------------------------------------------------------------------

@implementation DejalActivityView

@synthesize originalView, borderView, activityIndicator, activityLabel, labelWidth, showNetworkActivityIndicator;

static DejalActivityView *dejalActivityView = nil;

/*
 currentActivityView

 Returns the currently displayed activity view, or nil if there isn't one.

 Written by DJS 2009-07.
*/

+ (DejalActivityView *)currentActivityView;
{
    return dejalActivityView;
}

/*
 activityViewForView:

 Creates and adds an activity view centered within the specified view, using the label "Loading...".  Returns the activity view, already added as a subview of the specified view.

 Written by DJS 2009-07.
 Changed by DJS 2010-06 to add "new" prefix to the method name to make it clearer that this returns a retained object.
 Changed by DJS 2011-08 to remove the "new" prefix again.
*/

+ (DejalActivityView *)activityViewForView:(UIView *)addToView;
{
    return [self activityViewForView:addToView withLabel:NSLocalizedString(@"Loading...", @"Default DejalActivtyView label text") width:0];
}

/*
 activityViewForView:withLabel:

 Creates and adds an activity view centered within the specified view, using the specified label.  Returns the activity view, already added as a subview of the specified view.

 Written by DJS 2009-07.
 Changed by DJS 2010-06 to add "new" prefix to the method name to make it clearer that this returns a retained object.
 Changed by DJS 2011-08 to remove the "new" prefix again.
*/

+ (DejalActivityView *)activityViewForView:(UIView *)addToView withLabel:(NSString *)labelText;
{
    return [self activityViewForView:addToView withLabel:labelText width:0];
}

/*
 activityViewForView:withLabel:width:

 Creates and adds an activity view centered within the specified view, using the specified label and a fixed label width.  The fixed width is useful if you want to change the label text while the view is visible.  Returns the activity view, already added as a subview of the specified view.

 Written by DJS 2009-07.
 Changed by DJS 2010-06 to add "new" prefix to the method name to make it clearer that this returns a retained object.
 Changed by DJS 2011-08 to remove the "new" prefix again, and move the singleton stuff to here.
*/

+ (DejalActivityView *)activityViewForView:(UIView *)addToView withLabel:(NSString *)labelText width:(NSUInteger)aLabelWidth;
{
    // Immediately remove any existing activity view:
    if (dejalActivityView)
        [self removeView];

    // Remember the new view (so this is a singleton):
    dejalActivityView = [[self alloc] initForView:addToView withLabel:labelText width:aLabelWidth];

    return dejalActivityView;
}

/*
 initForView:withLabel:width:

 Designated initializer.  Configures the activity view using the specified label text and width, and adds as a subview of the specified view.

 Written by DJS 2009-07.
 Changed by DJS 2011-08 to move the singleton stuff to the calling class method, where it should be.
*/

- (DejalActivityView *)initForView:(UIView *)addToView withLabel:(NSString *)labelText width:(NSUInteger)aLabelWidth;
{
	if (!(self = [super initWithFrame:CGRectZero]))
		return nil;

    // Allow subclasses to change the view to which to add the activity view (e.g. to cover the keyboard):
    self.originalView = addToView;
    addToView = [self viewForView:addToView];

    // Configure this view (the background) and its subviews:
    [self setupBackground];
    self.labelWidth = aLabelWidth;
    self.borderView = [self makeBorderView];
    self.activityIndicator = [self makeActivityIndicator];
    self.activityLabel = [self makeActivityLabelWithText:labelText];

    // Assemble the subviews:
	[addToView addSubview:self];
    [self addSubview:self.borderView];
    [self.borderView addSubview:self.activityIndicator];
    [self.borderView addSubview:self.activityLabel];

	// Animate the view in, if appropriate:
	[self animateShow];

	return self;
}

- (void)dealloc;
{
    if ([dejalActivityView isEqual:self])
        dejalActivityView = nil;
}

/*
 removeView

 Immediately removes and releases the view without any animation.

 Written by DJS 2009-07.
 Changed by DJS 2009-09 to disable the network activity indicator if it was shown by this view.
*/

+ (void)removeView;
{
    if (!dejalActivityView)
        return;

    if (dejalActivityView.showNetworkActivityIndicator)
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    [dejalActivityView removeFromSuperview];

    // Remove the global reference:
    dejalActivityView = nil;
}

/*
 viewForView:

 Returns the view to which to add the activity view.  By default returns the same view.  Subclasses may override this to change the view.

 Written by DJS 2009-07.
*/

- (UIView *)viewForView:(UIView *)view;
{
    return view;
}

/*
 enclosingFrame

 Returns the frame to use for the activity view.  Defaults to the superview's bounds.  Subclasses may override this to use something different, if desired.

 Written by DJS 2009-07.
*/

- (CGRect)enclosingFrame;
{
    return self.superview.bounds;
}

/*
 setupBackground

 Configure the background of the activity view.

 Written by DJS 2009-07.
*/

- (void)setupBackground;
{
	self.opaque = NO;
    self.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
}

/*
 makeBorderView

 Returns a new view to contain the activity indicator and label.  By default this view is transparent.  Subclasses may override this method, optionally calling super, to use a different or customized view.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIView *)makeBorderView;
{
    UIView *view = [[UIView alloc] initWithFrame:CGRectZero];

    view.opaque = NO;
    view.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin;

    return view;
}

/*
 makeActivityIndicator

 Returns a new activity indicator view.  Subclasses may override this method, optionally calling super, to use a different or customized view.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIActivityIndicatorView *)makeActivityIndicator;
{
    UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];

    [indicator startAnimating];

    return indicator;
}

/*
 makeActivityLabelWithText:

 Returns a new activity label.  Subclasses may override this method, optionally calling super, to use a different or customized view.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
 Changed by chrisledet 2013-01 to use NSTextAlignmentLeft instead of the deprecated UITextAlignmentLeft.
*/

- (UILabel *)makeActivityLabelWithText:(NSString *)labelText;
{
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];

    label.font = [UIFont systemFontOfSize:[UIFont systemFontSize]];
    label.textAlignment = NSTextAlignmentLeft;
    label.textColor = [UIColor blackColor];
    label.backgroundColor = [UIColor clearColor];
    label.shadowColor = [UIColor whiteColor];
    label.shadowOffset = CGSizeMake(0.0, 1.0);
    label.text = labelText;

    return label;
}

/*
 layoutSubviews

 Positions and sizes the various views that make up the activity view, including after rotation.

 Written by DJS 2009-07.
*/

- (void)layoutSubviews;
{
    self.frame = [self enclosingFrame];

    // If we're animating a transform, don't lay out now, as can't use the frame property when transforming:
    if (!CGAffineTransformIsIdentity(self.borderView.transform))
        return;

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
    CGSize textSize = [self.activityLabel.text sizeWithFont:[UIFont systemFontOfSize:[UIFont systemFontSize]]];
#else
    CGSize textSize = [self.activityLabel.text sizeWithAttributes:@{NSFontAttributeName:[UIFont systemFontOfSize:[UIFont systemFontSize]]}];
#endif

    // Use the fixed width if one is specified:
    if (self.labelWidth > 10)
        textSize.width = self.labelWidth;

    self.activityLabel.frame = CGRectMake(self.activityLabel.frame.origin.x, self.activityLabel.frame.origin.y, textSize.width, textSize.height);

    // Calculate the size and position for the border view: with the indicator to the left of the label, and centered in the receiver:
	CGRect borderFrame = CGRectZero;
    borderFrame.size.width = self.activityIndicator.frame.size.width + textSize.width + 25.0;
    borderFrame.size.height = self.activityIndicator.frame.size.height + 10.0;
    borderFrame.origin.x = floor(0.5 * (self.frame.size.width - borderFrame.size.width));
    borderFrame.origin.y = floor(0.5 * (self.frame.size.height - borderFrame.size.height - 20.0));
    self.borderView.frame = borderFrame;

    // Calculate the position of the indicator: vertically centered and at the left of the border view:
    CGRect indicatorFrame = self.activityIndicator.frame;
	indicatorFrame.origin.x = 10.0;
	indicatorFrame.origin.y = 0.5 * (borderFrame.size.height - indicatorFrame.size.height);
    self.activityIndicator.frame = indicatorFrame;

    // Calculate the position of the label: vertically centered and at the right of the border view:
	CGRect labelFrame = self.activityLabel.frame;
    labelFrame.origin.x = borderFrame.size.width - labelFrame.size.width - 10.0;
	labelFrame.origin.y = floor(0.5 * (borderFrame.size.height - labelFrame.size.height));
    self.activityLabel.frame = labelFrame;
}

/*
 animateShow

 Animates the view into visibility.  Does nothing for the simple activity view.

 Written by DJS 2009-07.
*/

- (void)animateShow;
{
    // Does nothing by default
}

/*
 animateRemove

 Animates the view out of visibiltiy.  Does nothng for the simple activity view.

 Written by DJS 2009-07.
*/

- (void)animateRemove;
{
    // Does nothing by default
}

/*
 setShowNetworkActivityIndicator:

 Sets whether or not to show the network activity indicator in the status bar.  Set to YES if the activity is network-related.  This can be toggled on and off as desired while the activity view is visible (e.g. have it on while fetching data, then disable it while parsing it).  By default it is not shown.

 Written by DJS 2009-09.
*/

- (void)setShowNetworkActivityIndicator:(BOOL)show;
{
    showNetworkActivityIndicator = show;

    [UIApplication sharedApplication].networkActivityIndicatorVisible = show;
}

@end

// ----------------------------------------------------------------------------------------
// MARK: -
// ----------------------------------------------------------------------------------------

@implementation DejalWhiteActivityView

/*
 makeActivityIndicator

 Returns a new activity indicator view.  This subclass uses a white activity indicator instead of gray.

 Written by DJS 2009-10.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIActivityIndicatorView *)makeActivityIndicator;
{
    UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];

    [indicator startAnimating];

    return indicator;
}

/*
 makeActivityLabelWithText:

 Returns a new activity label.  This subclass uses white text instead of black.

 Written by DJS 2009-10.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UILabel *)makeActivityLabelWithText:(NSString *)labelText;
{
    UILabel *label = [super makeActivityLabelWithText:labelText];

    label.textColor = [UIColor whiteColor];
    label.shadowColor = [UIColor blackColor];

    return label;
}

@end

// ----------------------------------------------------------------------------------------
// MARK: -
// ----------------------------------------------------------------------------------------

@implementation DejalBezelActivityView

/*
 viewForView:

 Returns the view to which to add the activity view.  For the bezel style, if there is a keyboard displayed, the view is changed to the keyboard's superview.

 Written by DJS 2009-07.
*/

- (UIView *)viewForView:(UIView *)view;
{
    UIView *keyboardView = [[UIApplication sharedApplication] keyboardView];

    if (keyboardView)
        view = keyboardView.superview;

    return view;
}

/*
 enclosingFrame

 Returns the frame to use for the activity view.  For the bezel style, if there is a keyboard displayed, the frame is changed to cover the keyboard too.

 Written by DJS 2009-07.
*/

- (CGRect)enclosingFrame;
{
    CGRect frame = [super enclosingFrame];

    if (self.superview != self.originalView)
        frame = [self.originalView convertRect:self.originalView.bounds toView:self.superview];

    return frame;
}

/*
 setupBackground

 Configure the background of the activity view.

 Written by DJS 2009-07.
*/

- (void)setupBackground;
{
    [super setupBackground];

	self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.35];
}

/*
 makeBorderView

 Returns a new view to contain the activity indicator and label.  The bezel style has a semi-transparent rounded rectangle.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIView *)makeBorderView;
{
    UIView *view = [super makeBorderView];

    view.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
    view.layer.cornerRadius = 10.0;

    return view;
}

/*
 makeActivityIndicator

 Returns a new activity indicator view.  The bezel style uses a large white indicator.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIActivityIndicatorView *)makeActivityIndicator;
{
    UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhiteLarge];

    [indicator startAnimating];

    return indicator;
}

/*
 makeActivityLabelWithText:

 Returns a new activity label.  The bezel style uses centered white text.

 Written by DJS 2009-07.
 Changed by Suleman Sidat 2011-07 to support a multi-line label.
 Changed by DJS 2011-11 to simplify and make it easier to override.
 Changed by chrisledet 2013-01 to use NSTextAlignmentCenter and NSLineBreakByWordWrapping instead of the deprecated UITextAlignmentCenter and UILineBreakModeWordWrap.
*/

- (UILabel *)makeActivityLabelWithText:(NSString *)labelText;
{
    UILabel *label = [[UILabel alloc] initWithFrame:CGRectZero];

    label.font = [UIFont boldSystemFontOfSize:[UIFont systemFontSize]];
    label.textAlignment = NSTextAlignmentCenter;
    label.textColor = [UIColor whiteColor];
    label.backgroundColor = [UIColor clearColor];
    label.numberOfLines = 0;
    label.lineBreakMode = NSLineBreakByWordWrapping;
    label.text = labelText;

    return label;
}

/*
 layoutSubviews

 Positions and sizes the various views that make up the activity view, including after rotation.

 Written by DJS 2009-07.
 Changed by Suleman Sidat 2011-07 to support a multi-line label.
*/

- (void)layoutSubviews;
{
    // If we're animating a transform, don't lay out now, as can't use the frame property when transforming:
    if (!CGAffineTransformIsIdentity(self.borderView.transform))
        return;

    self.frame = [self enclosingFrame];

    CGSize maxSize = CGSizeMake(260, 400);

#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
    CGSize textSize = [self.activityLabel.text sizeWithFont:[UIFont boldSystemFontOfSize:[UIFont systemFontSize]]
                                          constrainedToSize:maxSize
                                              lineBreakMode:self.activityLabel.lineBreakMode];
#else
    NSMutableParagraphStyle *para = [NSMutableParagraphStyle new];
    para.lineBreakMode = self.activityLabel.lineBreakMode;
    CGSize textSize = [self.activityLabel.text boundingRectWithSize:maxSize
                                                            options:NSStringDrawingUsesLineFragmentOrigin
                                                         attributes:@{NSFontAttributeName:[UIFont boldSystemFontOfSize:[UIFont systemFontSize]],
                                                                      NSParagraphStyleAttributeName:para}
                                                            context:nil].size;
#endif

    // Use the fixed width if one is specified:
    if (self.labelWidth > 10)
        textSize.width = self.labelWidth;

    // Require that the label be at least as wide as the indicator, since that width is used for the border view:
    if (textSize.width < self.activityIndicator.frame.size.width)
        textSize.width = self.activityIndicator.frame.size.width + 10.0;

    // If there's no label text, don't need to allow height for it:
    if (self.activityLabel.text.length == 0)
        textSize.height = 0.0;

    self.activityLabel.frame = CGRectMake(self.activityLabel.frame.origin.x, self.activityLabel.frame.origin.y, textSize.width, textSize.height);

    // Calculate the size and position for the border view: with the indicator vertically above the label, and centered in the receiver:
	CGRect borderFrame = CGRectZero;
    borderFrame.size.width = textSize.width + 30.0;
    borderFrame.size.height = self.activityIndicator.frame.size.height + textSize.height + 40.0;
    borderFrame.origin.x = floor(0.5 * (self.frame.size.width - borderFrame.size.width));
    borderFrame.origin.y = floor(0.5 * (self.frame.size.height - borderFrame.size.height));
    self.borderView.frame = borderFrame;

    // Calculate the position of the indicator: horizontally centered and near the top of the border view:
    CGRect indicatorFrame = self.activityIndicator.frame;
	indicatorFrame.origin.x = 0.5 * (borderFrame.size.width - indicatorFrame.size.width);
	indicatorFrame.origin.y = 20.0;
    self.activityIndicator.frame = indicatorFrame;

    // Calculate the position of the label: horizontally centered and near the bottom of the border view:
	CGRect labelFrame = self.activityLabel.frame;
    labelFrame.origin.x = floor(0.5 * (borderFrame.size.width - labelFrame.size.width));
	labelFrame.origin.y = borderFrame.size.height - labelFrame.size.height - 10.0;
    self.activityLabel.frame = labelFrame;
}

/*
 animateShow

 Animates the view into visibility.  For the bezel style, fades in the background and zooms the bezel down from a large size.

 Written by DJS 2009-07.
*/

- (void)animateShow;
{
    self.alpha = 0.0;
    self.borderView.transform = CGAffineTransformMakeScale(3.0, 3.0);

	[UIView beginAnimations:nil context:nil];
//	[UIView setAnimationDuration:5.0];            // Uncomment to see the animation in slow motion

    self.borderView.transform = CGAffineTransformIdentity;
    self.alpha = 1.0;

	[UIView commitAnimations];
}

/*
 animateRemove

 Animates the view out, deferring the removal until the animation is complete.  For the bezel style, fades out the background and zooms the bezel down to half size.

 Written by DJS 2009-07.
 Changed by DJS 2009-09 to disable the network activity indicator if it was shown by this view.
*/

- (void)animateRemove;
{
    if (self.showNetworkActivityIndicator)
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    self.borderView.transform = CGAffineTransformIdentity;

	[UIView beginAnimations:nil context:nil];
//	[UIView setAnimationDuration:5.0];            // Uncomment to see the animation in slow motion
	[UIView setAnimationDelegate:self];
	[UIView setAnimationDidStopSelector:@selector(removeAnimationDidStop:finished:context:)];

    self.borderView.transform = CGAffineTransformMakeScale(0.5, 0.5);
    self.alpha = 0.0;

	[UIView commitAnimations];
}

- (void)removeAnimationDidStop:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context;
{
    [[self class] removeView];
}

/*
 removeViewAnimated:

 Animates the view out from the superview and releases it, or simply removes and releases it immediately if not animating.

 Written by DJS 2009-07.
*/

+ (void)removeViewAnimated:(BOOL)animated;
{
    if (!dejalActivityView)
        return;

    if (animated)
        [dejalActivityView animateRemove];
    else
        [[self class] removeView];
}

@end

// ----------------------------------------------------------------------------------------
// MARK: -
// ----------------------------------------------------------------------------------------

@implementation DejalKeyboardActivityView

/*
 activityView

 Creates and adds a keyboard-style activity view, using the label "Loading...".  Returns the activity view, already covering the keyboard, or nil if the keyboard isn't currently displayed.

 Written by DJS 2009-07.
 Changed by DJS 2010-06 to add "new" prefix to the method name to make it clearer that this returns a retained object.
 Changed by DJS 2011-08 to remove the "new" prefix again.
*/

+ (DejalKeyboardActivityView *)activityView;
{
    return [self activityViewWithLabel:NSLocalizedString(@"Loading...", @"Default DejalActivtyView label text")];
}

/*
 activityViewWithLabel:

 Creates and adds a keyboard-style activity view, using the specified label.  Returns the activity view, already covering the keyboard, or nil if the keyboard isn't currently displayed.

 Written by DJS 2009-07.
 Changed by DJS 2010-06 to add "new" prefix to the method name to make it clearer that this returns a retained object.
 Changed by DJS 2011-08 to remove the "new" prefix again.
*/

+ (DejalKeyboardActivityView *)activityViewWithLabel:(NSString *)labelText;
{
    UIView *keyboardView = [[UIApplication sharedApplication] keyboardView];

    if (!keyboardView)
        return nil;
    else
        return (DejalKeyboardActivityView *)[self activityViewForView:keyboardView withLabel:labelText];
}

/*
 viewForView:

 Returns the view to which to add the activity view.  For the keyboard style, returns the same view (which will already be the keyboard).

 Written by DJS 2009-07.
*/

- (UIView *)viewForView:(UIView *)view;
{
    return view;
}

/*
 animateShow

 Animates the view into visibility.  For the keyboard style, simply fades in.

 Written by DJS 2009-07.
*/

- (void)animateShow;
{
    self.alpha = 0.0;

	[UIView beginAnimations:nil context:nil];
	[UIView setAnimationDuration:1.0];

    self.alpha = 1.0;

	[UIView commitAnimations];
}

/*
 animateRemove

 Animates the view out, deferring the removal until the animation is complete.  For the keyboard style, simply fades out.

 Written by DJS 2009-07.
*/

- (void)animateRemove;
{
	[UIView beginAnimations:nil context:nil];
	[UIView setAnimationDuration:1.0];
	[UIView setAnimationDelegate:self];
	[UIView setAnimationDidStopSelector:@selector(removeAnimationDidStop:finished:context:)];

    self.alpha = 0.0;

	[UIView commitAnimations];
}

/*
 setupBackground

 Configure the background of the activity view.

 Written by DJS 2009-07.
*/

- (void)setupBackground;
{
    [super setupBackground];

    self.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.85];
}

/*
 makeBorderView

 Returns a new view to contain the activity indicator and label.  The keyboard style has a transparent border.

 Written by DJS 2009-07.
 Changed by DJS 2011-11 to simplify and make it easier to override.
*/

- (UIView *)makeBorderView;
{
    UIView *view = [super makeBorderView];

    view.backgroundColor = nil;

    return view;
}

@end

// ----------------------------------------------------------------------------------------
// MARK: -
// ----------------------------------------------------------------------------------------

@implementation UIApplication (KeyboardView)

//  keyboardView
//
//  Copyright Matt Gallagher 2009. All rights reserved.
//
//  Permission is given to use this source code file, free of charge, in any
//  project, commercial or otherwise, entirely at your risk, with the condition
//  that any redistribution (in part or whole) of source code must retain
//  this copyright and permission notice. Attribution in compiled projects is
//  appreciated but not required.

- (UIView *)keyboardView;
{
	NSArray *windows = [self windows];
	for (UIWindow *window in [windows reverseObjectEnumerator])
	{
		for (UIView *view in [window subviews])
		{
            // UIPeripheralHostView is used from iOS 4.0, UIKeyboard was used in previous versions:
			if (!strcmp(object_getClassName(view), "UIPeripheralHostView") || !strcmp(object_getClassName(view), "UIKeyboard"))
			{
				return view;
			}
		}
	}

	return nil;
}

@end


text:
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;

namespace ModernRonin.PraeterArtem.Functional
{
    /// Contains extension methods for <see cref="IEnumerable{T}"/>
    /// supporting a functional coding style.
    public static class EnumerableExtensions
    {
        /// More readable replacement for !Any{T}.
        public static bool IsEmpty<T>(
            [NotNull] this IEnumerable<T> enumerable)
        {
            return !enumerable.Any();
        }

        /// Passes each argument of <paramref name="enumerable"/> to
        /// <paramref name="action"/>.
        public static void UseIn<T>([NotNull] this IEnumerable<T> enumerable,
                                    [NotNull] Action<T> action)
        {
            if (enumerable == null)
                throw new ArgumentNullException("enumerable");
            if (action == null)
                throw new ArgumentNullException("action");
            foreach (var element in enumerable)
                action(element);
        }
        ///
        /// Calls each action contained in <paramref name="actions"/> with the
        /// argument <paramref name="argument"/>
        ///
        /// <typeparam name="T"></typeparam>
        public static void ExecuteOn<T>(
            [NotNull] this IEnumerable<Action<T>> actions, T argument)
        {
            if (actions == null)
                throw new ArgumentNullException("actions");
            actions.UseIn(a => a(argument));
        }
        ///
        /// Allows to easily pass single elements into functions expecting an
        /// IEnumerable.
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="element"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> ToEnumerable<T>(this T element)
        {
            if (element is IEnumerable<T>)
                return CastToEnumerable(element);
            return WrapAsEnumerable(element);
        }
        [NotNull]
        static IEnumerable<T> CastToEnumerable<T>(T singleElement)
        {
            return (IEnumerable<T>) singleElement;
        }
        [NotNull]
        static IEnumerable<T> WrapAsEnumerable<T>(T singleElement)
        {
            yield return singleElement;
        }
        /// Adds all element to <paramref name="destination"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="destination"></param>
        public static void AddTo<T>([NotNull] this IEnumerable<T> enumerable,
                                    [NotNull] ICollection<T> destination)
        {
            enumerable.UseIn(destination.Add);
        }

        /// Returns all elements which are not null.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> ExceptNullValues<T>(
            [NotNull] this IEnumerable<T> enumerable) where T : class
        {
            return enumerable.Where(e => null != e);
        }

        /// Returns all elements except the first.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> ButFirst<T>(
            [NotNull] this IEnumerable<T> enumerable)
        {
            return enumerable.Skip(1);
        }
        /// Returns all elements except the last.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> ButLast<T>(
            [NotNull] this IEnumerable<T> enumerable)
        {
            var enumerator = enumerable.GetEnumerator();
            if (!enumerator.MoveNext())
                yield break;
            var first = enumerator.Current;
            while (enumerator.MoveNext())
            {
                yield return first;
                first = enumerator.Current;
            }
        }
        /// Returns all elements of <paramref name="enumerable"/> except
        /// <paramref name="value"/>/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> Except<T>(
            [NotNull] this IEnumerable<T> enumerable, T value)
        {
            return enumerable.Except(value.ToEnumerable());
        }
        /// Returns all elements of <paramref name="enumerable"/>
        /// which are greater than <paramref name="limit"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="limit"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> GreaterThan<T>(
            [NotNull] this IEnumerable<T> enumerable, T limit)
            where T : IComparable<T>
        {
            return enumerable.Where(e => 0 < e.CompareTo(limit));
        }
        /// Returns all elements of <paramref name="enumerable"/>
        /// which are greater than or equal to <paramref name="limit"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="limit"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> GreaterThanOrEqualTo<T>(
            [NotNull] this IEnumerable<T> enumerable, T limit)
            where T : IComparable<T>
        {
            return enumerable.Where(e => 0 <= e.CompareTo(limit));
        }
        /// Returns all elements of <paramref name="enumerable"/>
        /// which are smaller than <paramref name="limit"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="limit"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> SmallerThan<T>(
            [NotNull] this IEnumerable<T> enumerable, T limit)
            where T : IComparable<T>
        {
            return enumerable.Where(e => 0 > e.CompareTo(limit));
        }
        /// Returns all elements of <paramref name="enumerable"/>
        /// which are smaller than or equal to<paramref name="limit"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="limit"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> SmallerThanOrEqualTo<T>(
            [NotNull] this IEnumerable<T> enumerable, T limit)
            where T : IComparable<T>
        {
            return enumerable.Where(e => 0 >= e.CompareTo(limit));
        }
        ///
        /// Returns all elements of <paramref name="enumerable"/> which are
        /// equal to <paramref name="check"/>
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="check"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> EqualTo<T>(
            [NotNull] this IEnumerable<T> enumerable, T check)
        {
            return enumerable.Where(e => e.Equals(check));
        }
        /// Returns all elements of <paramref name="enumerable"/>
        /// which are reference-equal to <paramref name="check"/>.
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="check"></param>
        /// <returns></returns>
        [NotNull]
        public static IEnumerable<T> SameAs<T>(
            [NotNull] this IEnumerable<T> enumerable, T check)
        {
            return enumerable.Where(e => ReferenceEquals(e, check));
        }
        ///
        /// Returns the element of <paramref name="enumerable"/>
        /// with the smallest evaluation when passed to <paramref name="evaluator"/>.
        /// (In contrast to the BCL .Min() method which returns the minimal value, not
        /// the minimal element.)
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="evaluator"></param>
        /// <returns></returns>
        public static T MinElement<T>(
            [NotNull] this IEnumerable<T> enumerable,
            [NotNull] Func<T, int> evaluator)
        {
            if (enumerable == null)
                throw new ArgumentNullException("enumerable");
            if (evaluator == null)
                throw new ArgumentNullException("evaluator");
            var minimumScore = Int32.MaxValue;
            var result = default(T);
            foreach (var element in enumerable)
            {
                var score = evaluator(element);
                if (score <= minimumScore)
                {
                    minimumScore = score;
                    result = element;
                }
            }
            return result;
        }
        ///
        /// Returns the element of <paramref name="enumerable"/>
        /// with the greatest evaluation when passed to <paramref name="evaluator"/>.
        /// (In contrast to the BCL .Max() method which returns the minimal value, not
        /// the minimal element.)
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="evaluator"></param>
        /// <returns></returns>
        public static T MaxElement<T>(
            [NotNull] this IEnumerable<T> enumerable,
            [NotNull] Func<T, int> evaluator)
        {
            return enumerable.MinElement(e => -evaluator(e));
        }

        ///
        /// Overload for the BCL .Min() method which allows to pass a value to
        /// be used in case
        /// <paramref name="enumerable"/> is empty.
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="valueToReturnIfEmpty"></param>
        /// <returns></returns>
        public static T Min<T>([NotNull] this IEnumerable<T> enumerable,
                               T valueToReturnIfEmpty)
            where T : IComparable<T>
        {
            var frozen = enumerable as T[] ?? enumerable.ToArray();
            return frozen.Any() ? frozen.Min() : valueToReturnIfEmpty;
        }
        ///
        /// Overload for the BCL .Max() method which allows to pass a value to
        /// be used in case
        /// <paramref name="enumerable"/> is empty.
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="valueToReturnIfEmpty"></param>
        /// <returns></returns>
        public static T Max<T>([NotNull] this IEnumerable<T> enumerable,
                               T valueToReturnIfEmpty)
            where T : IComparable<T>
        {
            var frozen = enumerable as T[] ?? enumerable.ToArray();
            return frozen.Any() ? frozen.Max() : valueToReturnIfEmpty;
        }
        ///
        /// Splits the enumerable into multiple enumerables of size
        /// <paramref name="chunkSize"/>. Note that the last chunk may contain fewer
        /// items.
        /// <example>
        /// new []{1,2,3,4,5,6,7,8,9, 0}.Chunk(3) will result in 3 chunks with
        /// 3 elements and a fourth chunk with only one element
        /// </example>
        ///
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <param name="chunkSize"></param>
        /// <returns></returns>
        public static IEnumerable<IEnumerable<T>> Split<T>(
            this IEnumerable<T> enumerable, int chunkSize)
        {
            for (var rest = enumerable.ToArray();
                 !rest.IsEmpty();
                 rest = rest.Skip(chunkSize).ToArray())
                yield return rest.Take(chunkSize);
        }
        /// Returns an enumeration of all elements .ToString().
        /// <typeparam name="T"></typeparam>
        /// <param name="enumerable"></param>
        /// <returns></returns>
        public static IEnumerable<string> ToStrings<T>(
            this IEnumerable<T> enumerable)
        {
            return enumerable.Select(e => e.ToString());
        }
    }
}