text:
package org.apache.druid.query;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.druid.collections.CloseableStupidPool;
import org.apache.druid.common.config.NullHandling;
import org.apache.druid.data.input.impl.CSVParseSpec;
import org.apache.druid.data.input.impl.DimensionsSpec;
import org.apache.druid.data.input.impl.JSONParseSpec;
import org.apache.druid.data.input.impl.StringInputRowParser;
import org.apache.druid.data.input.impl.TimestampSpec;
import org.apache.druid.java.util.common.DateTimes;
import org.apache.druid.java.util.common.FileUtils;
import org.apache.druid.java.util.common.granularity.Granularities;
import org.apache.druid.java.util.common.guava.Sequence;
import org.apache.druid.query.aggregation.AggregationTestHelper;
import org.apache.druid.query.aggregation.CountAggregatorFactory;
import org.apache.druid.query.dimension.DefaultDimensionSpec;
import org.apache.druid.query.dimension.ListFilteredDimensionSpec;
import org.apache.druid.query.dimension.RegexFilteredDimensionSpec;
import org.apache.druid.query.expression.TestExprMacroTable;
import org.apache.druid.query.filter.InDimFilter;
import org.apache.druid.query.filter.SelectorDimFilter;
import org.apache.druid.query.groupby.GroupByQuery;
import org.apache.druid.query.groupby.GroupByQueryConfig;
import org.apache.druid.query.groupby.GroupByQueryRunnerTest;
import org.apache.druid.query.groupby.GroupByQueryRunnerTestHelper;
import org.apache.druid.query.groupby.ResultRow;
import org.apache.druid.query.groupby.orderby.DefaultLimitSpec;
import org.apache.druid.query.groupby.orderby.OrderByColumnSpec;
import org.apache.druid.query.groupby.strategy.GroupByStrategySelector;
import org.apache.druid.query.spec.LegacySegmentSpec;
import org.apache.druid.query.topn.TopNQuery;
import org.apache.druid.query.topn.TopNQueryBuilder;
import org.apache.druid.query.topn.TopNQueryConfig;
import org.apache.druid.query.topn.TopNQueryQueryToolChest;
import org.apache.druid.query.topn.TopNQueryRunnerFactory;
import org.apache.druid.query.topn.TopNResultValue;
import org.apache.druid.segment.IncrementalIndexSegment;
import org.apache.druid.segment.IndexSpec;
import org.apache.druid.segment.QueryableIndex;
import org.apache.druid.segment.QueryableIndexSegment;
import org.apache.druid.segment.TestHelper;
import org.apache.druid.segment.column.ValueType;
import org.apache.druid.segment.incremental.IncrementalIndex;
import org.apache.druid.segment.incremental.OnheapIncrementalIndex;
import org.apache.druid.segment.virtual.ExpressionVirtualColumn;
import org.apache.druid.segment.writeout.OffHeapMemorySegmentWriteOutMediumFactory;
import org.apache.druid.segment.writeout.SegmentWriteOutMediumFactory;
import org.apache.druid.segment.writeout.TmpFileSegmentWriteOutMediumFactory;
import org.apache.druid.testing.InitializedNullHandlingTest;
import org.apache.druid.timeline.SegmentId;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;

import java.io.File;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

/**
 */
@RunWith(Parameterized.class)
public class MultiValuedDimensionTest extends InitializedNullHandlingTest
{
  @Parameterized.Parameters(name = "groupby: {0} forceHashAggregation: {2} ({1})")
  public static Collection<?> constructorFeeder()
  {
    final List<Object[]> constructors = new ArrayList<>();
    for (GroupByQueryConfig config : GroupByQueryRunnerTest.testConfigs()) {
      constructors.add(new Object[]{config, TmpFileSegmentWriteOutMediumFactory.instance(), false});
      constructors.add(new Object[]{config, OffHeapMemorySegmentWriteOutMediumFactory.instance(), false});
      constructors.add(new Object[]{config, TmpFileSegmentWriteOutMediumFactory.instance(), true});
      constructors.add(new Object[]{config, OffHeapMemorySegmentWriteOutMediumFactory.instance(), true});
    }
    return constructors;
  }

  private final AggregationTestHelper helper;
  private final SegmentWriteOutMediumFactory segmentWriteOutMediumFactory;

  private IncrementalIndex incrementalIndex;
  private QueryableIndex queryableIndex;
  private File persistedSegmentDir;

  private IncrementalIndex incrementalIndexNullSampler;
  private QueryableIndex queryableIndexNullSampler;
  private File persistedSegmentDirNullSampler;

  private final GroupByQueryConfig config;
  private final ImmutableMap<String, Object> context;

  @Rule
  public ExpectedException expectedException = ExpectedException.none();

  public MultiValuedDimensionTest(final GroupByQueryConfig config, SegmentWriteOutMediumFactory segmentWriteOutMediumFactory, boolean forceHashAggregation)
  {
    helper = AggregationTestHelper.createGroupByQueryAggregationTestHelper(
        ImmutableList.of(),
        config,
        null
    );
    this.config = config;
    this.segmentWriteOutMediumFactory = segmentWriteOutMediumFactory;

    this.context = config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)
                   ? ImmutableMap.of()
                   : ImmutableMap.of("forceHashAggregation", forceHashAggregation);
  }

  @Before
  public void setup() throws Exception
  {
    incrementalIndex = new OnheapIncrementalIndex.Builder()
        .setSimpleTestingIndexSchema(new CountAggregatorFactory("count"))
        .setMaxRowCount(5000)
        .build();

    StringInputRowParser parser = new StringInputRowParser(
        new CSVParseSpec(
            new TimestampSpec("timestamp", "iso", null),
            new DimensionsSpec(DimensionsSpec.getDefaultSchemas(ImmutableList.of("product", "tags", "othertags")), null, null),
            "\t",
            ImmutableList.of("timestamp", "product", "tags", "othertags"),
            false,
            0
        ),
        "UTF-8"
    );

    String[] rows = new String[]{
        "2011-01-12T00:00:00.000Z,product_1,t1\tt2\tt3,u1\tu2",
        "2011-01-13T00:00:00.000Z,product_2,t3\tt4\tt5,u3\tu4",
        "2011-01-14T00:00:00.000Z,product_3,t5\tt6\tt7,u1\tu5",
        "2011-01-14T00:00:00.000Z,product_4,\"\",u2"
    };

    for (String row : rows) {
      incrementalIndex.add(parser.parse(row));
    }

    persistedSegmentDir = FileUtils.createTempDir();
    TestHelper.getTestIndexMergerV9(segmentWriteOutMediumFactory)
              .persist(incrementalIndex, persistedSegmentDir, new IndexSpec(), null);
    queryableIndex = TestHelper.getTestIndexIO().loadIndex(persistedSegmentDir);

    StringInputRowParser parserNullSampler = new StringInputRowParser(
        new JSONParseSpec(
            new TimestampSpec("time", "iso", null),
            new DimensionsSpec(DimensionsSpec.getDefaultSchemas(ImmutableList.of("product", "tags", "othertags")), null, null)
        ),
        "UTF-8"
    );

    incrementalIndexNullSampler = new OnheapIncrementalIndex.Builder()
        .setSimpleTestingIndexSchema(new CountAggregatorFactory("count"))
        .setMaxRowCount(5000)
        .build();

    String[] rowsNullSampler = new String[]{
        "{\"time\":\"2011-01-13T00:00:00.000Z\",\"product\":\"product_1\",\"tags\":[],\"othertags\":[\"u1\", \"u2\"]}",
        "{\"time\":\"2011-01-12T00:00:00.000Z\",\"product\":\"product_2\",\"othertags\":[\"u3\", \"u4\"]}",
        "{\"time\":\"2011-01-14T00:00:00.000Z\",\"product\":\"product_3\",\"tags\":[\"\"],\"othertags\":[\"u1\", \"u5\"]}",
        "{\"time\":\"2011-01-15T00:00:00.000Z\",\"product\":\"product_4\",\"tags\":[\"t1\", \"t2\", \"\"],\"othertags\":[\"u6\", \"u7\"]}",
        "{\"time\":\"2011-01-16T00:00:00.000Z\",\"product\":\"product_5\",\"tags\":[],\"othertags\":[]}",
        "{\"time\":\"2011-01-16T00:00:00.000Z\",\"product\":\"product_6\"}",
        "{\"time\":\"2011-01-16T00:00:00.000Z\",\"product\":\"product_7\",\"othertags\":[]}",
        "{\"time\":\"2011-01-16T00:00:00.000Z\",\"product\":\"product_8\",\"tags\":[\"\"],\"othertags\":[]}"
    };

    for (String row : rowsNullSampler) {
      incrementalIndexNullSampler.add(parserNullSampler.parse(row));
    }
    persistedSegmentDirNullSampler = FileUtils.createTempDir();
    TestHelper.getTestIndexMergerV9(segmentWriteOutMediumFactory)
              .persist(incrementalIndexNullSampler, persistedSegmentDirNullSampler, new IndexSpec(), null);

    queryableIndexNullSampler = TestHelper.getTestIndexIO().loadIndex(persistedSegmentDirNullSampler);
  }

  @After
  public void teardown() throws IOException
  {
    helper.close();
  }

  @Test
  public void testGroupByNoFilter()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tags", "tags"))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(
            query,
            "1970",
            "tags",
            NullHandling.replaceWithDefault() ? null : "",
            "count",
            2L
        ),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t3", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t4", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t5", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t6", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tags", "t7", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "noFilter");
  }

  @Test
  public void testGroupByWithDimFilter()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tags", "tags"))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setDimFilter(new SelectorDimFilter("tags", "t3", null))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t3", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t4", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t5", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "dimFilter");
  }

  @Test
  public void testGroupByWithDimFilterEmptyResults()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tags", "tags"))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setDimFilter(new InDimFilter("product", ImmutableList.of("product_5"), null))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndexNullSampler, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndexNullSampler, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Collections.singletonList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", null, "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filter-empty");
  }

  @Test
  public void testGroupByWithDimFilterNullishResults()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tags", "tags"))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setDimFilter(
            new InDimFilter("product", ImmutableList.of("product_5", "product_6", "product_8"), null)
        )
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndexNullSampler, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndexNullSampler, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults;
    // an empty row e.g. [], or group by 'missing' value, is grouped with the default string value, "" or null
    // grouping input is filtered to [], null, [""]
    if (NullHandling.replaceWithDefault()) {
      // when sql compatible null handling is disabled, the inputs are effectively [], null, [null] and
      // are all grouped as null
      expectedResults = Collections.singletonList(
          GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", null, "count", 6L)
      );
    } else {
      // with sql compatible null handling, null and [] = null, but [""] = ""
      expectedResults = ImmutableList.of(
          GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", null, "count", 4L),
          GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "", "count", 2L)
      );
    }

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filter-nullish");
  }

  @Test
  public void testGroupByWithDimFilterAndWithFilteredDimSpec()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new RegexFilteredDimensionSpec(new DefaultDimensionSpec("tags", "tags"), "t3"))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setDimFilter(new SelectorDimFilter("tags", "t3", null))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Collections.singletonList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970-01-01T00:00:00.000Z", "tags", "t3", "count", 4L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filteredDim");
  }

  @Test
  public void testGroupByExpression()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "map(x -> concat(x, 'foo'), tags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3foo", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t4foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t5foo", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t6foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t7foo", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr");
  }

  @Test
  public void testGroupByExpressionMultiMulti()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "cartesian_map((x,y) -> concat(x, y), tags, othertags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3u1", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-multi-multi");
  }

  @Test
  public void testGroupByExpressionMultiMultiAuto()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "map((x) -> concat(x, othertags), tags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3u1", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-multi-multi-auto");
  }

  @Test
  public void testGroupByExpressionMultiMultiAutoAuto()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "concat(tags, othertags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3u1", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-multi-multi-auto-auto");
  }

  @Test
  public void testGroupByExpressionMultiMultiAutoAutoDupeIdentifier()
  {
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "concat(tags, tags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimitSpec(new DefaultLimitSpec(ImmutableList.of(new OrderByColumnSpec("count", OrderByColumnSpec.Direction.DESCENDING)), 5))
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3t3", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t5t5", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(
            query,
            "1970",
            "texpr",
            NullHandling.emptyToNullIfNeeded(""),
            "count",
            2L
        ),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1t1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2t2", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-multi-multi-auto-auto-self");
  }

  @Test
  public void testGroupByExpressionMultiMultiAutoAutoWithFilter()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "concat(tags, othertags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setDimFilter(new SelectorDimFilter("texpr", "t1u1", null))
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t1u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u1", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t2u2", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "texpr", "t3u1", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-multi-multi-auto-auto");
  }

  @Test
  public void testGroupByExpressionAuto()
  {
    // virtual column is a single input column and input is not used explicitly as an array,
    // so this one will work for group by v1, even with multi-value inputs
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "concat(tags, 'foo')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t1foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t2foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t3foo", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t4foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t5foo", "count", 4L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t6foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t7foo", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-auto");
  }

  @Test
  public void testGroupByExpressionArrayExpressionFilter()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 only supports dimensions with an outputType of STRING.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt", ValueType.LONG))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "array_offset_of(tags, 't2')",
                ValueType.LONG,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", NullHandling.replaceWithDefault() ? -1L : null, "count", 6L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", 1L, "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-auto");
  }

  @Test
  public void testGroupByExpressionArrayFnArg()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "array_to_string(map(tags -> concat('foo', tags), tags), ', ')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot1, foot2, foot3", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot3, foot4, foot5", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot5, foot6, foot7", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-array-fn");
  }

  @Test
  public void testGroupByExpressionAutoArrayFnArg()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "array_to_string(concat('foo', tags), ', ')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot1, foot2, foot3", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot3, foot4, foot5", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot5, foot6, foot7", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-arrayfn-auto");
  }

  @Test
  public void testGroupByExpressionFoldArrayToString()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "fold((tag, acc) -> concat(acc, tag), tags, '')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(
            query,
            "1970-01-01T00:00:00.000Z",
            "tt",
            NullHandling.replaceWithDefault() ? null : "",
            "count",
            2L
        ),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t1t2t3", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t3t4t5", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "t5t6t7", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-arrayfn-auto");
  }

  @Test
  public void testGroupByExpressionFoldArrayToStringWithConcats()
  {
    if (config.getDefaultStrategy().equals(GroupByStrategySelector.STRATEGY_V1)) {
      expectedException.expect(RuntimeException.class);
      expectedException.expectMessage("GroupBy v1 does not support dimension selectors with unknown cardinality.");
    }
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("tt", "tt"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "tt",
                "fold((tag, acc) -> concat(concat(acc, case_searched(acc == '', '', ', '), concat('foo', tag)))), tags, '')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    Sequence<ResultRow> result = helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    );

    List<ResultRow> expectedResults = Arrays.asList(
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foo", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot1, foot2, foot3", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot3, foot4, foot5", "count", 2L),
        GroupByQueryRunnerTestHelper.createExpectedRow(query, "1970", "tt", "foot5, foot6, foot7", "count", 2L)
    );

    TestHelper.assertExpectedObjects(expectedResults, result.toList(), "expr-arrayfn-auto");
  }

  @Test
  public void testGroupByExpressionMultiConflicting()
  {
    expectedException.expect(RuntimeException.class);
    expectedException.expectMessage(
        "Invalid expression: (concat [(map ([x] -> (concat [x, othertags])), [tags]), tags]); [tags] used as both scalar and array variables"
    );
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "concat(map((x) -> concat(x, othertags), tags), tags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    ).toList();
  }

  @Test
  public void testGroupByExpressionMultiConflictingAlso()
  {
    expectedException.expect(RuntimeException.class);
    expectedException.expectMessage(
        "Invalid expression: (array_concat [tags, (array_append [othertags, tags])]); [tags] used as both scalar and array variables"
    );
    GroupByQuery query = GroupByQuery
        .builder()
        .setDataSource("xx")
        .setQuerySegmentSpec(new LegacySegmentSpec("1970/3000"))
        .setGranularity(Granularities.ALL)
        .setDimensions(new DefaultDimensionSpec("texpr", "texpr"))
        .setVirtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "array_concat(tags, (array_append(othertags, tags)))",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .setLimit(5)
        .setAggregatorSpecs(new CountAggregatorFactory("count"))
        .setContext(context)
        .build();

    helper.runQueryOnSegmentsObjs(
        ImmutableList.of(
            new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
            new IncrementalIndexSegment(incrementalIndex, SegmentId.dummy("sid2"))
        ),
        query
    ).toList();
  }

  @Test
  public void testTopNWithDimFilterAndWithFilteredDimSpec()
  {
    TopNQuery query = new TopNQueryBuilder()
        .dataSource("xx")
        .granularity(Granularities.ALL)
        .dimension(new ListFilteredDimensionSpec(
            new DefaultDimensionSpec("tags", "tags"),
            ImmutableSet.of("t3"),
            null
        ))
        .metric("count")
        .intervals(QueryRunnerTestHelper.FULL_ON_INTERVAL_SPEC)
        .aggregators(new CountAggregatorFactory("count"))
        .threshold(5)
        .filters(new SelectorDimFilter("tags", "t3", null))
        .build();

    try (CloseableStupidPool<ByteBuffer> pool = TestQueryRunners.createDefaultNonBlockingPool()) {
      QueryRunnerFactory factory = new TopNQueryRunnerFactory(
          pool,
          new TopNQueryQueryToolChest(new TopNQueryConfig()),
          QueryRunnerTestHelper.NOOP_QUERYWATCHER
      );
      QueryRunner<Result<TopNResultValue>> runner = QueryRunnerTestHelper.makeQueryRunner(
          factory,
          new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
          null
      );
      Sequence<Result<TopNResultValue>> result = runner.run(QueryPlus.wrap(query));
      List<Result<TopNResultValue>> expectedResults = Collections.singletonList(
          new Result<TopNResultValue>(
              DateTimes.of("2011-01-12T00:00:00.000Z"),
              new TopNResultValue(
                  Collections.<Map<String, Object>>singletonList(
                      ImmutableMap.of(
                          "tags", "t3",
                          "count", 2L
                      )
                  )
              )
          )
      );
      TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filteredDim");
    }
  }

  @Test
  public void testTopNExpression()
  {
    TopNQuery query = new TopNQueryBuilder()
        .dataSource("xx")
        .granularity(Granularities.ALL)
        .dimension(new DefaultDimensionSpec("texpr", "texpr"))
        .virtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "map(x -> concat(x, 'foo'), tags)",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .metric("count")
        .intervals(QueryRunnerTestHelper.FULL_ON_INTERVAL_SPEC)
        .aggregators(new CountAggregatorFactory("count"))
        .threshold(15)
        .build();

    try (CloseableStupidPool<ByteBuffer> pool = TestQueryRunners.createDefaultNonBlockingPool()) {
      QueryRunnerFactory factory = new TopNQueryRunnerFactory(
          pool,
          new TopNQueryQueryToolChest(new TopNQueryConfig()),
          QueryRunnerTestHelper.NOOP_QUERYWATCHER
      );
      QueryRunner<Result<TopNResultValue>> runner = QueryRunnerTestHelper.makeQueryRunner(
          factory,
          new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
          null
      );
      Sequence<Result<TopNResultValue>> result = runner.run(QueryPlus.wrap(query));
      List<Map<String, Object>> expected =
          ImmutableList.<Map<String, Object>>builder()
                       .add(ImmutableMap.of("texpr", "t3foo", "count", 2L))
                       .add(ImmutableMap.of("texpr", "t5foo", "count", 2L))
                       .add(ImmutableMap.of("texpr", "foo", "count", 1L))
                       .add(ImmutableMap.of("texpr", "t1foo", "count", 1L))
                       .add(ImmutableMap.of("texpr", "t2foo", "count", 1L))
                       .add(ImmutableMap.of("texpr", "t4foo", "count", 1L))
                       .add(ImmutableMap.of("texpr", "t6foo", "count", 1L))
                       .add(ImmutableMap.of("texpr", "t7foo", "count", 1L))
                       .build();

      List<Result<TopNResultValue>> expectedResults = Collections.singletonList(
          new Result<TopNResultValue>(
              DateTimes.of("2011-01-12T00:00:00.000Z"),
              new TopNResultValue(
                  expected
              )
          )
      );
      TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filteredDim");
    }
  }

  @Test
  public void testTopNExpressionAutoTransform()
  {
    TopNQuery query = new TopNQueryBuilder()
        .dataSource("xx")
        .granularity(Granularities.ALL)
        .dimension(new DefaultDimensionSpec("texpr", "texpr"))
        .virtualColumns(
            new ExpressionVirtualColumn(
                "texpr",
                "concat(tags, 'foo')",
                ValueType.STRING,
                TestExprMacroTable.INSTANCE
            )
        )
        .metric("count")
        .intervals(QueryRunnerTestHelper.FULL_ON_INTERVAL_SPEC)
        .aggregators(new CountAggregatorFactory("count"))
        .threshold(15)
        .build();

    try (CloseableStupidPool<ByteBuffer> pool = TestQueryRunners.createDefaultNonBlockingPool()) {
      QueryRunnerFactory factory = new TopNQueryRunnerFactory(
          pool,
          new TopNQueryQueryToolChest(new TopNQueryConfig()),
          QueryRunnerTestHelper.NOOP_QUERYWATCHER
      );
      QueryRunner<Result<TopNResultValue>> runner = QueryRunnerTestHelper.makeQueryRunner(
          factory,
          new QueryableIndexSegment(queryableIndex, SegmentId.dummy("sid1")),
          null
      );
      Sequence<Result<TopNResultValue>> result = runner.run(QueryPlus.wrap(query));

      List<Map<String, Object>> expected =
          ImmutableList.<Map<String, Object>>builder()
              .add(ImmutableMap.of("texpr", "t3foo", "count", 2L))
              .add(ImmutableMap.of("texpr", "t5foo", "count", 2L))
              .add(ImmutableMap.of("texpr", "foo", "count", 1L))
              .add(ImmutableMap.of("texpr", "t1foo", "count", 1L))
              .add(ImmutableMap.of("texpr", "t2foo", "count", 1L))
              .add(ImmutableMap.of("texpr", "t4foo", "count", 1L))
              .add(ImmutableMap.of("texpr", "t6foo", "count", 1L))
              .add(ImmutableMap.of("texpr", "t7foo", "count", 1L))
              .build();

      List<Result<TopNResultValue>> expectedResults = Collections.singletonList(
          new Result<TopNResultValue>(
              DateTimes.of("2011-01-12T00:00:00.000Z"),
              new TopNResultValue(
                  expected
              )
          )
      );
      TestHelper.assertExpectedObjects(expectedResults, result.toList(), "filteredDim");
    }
  }

  @After
  public void cleanup() throws Exception
  {
    queryableIndex.close();
    incrementalIndex.close();
    FileUtils.deleteDirectory(persistedSegmentDir);
  }
}


text:
package de.fu_berlin.imp.apiua.entity.preferences;

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

import org.eclipse.jface.util.PropertyChangeEvent;

import com.bkahlert.nebula.utils.EclipsePreferenceUtil;

import de.fu_berlin.imp.apiua.core.model.DataSource;
import de.fu_berlin.imp.apiua.entity.Activator;

public class SUAEntityPreferenceUtil extends EclipsePreferenceUtil {

	public SUAEntityPreferenceUtil() {
		super(Activator.getDefault());
	}

	public boolean getFilterDiffs() {
		return getPreferenceStore().getBoolean(
				SUAEntityPreferenceConstants.FILTER_DIFFS);
	}

	public boolean filterDiffsChanged(PropertyChangeEvent event) {
		return event.getProperty().equals(
				SUAEntityPreferenceConstants.FILTER_DIFFS);
	}

	public boolean getFilterDoclogs() {
		return getPreferenceStore().getBoolean(
				SUAEntityPreferenceConstants.FILTER_DOCLOGS);
	}

	public boolean filterDoclogsChanged(PropertyChangeEvent event) {
		return event.getProperty().equals(
				SUAEntityPreferenceConstants.FILTER_DOCLOGS);
	}

	public boolean getFilterSurveys() {
		return getPreferenceStore().getBoolean(
				SUAEntityPreferenceConstants.FILTER_SURVEYS);
	}

	public boolean filterSurveysChanged(PropertyChangeEvent event) {
		return event.getProperty().equals(
				SUAEntityPreferenceConstants.FILTER_SURVEYS);
	}

	public List<DataSource> getFilterdDataSources() {
		List<DataSource> filteredDataSources = new ArrayList<DataSource>();
		if (this.getFilterDiffs())
			filteredDataSources.add(DataSource.DIFFS);
		if (this.getFilterDoclogs())
			filteredDataSources.add(DataSource.DOCLOG);
		if (this.getFilterSurveys())
			filteredDataSources.add(DataSource.SURVEYRECORD);
		return filteredDataSources;
	}
}

redpajama_set_name: RedPajamaGithub


text:
//
// HtmlTagCallback.cs
//
// Author: Jeffrey Stedfast <jeff@xamarin.com>
//
// Copyright (c) 2013-2016 Xamarin Inc. (www.xamarin.com)
//
// 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.
//

namespace MimeKit.Text {
	/// <summary>
	/// An HTML tag callback delegate.
	/// </summary>
	/// <remarks>
	/// The <see cref="HtmlTagCallback"/> delegate is called when a converter
	/// is ready to write a new HTML tag, allowing developers to customize
	/// whether the tag gets written at all, which attributes get written, etc.
	/// </remarks>
	/// <example>
	/// <code language="c#" source="Examples\MimeVisitorExamples.cs" region="HtmlPreviewVisitor" />
	/// </example>
	/// <param name="tagContext">The HTML tag context.</param>
	/// <param name="htmlWriter">The HTML writer.</param>
	public delegate void HtmlTagCallback (HtmlTagContext tagContext, HtmlWriter htmlWriter);
}


text:
import { useCallback } from 'react';

import { useQueryParams } from 'app/core/hooks/useQueryParams';
import store from 'app/core/store';

import { ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, ALERTMANAGER_NAME_QUERY_KEY } from '../utils/constants';
import { AlertManagerDataSource, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';

function useIsAlertManagerAvailable(availableAlertManagers: AlertManagerDataSource[]) {
  return useCallback(
    (alertManagerName: string) => {
      const availableAlertManagersNames = availableAlertManagers.map((am) => am.name);
      return availableAlertManagersNames.includes(alertManagerName);
    },
    [availableAlertManagers]
  );
}

/* This will return am name either from query params or from local storage or a default (grafana).
 * Due to RBAC permissions Grafana Managed Alert manager or external alert managers may not be available
 * In the worst case neihter GMA nor external alert manager is available
 */
export function useAlertManagerSourceName(
  availableAlertManagers: AlertManagerDataSource[]
): [string | undefined, (alertManagerSourceName: string) => void] {
  const [queryParams, updateQueryParams] = useQueryParams();
  const isAlertManagerAvailable = useIsAlertManagerAvailable(availableAlertManagers);

  const update = useCallback(
    (alertManagerSourceName: string) => {
      if (!isAlertManagerAvailable(alertManagerSourceName)) {
        return;
      }
      if (alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME) {
        store.delete(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
        updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: null });
      } else {
        store.set(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY, alertManagerSourceName);
        updateQueryParams({ [ALERTMANAGER_NAME_QUERY_KEY]: alertManagerSourceName });
      }
    },
    [updateQueryParams, isAlertManagerAvailable]
  );

  const querySource = queryParams[ALERTMANAGER_NAME_QUERY_KEY];

  if (querySource && typeof querySource === 'string') {
    if (isAlertManagerAvailable(querySource)) {
      return [querySource, update];
    } else {
      // non existing alertmanager
      return [undefined, update];
    }
  }

  const storeSource = store.get(ALERTMANAGER_NAME_LOCAL_STORAGE_KEY);
  if (storeSource && typeof storeSource === 'string' && isAlertManagerAvailable(storeSource)) {
    update(storeSource);
    return [storeSource, update];
  }

  if (isAlertManagerAvailable(GRAFANA_RULES_SOURCE_NAME)) {
    return [GRAFANA_RULES_SOURCE_NAME, update];
  }

  return [undefined, update];
}


text:
A commandline-tool to check the fund income

![screenshot](http://ww2.sinaimg.cn/large/6273fe87gw1edvsjzz9q5j20k20ecgqd.jpg)

### Installation
````
$ [sudo] npm install xxb -g
````

### Usage
Just run `xxb` , you will get the pretty table.
````
$ xxb
````

### Configuration

Configuration options are set in the ~/.xxb file. If xxb cannot find an user defined configuration file in the home directory, the bundled config.json used instead.

Example configuration:
````
{
    "funds": [
      {
        "code": "000343",
        "bao": "理财通",
        "platform": "微信"
      }
    ]
}
````

### Changelog
- 1.0.2 fix [#11](https://github.com/coolzilj/xxb/issues/11)
- 1.0.1 fix: source update for "7日年化收益率". [#10](https://github.com/coolzilj/xxb/pull/10)
- 1.0.0 Update default config, fix homepath on windows. [#7](https://github.com/coolzilj/xxb/pull/7) [#8](https://github.com/coolzilj/xxb/pull/8)
- 0.0.4 Respect http_proxy & https_proxy environment variable. [#6](https://github.com/coolzilj/xxb/pull/6)
- 0.0.3 Add "广发天天红基金" to default config. [#5](https://github.com/coolzilj/xxb/pull/5)
- 0.0.2 Sort table by price desc. [#3](https://github.com/coolzilj/xxb/pull/3)
- 0.0.1 Hello xxb.

### Contributing
- Fork this repo
- Clone your repo
- Install dependencies
- Checkout a feature branch
- Feel free to add your features
- Make sure your features are fully tested
- Open a pull request, and enjoy <3

---
Handcraft by [Liu Jin](https://github.com/coolzilj)


text:
---
layout: page
title: "Test du capteur de température DS18B20 avec le protocole OneWire"
modified:
categories:
excerpt:
tags: []
image:
     feature:
date: 2016-05-21T19:00:00+02:00
published: true
author: Nico
---

![...](/files/2016-05-21-test_capteur_temp_DS18B20/2016-05-21-test_capteur_temp_DS18B20_003_lowres.jpg)

![...](/files/2016-05-21-test_capteur_temp_DS18B20/2016-05-21-test_capteur_temp_DS18B20_001_lowres.jpg)

![...](/files/2016-05-21-test_capteur_temp_DS18B20/2016-05-21-test_capteur_temp_DS18B20_002_lowres.jpg)

~~~ c++

/*

PROGRAMME BASIQUE POUR LIRE UN CAPTEUR DE TEMPÉRATURE DS18B20
AVEC LE PROTOCOLE OneWire (= Dallas)

RÉFÉRENCE AliExpress DU CAPTEUR
http://fr.aliexpress.com/item/Stainless-steel-package-Waterproof-DS18b20-temperature-probe-temperature-sensor-18B20-For-Arduino/32236998050.html

VERSION ORIGINALE DU PROGRAMME
http://www.milesburton.com/?title=Dallas_Temperature_Control_Library

LIBRAIRIE OneWire
https://github.com/PaulStoffregen/OneWire.git

LIBRAIRIE DallasTemperature
https://github.com/milesburton/Arduino-Temperature-Control-Library

CONNEXIONS
fil rouge    +5V
fil noir     GND
fil jaune    données OneWire PORTD2 (pin 2)
Résistance pull-up de 4.7 kΩ entre le signal (fil jaune) et +5V (fil rouge)

MICROCONTRÔLEUR
Clone Arduino Nano

REMARQUES
- Le protocole OneWire est lent, il faut environ 780 ms pour une lecture.
- Le capteur DS18B20 n'est pas très réactif, il faut environ 5 min
  pour qu'il se stabilise.

mai 2016, ouilogique.com

*/

#include <OneWire.h>
#include <DallasTemperature.h>

// Le fil des données est connectés à la broche 2
#define ONE_WIRE_BUS 2

// Initialisation d'une instance pour communiquer avec le protocole OneWire
OneWire oneWire( ONE_WIRE_BUS );

// Initialise DallasTemperature avec la référence à OneWire.
DallasTemperature sensors( &oneWire );

void setup()
{
  Serial.begin( 115200 );
  Serial.print( "Demo capteur de temperature DS18B20\n" );

  // Démarre le processus de lecture.
  // IC Default 9 bit. If you have troubles consider upping it 12.
  // Ups the delay giving the IC more time to process the temperature
  // measurement
  sensors.begin();
}

void loop()
{
  long t1 = millis();

  // Requête de toutes les températures disponibles sur le bus
  sensors.requestTemperatures();
  // On ne garde que la première température (index = 0)
  float Temp = sensors.getTempCByIndex( 0 );

  long t2 = millis();
  long dt = t2 - t1;

  Serial.print( "t = " );
  Serial.print( t2 );
  Serial.print( " ms\t" );

  Serial.print( "dt = " );
  Serial.print( dt );
  Serial.print( " ms\t" );

  Serial.print( "T = " );
  Serial.print( Temp, 1 );
  Serial.print( " degC\n" );
}

~~~


text:
/*
Script: CiUI.js

Handles iPhone-optimized pages and mimics iPhone UI behavior

License:
	http://clientside.cnet.com/wiki/cnet-libraries#license
*/

(function() {

var bodyEl; // see ciUI.initialize() for details
var buttonForward = "go_forward";
var buttonBackward = "go_back";
var buttonLoadMore = "load_more";
var backButtonEl; // see ciUI.initialize() for details
var backButtonTextEl; // see ciUI.initialize() for details
var pageTitleEl; // see ciUI.initialize() for details
var update = "do_update";
var updateAttr = "update";

var pages = []; // see ciUI.initialize() for details
var pageHistory = [];

var homePage = location.href;
var hashPrefix = "#__";
var currentHash = location.hash;
var currentPage = 1; // possible values are 1 or -1 (1: from, -1: to)

var animSpeed = 20; // smaller number means slower animation
var navigationCheckInterval = 300;
var navigtionChangeTimer;
var uiIsActive;

window.ciUI = {
	initialize: function() {
		bodyEl = $("iphone_body");
		backButtonEl = $("iphone_backbutton");
		backButtonTextEl = $("iphone_backbutton_text");
		pageTitleEl = $("iphone_title");
		if (!bodyEl || !backButtonEl || !backButtonTextEl || !pageTitleEl) return;
		if (pageTitleEl && pageTitleEl.innerHTML == "") pageTitleEl.innerHTML = document.title;
		this.setupPages();
		this.animating = false;
		setTimeout(scrollTo, 100, 0, 1);
		return true;
	},

	setupPages: function() {
		var page1 = document.createElement('div');
		var page2 = document.createElement('div');
		var home  = document.createElement('div');
		var ascrolltop = document.createElement('a');

		page1.id = "__page1__";
		page1.className = "iphone_page";
		page1.style.left = "0%";
		page1.style.display = "block";

		page2.id = "__page2__";
		page2.className = "iphone_page";
		page2.style.left = "100%";
		page2.style.display = "none";

		home.id	= "__home__";
		home.style.display = "none";
		ascrolltop.id = "__scroll_top__";
		ascrolltop.name = "";

		page1.innerHTML = home.innerHTML = bodyEl.innerHTML;
		bodyEl.innerHTML = "";
		bodyEl.appendChild(page1);
		bodyEl.appendChild(page2);
		document.body.appendChild(home);
		document.body.insertBefore(ascrolltop, document.body.firstChild);

		this.adjustBodyToEl(page1);

		pages[1] = $(page1.id);
		pages[0] = $(home.id);
		pages[-1] = $(page2.id);
	},

	goToPage: function(target, backwards) {
		if (backwards === undefined) backwards = false;

		this.slidePages(backwards);
		// BACKWARD
		if (backwards) {
			currentHash = location.hash;
			pageHistory.splice(pageHistory.indexOf(target)+1, pageHistory.length);
		}
		// FORWARD
		else {
   			pageHistory.push(target);
		}
		this.updatePage(target);
	},

	loadMore: function(target) {
		target.className = "load_more_loading";
		(new this.ajax(target.href, updatePage)).run();

		function updatePage(content) {
			target.parentNode.removeChild(target);
			pages[currentPage].innerHTML += content;
			ciUI.adjustBodyToEl(pages[currentPage]);
		}
	},

	updatePage: function(target) {
		var nextHash = hashPrefix + (pageHistory.length);
		backButtonEl.style.display = (pageIndexFromHash(nextHash) == 0 || !target) ? "none" : "block";
		// Update body with new page content
		if (pageIndexFromHash(nextHash) == 0 || !target) {
			pageTitleEl.innerHTML = trim(document.title);
			this.updatePageContent(pages[0].innerHTML); // page[0] is home, no need for AJAX
		}
		else {
			backButtonTextEl.innerHTML = (pageHistory[pageHistory.length-2]) ? trim(pageHistory[pageHistory.length-2].title, 15) : "Back";
			pageTitleEl.innerHTML = trim(target.title);
			var url = target.href;
			if (!url && target.tagName.toLowerCase() == "form") url = target.action+"?"+target.toQueryString();
			(new this.ajax(url, this.updatePageContent)).run();
		}
	},

	updatePageContent: function(content) {
		var updateTimer = setInterval(update, 0);

		function update() {
			if (!ciUI.animating) {
				$("__scroll_top__").name = currentHashName(true);
				pages[currentPage].innerHTML = content;
				pages[-currentPage].innerHTML = "";
				clearInterval(updateTimer);
				ciUI.adjustBodyToEl(pages[currentPage]);
				location.href = currentHash = currentHashName();
				if (ciUI.callbackFunct) ciUI.callbackFunct();
			}
		}
	},

	slidePages: function(backwards) {
		this.animating = true;

		var fromPage = pages[currentPage];
		var toPage = pages[-currentPage];
		var progress = 100;

		toPage.innerHTML = this.loadingPage();
		toPage.style.display = "block";

		if (!backwards) toPage.style.left = "100%";

		clearInterval(navigationChangeTimer); // pause the history daemon during animation.
		setTimeout(scrollTo, 100, 0, 1);
		var animTimer = setInterval(animate, 0);

		function animate() {
			progress -= animSpeed;

			if (progress <= 0) {
				clearInterval(animTimer);
				navigationChangeTimer = setInterval(navigationChangeAgent, navigationCheckInterval); // restart the history daemon
				currentPage *= -1;
				progress = 0;
				ciUI.animating = false;
			}
			fromPage.style.left = (backwards ? (100 - progress) : (progress - 100)) + "%";
			toPage.style.left = (backwards ? -progress : progress) + "%";
		}
	},

	ajax: function(sourceURL, responseConsumer, responseType) {
		var httpRequest = false;

		this.run = function()
		{
			if (responseType === undefined) responseType = "TEXT";
			responseType = responseType.toUpperCase();

			if (window.XMLHttpRequest) {
				httpRequest = new XMLHttpRequest();
				if (httpRequest.overrideMimeType) {
					httpRequest.overrideMimeType('text/xml');
				}
			}
			else if (window.ActiveXObject) {
				try {
					httpRequest = new ActiveXObject("Msxml2.XMLHTTP");
				}
				catch (e) {
					try {
						httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
					}
					catch (e) {}
				}
			}

			if (!httpRequest) {
				//console.log('Giving up, cannot create an XMLHTTP instance');
				return false;
			}

			httpRequest.open('GET', sourceURL, true);
			httpRequest.onreadystatechange = getResponse;
			httpRequest.send(null);
		};

		function getResponse() {
			if (httpRequest.readyState == 4)
				if (httpRequest.status == 200) {
					if (responseType == "XML")
						responseConsumer(httpRequest.responseXML);
					else
						responseConsumer(httpRequest.responseText);
				}
				else
					;//console.log("There was a problem with the request: " + sourceUrl);
		}
	},

	update: function(url, element) {
		(new this.ajax(url, function(html){
			if (!element.tagName) element = $(element);
			element.innerHTML = html;
		})).run();
	},

	cancel: function(event) {
		pageHistory = [];
		location.reload(true);
		location.hash = "";
		location.href = homePage;
		return false;
	},

	loadingPage: function() {
		return $("iphone_loading_page").innerHTML;
	},

	// a dirty hack to move the footer to proper location since iphone_body doesn't expand with content
	adjustBodyToEl: function(el) {
		bodyEl.style.height = el.offsetHeight + "px";
	}
};

addEventListener("load", function(event) {
	uiIsActive = ciUI.initialize();
	if (uiIsActive) navigationChangeTimer = setInterval(navigationChangeAgent, navigationCheckInterval);
}, false);

addEventListener("click", function(event) {
	if (!uiIsActive) return;
	var a = findParent(event.target, "a");
	// We don't want to prevent defaults by default because there may be actual hrefs going to outside pages
	if (a && a.hasClass(buttonForward)) {
		ciUI.goToPage(a);
		event.preventDefault();
	} else if (a && a.hasClass(buttonBackward)) {
		history.back();
		event.preventDefault();
	} else if (a && a.hasClass(buttonLoadMore)) {
		ciUI.loadMore(a);
		event.preventDefault();
	}
}, true);

addEventListener("submit", function(event) {
	if (!uiIsActive) return;
	var a;
	var form = findParent(event.target, "form");
	if (!form) return;
	if (form.hasClass(buttonForward)) {
		event.preventDefault();
		return ciUI.goToPage(form);
	} else if (form.hasClass(update)) {
		event.preventDefault();
		ciUI.update(form.action+"?"+form.toQueryString(), form.getAttribute(updateAttr));
		}
}, true);

addEventListener("click", function(event) {
	if (!uiIsActive) return;
	var a = findParent(event.target, ("a"));
	if (!a || !a.hasClass(update)) return;
	ciUI.update(a.href, a.getAttribute(updateAttr));
}, true);

function trim(text, maxLength) {
	if (maxLength === undefined) maxLength = 20;
	return (text.length > maxLength) ? text.substring(0, maxLength - 3) + "..." : text;
};

function currentHashName(ommitHashSymbol) {
	return (ommitHashSymbol) ? hashPrefix.substr(1, hashPrefix.length) + (pageHistory.length) : hashPrefix + (pageHistory.length);
};

function navigationChangeAgent() {
	if (currentHash != location.hash) {
		ciUI.goToPage(pageHistory[pageHistory.length-2], true);
	}
};

function pageIndexFromHash(hash) {
	return (hash) ? hash.substr(hash.lastIndexOf("_") + 1, hash.length) : 0;
};

Element.prototype.hasClass = function(name) {
	return this.className.indexOf(name) != -1;
};

Element.prototype.toQueryString = function(){
	var qs = "";
	var getvals = function(element){
		var kids = element.childNodes;
		for (var i=0; i < kids.length; i++) {
			var el = kids[i];
			if (el.tagName) {
				var t = el.tagName.toLowerCase();
				if (t.toLowerCase() == "input" || t.toLowerCase() == "textarea") qs += el.name + "=" + escape(el.value) + "&";
				else if (t == "select") qs += el.name + "=" + escape(el.options[el.selectedIndex].value) + "&"; //no support for multiselect yet
				else getvals(el);
			}
		}
	};
	getvals(this);
	return qs;
}

// This function is courtesy of iUI
function findParent(node, localName) {
    while (node && (node.nodeType != 1 || node.localName.toLowerCase() != localName))
        node = node.parentNode;
    return node;
};

function $(id) { return document.getElementById(id); };

})();


text:
import { UrlSerializer, UrlTree, DefaultUrlSerializer } from '@angular/router';

export class CustomUrlSerializer implements UrlSerializer {

  parse(url: any): UrlTree {
    let dus = new DefaultUrlSerializer();
    return dus.parse(url);
  }

  serialize(tree: UrlTree): any {
    let dus = new DefaultUrlSerializer(),
        path = dus.serialize(tree);

    const at = new RegExp(/%40/g);
    const space = new RegExp(/%20/g);

    path = path.replace(at, '@');
    path = path.replace(space, ' ');

   // use your regex to replace as per your requirement.
    return path;
  }

}


text:
<?xml version="1.0" encoding="utf-8"?>

<!--
  ~ The MIT License (MIT)
  ~
  ~ Copyright (c) 2014 Myriad Mobile
  ~
  ~ 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.
  -->

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DetailActivity">

    <com.myriadmobile.library.hopscotch.example.SimplePathView
        android:id="@+id/image_path1"
        android:background="#FFF"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

    <com.myriadmobile.library.hopscotch.example.SimplePathView
        android:id="@+id/image_path2"
        android:layout_marginTop="@dimen/activity_vertical_margin"
        android:background="#FFF"
        android:layout_width="match_parent"
        android:layout_height="200dp" />

</LinearLayout>


text:
package com.heaven7.util.extra.collection;

/**
 * the predicate of element
 *
 * @param <T> the  item type
 * @since 1.1.0
 */
public interface ElementPredicate<T> {

    /**
     * called when we want to predicate the item with the param.
     *
     * @param t     the item
     * @param param the other param
     * @return true if predicate ok.
     */
    boolean test(T t, Object param);
}


text:
Imports System.Text
Imports System
Imports System.Runtime
Imports System.IO
Imports System.Collections
Imports System.Runtime.InteropServices
Imports System.Reflection
Imports System.ComponentModel

Namespace SampleNamespace

	''' <summary>
	''' This is a class definition.
	''' </summary>
	Public Class SampleClass

		#Region "Fields"

		'This field has a regular comment
		Dim _simpleField As Boolean
		Private _fieldWithInitialVal As Integer = 1
		Private Dim _dimField As Boolean

		''' <summary>
		''' This is a static readonly string
		''' </summary>
		Protected Shared ReadOnly StaticStr As String = "static; string;"

		Private _genericField As Nullable(Of Integer)
		Protected Friend _arrayField As String() = {}
		Friend internal As Boolean

		''' <summary>
		''' This field has an attribute
		''' </summary>
		<ThreadStatic()> _
		Private Shared _attributedField As String = Nothing

		Public Const ConstantStr As String = "constant string"

		Private _val1, _val2 As Integer
		Private Shared _val3 , _val4 ,_val5, _val6 As Integer
		Private _val7 As Integer : Private _val8 As Integer

		#End Region

		#Region "Constructors"

		''' <summary>
		''' Instance constructor
		''' </summary>
		Public Sub New()
		End Sub

		''' <summary>
		''' Internal constructor with params
		''' </summary>
		''' <param name="arrayParam"></param>
		Friend Sub New(ByVal arrayParam As String())
		End Sub

		' Static constructor
		REM Another comment
		'' Not an XML comment
		''' XML comment
		Shared Sub New()
		End Sub		' New

		#End Region

		#Region "Properties"

		''' <summary>
		''' Simple property
		''' </summary>
		Public Property SimpleProperty() As Boolean
			Get
				Return _simpleField
			End Get
			Set
				_simpleField = value
			End Set
		End Property

		'
		' This is a protected property.  Also virtual.
		'
		Protected Overridable ReadOnly Property ProtectedProperty() As Integer
			Get
				Return _fieldWithInitialVal
			End Get
		End Property

		''' <summary>
		''' This property is static
		''' </summary>
		' Mixed comment style here
		Public Shared ReadOnly Property StaticProperty() As String
			Get
				Return StaticStr + " is returned."
			End Get
		End Property

		' This property has multiple attributes and different
		' ordering for the static specification.
		<Obsolete("This property has attributes.")> _
		<Description("Multiple attribute property.")> _
		Public Shared ReadOnly Property AttributedProperty() As String
			Get
				Return _attributedField
			End Get
		End Property

		''' <summary>
		''' Generic property.  This comment has extra whitespace
		''' before the member, but should still be considered header comments.
		''' This member also has an attribute before the comment
		''' that should still be matched to the property.
		''' </summary>

		<Obsolete(), Description("Multiple attribute property.")> _
		Friend Property GenericProperty() As Nullable(Of Integer)
			Get
				Return _genericField
			End Get
			Set
				_genericField = value
				If _genericField.HasValue Then
					internal = True
				End If
			End Set
		End Property

		''' <summary>
		''' This property returns an array
		''' </summary>
		Public Shadows ReadOnly Property ArrayProperty() As String()
			Get
				Return _arrayField
			End Get
		End Property

		''' <summary>
		''' Indexer property
		''' </summary>
		''' <param name="index"></param>
		''' <returns></returns>
		Public Default Property Item(ByVal index As Integer) As String
			Get
				Return _arrayField(index)
			End Get
			Set
				_arrayField(index) = value
			End Set
		End Property

		Public Default Property Item(ByVal string1 As String, ByVal string2 As String) As Integer
			Get
				Return 0
			End Get
			Set
			End Set
		End Property

		#End Region

		#Region "Methods"

		''' <summary>
		''' Finalizer
		''' </summary>
		''' <remarks></remarks>
		Protected Overrides Sub Finalize()
			Try

			Finally
				MyBase.Finalize()
			End Try
		End Sub

		' Simple method
		Public Sub DoSomething()
			'
			' Make sure we detect that we're in a string while
			' parsing the following line.
			'
			Console.WriteLine("}")
			RaiseEvent ExplicitEvent(Me, false)
		End Sub

		'
		' Comment here
		'
		Public Overloads Overrides Function ToString() As String
			' This comment has a block end character in it. }

			Return "SampleClass"
		End Function

		''' <summary>
		''' Simple method with params and a return value
		''' </summary>
		''' <param name="intParam"></param>
		''' <param name="stringParam"></param>
		''' <returns></returns>
		Private Function GetBoolValue(ByVal intParam As Integer, ByVal stringParam As String) As Boolean
			Return True
		End Function

		''' <summary>
		''' This method has parameter attributes
		''' </summary>
		''' <param name="intParam">Int parameter</param>
		''' <returns></returns>
		<Description("Method with parameter attributes")> _
		Friend Shared Function GetWithParamAttributes( _
			<Description("Int parameter")> _
			ByVal intParam As Integer, _
			<Description("String parameter")> _
			ByVal stringParam As String) As Nullable(Of Integer)
			If intParam = 0 Then
				Return Nothing
			Else
				Return intParam
			End If
		End Function

		''' <summary>
		''' This method has parameter attributes
		''' </summary>
		Public Function GetWithTypeParameters(Of T1 As {IDisposable, New}, T2 As {IDisposable, New})(ByVal typeParam1 As Action(Of T1), ByVal typeParam2 As Action(Of T2)) As Boolean
			Try
				typeParam1(New T1())
				typeParam2(New T2())
				Return True
			Catch generatedExceptionName As InvalidOperationException
				Return False
			End Try
		End Function

		<DllImport("User32.dll")> _
		Public Shared Function MessageBox(ByVal h As Integer, ByVal m As String, ByVal c As String, ByVal type As Integer) As Integer
		End Function

		#End Region

		#Region "Delegates"

		''' <summary>
		''' Sample delegate definition
		''' </summary>
		''' <param name="sender"></param>
		''' <param name="boolParam"></param>
		Public Delegate Sub SampleEventHandler(ByVal sender As Object, ByVal boolParam As Boolean)

		''' <summary>
		''' Generic delegate
		''' </summary>
		''' <typeparam name="T"></typeparam>
		''' <param name="t1"></param>
		''' <param name="t2"></param>
		''' <returns></returns>
		Private Delegate Function Compare(Of T As Class)(ByVal t1 As T, ByVal t2 As T) As Integer

		#End Region

		#Region "Events"

		''' <summary>
		''' Simple event
		''' </summary>
		Public Event SimpleEvent As SampleEventHandler

		''' <summary>
		''' Generic event
		''' </summary>
		Public Event GenericEvent As EventHandler(Of EventArgs)

		''' <summary>
		''' Another event
		''' </summary>
		Public Event AnotherEvent(ByVal args As EventArgs)

		''' <summary>
		''' Explicit event
		''' </summary>
		Public Custom Event ExplicitEvent As SampleEventHandler
			AddHandler(ByVal value As SampleEventHandler)
			End AddHandler
			RemoveHandler(ByVal value As SampleEventHandler)
			End RemoveHandler
      		RaiseEvent(ByVal sender As Object, ByVal boolParam As Boolean)
      		End RaiseEvent
		End Event

		#End Region

		#Region "Nested Types"

		''' <summary>
		''' Sample enumeration
		''' </summary>
		<Flags()> _
		Private Enum SampleEnum
			None = 0
			Some = 1
			More = 2
		End Enum

		''' <summary>
		''' Nested structure
		''' </summary>
		Public Structure SampleStructure
			Public ReadOnly Name As String

			''' <summary>
			''' Creates a new SampleStructure
			''' </summary>
			Public Sub New(ByVal name As String)
				Name = name
			End Sub
		End Structure

		''' <summary>
		''' Nested class
		''' </summary>
		Private Class SampleNestedClass(Of T As New)
			''' <summary>
			''' Creates a new SampleNestedClass
			''' </summary>
			Public Sub New()
			End Sub
		End Class

		''' <summary>
		''' Sample nested, static class (Note: VB static classes are sealed with a private constructor)
		''' </summary>
		Friend NotInheritable Class SampleNestedStaticClass
			Private Sub New()
			End Sub
			Public Shared Function DoSomething(ByVal stringParam As String) As Boolean
				Return True
			End Function
		End Class

		#End Region

	End Class
End Namespace


text:
pub mod console {
    use config::Verbosity;

    use term_painter::ToStyle;
    use term_painter::Color::*;
    use std;
    use std::io::Write;
    use std::sync::{Once, ONCE_INIT};

    static mut VERBOSITY: Option<Verbosity> = None;
    static INIT: Once = ONCE_INIT;

    pub fn init(verbosity: Verbosity) {
        unsafe {
            INIT.call_once(|| {
                VERBOSITY = Some(verbosity);
            });
        }
    }

    pub fn verbose<T: Into<String>>(msg: T) {
        if is_relevant(Verbosity::VERBOSE) { print!("{}", Cyan.paint(msg.into())) }
        let _ = std::io::stdout().flush();
    }

    pub fn verboseln<T: Into<String>>(msg: T) {
        if is_relevant(Verbosity::VERBOSE) { println!("{}", Cyan.paint(msg.into())) }
    }

    pub fn info<T: Into<String>>(msg: T) {
        if is_relevant(Verbosity::NORMAL) { println!("{}", Blue.paint(msg.into())) }
    }

    pub fn msgln<T: Into<String>>(msg: T) {
        println!("{}", msg.into())
    }

    pub fn msg<T: Into<String>>(msg: T) {
        print!("{}", msg.into());
        let _ = std::io::stdout().flush();
    }

    pub fn error<T: Into<String>>(msg: T) {
        if is_relevant(Verbosity::NORMAL) { println!("{}", Red.paint(msg.into())) }
    }

    pub fn warning<T: Into<String>>(msg: T) {
        if is_relevant(Verbosity::NORMAL) { println!("{}", Yellow.paint(msg.into())) }
    }

    fn is_relevant(my_verbosity: Verbosity) -> bool {
        let verbosity = unsafe { VERBOSITY.unwrap() };
        my_verbosity >= verbosity
    }
}

pub mod output {
    use super::console::msgln;

    error_chain! {
        errors {
           OutputFailed {
                description("Failed to print message")
                display("Failed to print message")
            }
        }
    }

    pub fn as_json(json: &str) -> Result<()> {
        msgln(json);
        Ok(())
    }
}

pub mod time {
    use std::time::{Duration, UNIX_EPOCH, SystemTime};
    use humantime;

    error_chain! {
        errors {
            FailedToParseDuration {
                description("Failed to parse duration")
                display("Failed to parse duration")
            }
        }
    }
    pub fn parse_duration(since: &str) -> Result<Duration> {
        let duration: Duration = humantime::parse_duration(since).chain_err(|| ErrorKind::FailedToParseDuration)?;
        let now = SystemTime::now();
        let history = (now - duration).duration_since(UNIX_EPOCH).chain_err(|| ErrorKind::FailedToParseDuration)?;

        Ok(history)
    }

    pub fn parse_duration_to_unix_ts(since: &str) -> Result<u64> {
        let unix_ts = parse_duration(since)?.as_secs();

        Ok(unix_ts)
    }
}

pub mod io {
    use std::io::{Read, Result, Write};

    pub struct ReadWithProgress<'a, T: Read + 'a, F: FnMut(usize, usize) -> () + 'a> {
        read: &'a mut T,
        size: usize,
        progress: Option<F>
    }

    impl<'a, T: Read + 'a, F: FnMut(usize, usize) -> () + 'a> ReadWithProgress<'a, T, F> {
        pub fn new(read: &'a mut T, size: usize, progress: Option<F>) -> Self {
            ReadWithProgress { read: read, size: size, progress: progress }
        }
    }

    impl<'a, T: Read + 'a, F: FnMut(usize, usize) -> () + 'a> Read for ReadWithProgress<'a, T, F> {
        fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
            let result = self.read.read(buf);
            if self.progress.is_some() && result.is_ok() {
                let delta = *result.as_ref().unwrap();
                let  progress = self.progress.as_mut().unwrap();
                progress(self.size, delta);
            }
            result
        }
    }

    pub struct WriteWithProgress<'a, T: Write + 'a, F: FnMut(usize, usize) -> () + 'a> {
        write: &'a mut T,
        size: usize,
        progress: Option<F>
    }

    impl<'a, T: Write + 'a, F: FnMut(usize, usize) -> () + 'a> WriteWithProgress<'a, T, F> {
        pub fn new(write: &'a mut T, size: usize, progress: Option<F>) -> Self {
            WriteWithProgress { write: write, size: size, progress: progress }
        }
    }

    impl<'a, T: Write + 'a, F: FnMut(usize, usize) -> () + 'a> Write for WriteWithProgress<'a, T, F> {
        fn write(&mut self, buf: &[u8]) -> Result<usize> {
            let result = self.write.write(buf);
            if self.progress.is_some() && result.is_ok() {
                let delta = *result.as_ref().unwrap();
                let  progress = self.progress.as_mut().unwrap();
                progress(self.size, delta);
            }
            result
        }

        fn flush(&mut self) -> Result<()> {
            self.write.flush()
        }
    }

 }


text:
echo

# Arithmetic Operators
# ---------- ---------

echo "Arithmetic Operators"
echo
a=`expr 5 + 3`
echo "5 + 3 = $a"

a=`expr $a + 1`
echo
echo "a + 1 = $a"
echo "(incrementing a variable)"

a=`expr 5 % 3`
# modulo
echo
echo "5 mod 3 = $a"

echo
echo

# Logical Operators
# ------- ---------

#  Returns 1 if true, 0 if false,
#+ opposite of normal Bash convention.

echo "Logical Operators"
echo

x=24
y=25
b=`expr $x = $y`         # Test equality.
echo "b = $b"            # 0  ( $x -ne $y )
echo

a=3
b=`expr $a \> 10`
echo 'b=`expr $a \> 10`, therefore...'
echo "If a > 10, b = 0 (false)"
echo "b = $b"            # 0  ( 3 ! -gt 10 )
echo

b=`expr $a \< 10`
echo "If a < 10, b = 1 (true)"
echo "b = $b"            # 1  ( 3 -lt 10 )
echo
# Note escaping of operators.

b=`expr $a \<= 3`
echo "If a <= 3, b = 1 (true)"
echo "b = $b"            # 1  ( 3 -le 3 )
# There is also a "\>=" operator (greater than or equal to).

echo
echo

# String Operators
# ------ ---------

echo "String Operators"
echo

a=1234zipper43231
echo "The string being operated upon is \"$a\"."

# length: length of string
b=`expr length $a`
echo "Length of \"$a\" is $b."

# index: position of first character in substring
#        that matches a character in string
b=`expr index $a 23`
echo "Numerical position of first \"2\" in \"$a\" is \"$b\"."

# substr: extract substring, starting position & length specified
b=`expr substr $a 2 6`
echo "Substring of \"$a\", starting at position 2,\
and 6 chars long is \"$b\"."

#  The default behavior of the 'match' operations is to
#+ search for the specified match at the BEGINNING of the string.
#
#       Using Regular Expressions ...
b=`expr match "$a" '[0-9]*'`               #  Numerical count.
echo Number of digits at the beginning of \"$a\" is $b.
b=`expr match "$a" '\([0-9]*\)'`           #  Note that escaped parentheses
#                   ==      ==             #+ trigger substring match.
echo "The digits at the beginning of \"$a\" are \"$b\"."

echo

exit 0


text:
.placeholder{
    border-radius:5px;
}

.top-bar,.top-bar-section li:not(.has-form) a:not(.button){
    background:#fff !important;
    border:0px !important;
    box-sizing: border-box !important;
    color: #999 !important;
    padding: 0.1875rem 1rem !important;
    font-size:0.875rem !important;
}

.th {
    border: 4px solid #ffffff;
    box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.2);
    display: inline-block;
    line-height: 0;
    max-width: 100%;
    transition: all 200ms ease-out 0s;
    margin-bottom:50px;
    margin:10px;
}

.gallery-item-checkbox{
    float:left;
    position:absolute;
    -ms-transform: scale(1.2); /* IE */
    -moz-transform: scale(1.2); /* FF */
    -webkit-transform: scale(1.2); /* Safari and Chrome */
    -o-transform: scale(1.2); /* Opera */
    padding: 10px;
}

input.search-bar {
    border-bottom-left-radius: 3px;
    border-top-left-radius: 3px;
    font-size: 12px;
    height: 38px;
}
input.search-bar:focus {
    box-shadow: none;
}
input.search-bar {
    border-radius: 0.1875em 0 0 0.1875em;
}
.button.search, button.search {
    -moz-border-bottom-colors: none;
    -moz-border-left-colors: none;
    -moz-border-right-colors: none;
    -moz-border-top-colors: none;
    background-color: #ededed;
    border-bottom-right-radius: 3px;
    border-color: #e6e6e6 #e6e6e6 #e6e6e6 -moz-use-text-color;
    border-image: none;
    border-style: solid solid solid none;
    border-top-right-radius: 3px;
    border-width: 1px 1px 1px medium;
    font-size: 18px;
    height: 38px;
    padding-top: 0;
}
input[type="text"], input[type="password"], input[type="date"], input[type="datetime"], input[type="datetime-local"], input[type="month"], input[type="week"], input[type="email"], input[type="number"], input[type="search"], input[type="tel"], input[type="time"], input[type="url"], textarea {
    border: 1px solid #cccccc;
    border-radius: 0;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1) inset;
    box-sizing: border-box;
    color: rgba(0, 0, 0, 0.75);
    display: block;
    font-family: inherit;
    font-size: 0.875em;
    margin: 0 0 1em;
    padding: 0.5em;
    transition: box-shadow 0.45s ease 0s, border-color 0.45s ease-in-out 0s;
    width: 100%;
}

textarea, input {
    background-color: #f7f7f7 !important;
    border: 1px solid #ebebeb !important;
    box-shadow: 0 0 0 0 transparent !important;
}


text:
import {Component} from '@angular/core';

import {AboutPage} from '../about/about';
import {HomePage} from '../home/home';
import {TeamsPage} from "../teams/teams";

@Component({
    templateUrl: 'tabs.html'
})
export class TabsPage {

    tab1Root = HomePage;
    tab2Root = TeamsPage;
    tab3Root = AboutPage;

    constructor() {

    }
}


text: Echo.Variables.raceCondition.second = true;


text:
using System;
using System.Linq;

namespace Microsoft.WindowsAzure.Management.Sql.Models
{
    /// <summary>
    /// Represents the parameters that are needed to retrieve the status of an
    /// Azure SQL Database import or export operation.
    /// </summary>
    public partial class DacGetStatusParameters
    {
        private string _password;

        /// <summary>
        /// Required. Gets or sets the password for the Azure SQL Database
        /// server where the import or export operation is occurring.
        /// </summary>
        public string Password
        {
            get { return this._password; }
            set { this._password = value; }
        }

        private string _requestId;

        /// <summary>
        /// Required. Gets or sets the Id for the import or export operation of
        /// which to query the status.
        /// </summary>
        public string RequestId
        {
            get { return this._requestId; }
            set { this._requestId = value; }
        }

        private string _serverName;

        /// <summary>
        /// Required. Gets or sets the fully qualified name of the Azure SQL
        /// Database server where the import or export operation is occurring.
        /// </summary>
        public string ServerName
        {
            get { return this._serverName; }
            set { this._serverName = value; }
        }

        private string _userName;

        /// <summary>
        /// Required. Gets or sets the username for the Azure SQL Database
        /// server where the import or export operation is occurring.
        /// </summary>
        public string UserName
        {
            get { return this._userName; }
            set { this._userName = value; }
        }

        /// <summary>
        /// Initializes a new instance of the DacGetStatusParameters class.
        /// </summary>
        public DacGetStatusParameters()
        {
        }
    }
}


text:
{% extends "manage/manage_base.html" %}
{% load i18n %}

{% block title %}{% trans "Add shipping method" %}{% endblock %}
{% block section %}add-shipping-method{% endblock %}
{% block help_link %}user/management/shop/shipping_methods.html{% endblock %}
{% block left-slot-wrapper %}{% endblock %}

{% block content %}

{% trans 'Add Shipping Method' %}

{% trans 'Data' %}

            <form method="post">
                {% csrf_token %}
                {% include "manage/lfs_form.html" %}

                    <input type="submit"
                           class="button"
                           value="{% trans 'Add shipping method' %}" />
                    <input type="button"
                           class="button"
                           onclick="window.location='{{ came_from }}'"
                           value="{% trans 'Cancel' %}" />

            </form>

{% endblock %}


text:
module Fog
  module Softlayer
    class Compute

      class Mock
        def get_bare_metal_server(identifier)
          response = Excon::Response.new
          response.body = @bare_metal_servers.map {|vm| vm if vm['id'] == identifier.to_s }.compact.first || {}
          response.status = response.body.empty? ? 404 : 200
          if response.status == 404
            response.body = {
                "error"=>"Unable to find object with id of '#{identifier}'.",
                "code"=>"SoftLayer_Exception_ObjectNotFound"
            }
          end
          response
        end

      end

      class Real
        def get_bare_metal_server(identifier)
          request(:hardware_server, identifier, :expected => [200, 404], :query => 'objectMask=mask[datacenter,tagReferences,memory,provisionDate,processorCoreAmount,hardDrives,datacenter,hourlyBillingFlag,operatingSystem.softwareLicense.softwareDescription.referenceCode,sshKeys.id,privateNetworkOnlyFlag,userData,frontendNetworkComponents,backendNetworkComponents]')
        end
      end
    end
  end
end


text:
'use strict';

function sanitize(str) {

    if(!str) return '';

    return String(str)
        .replace(/\./g, '_')
        .replace(/\s/g, '')
        .toLowerCase();
}

/**
 * formats capability object into sanitized string for e.g.filenames
 * @param {Object} caps  Selenium capabilities
 */
exports.caps = function(caps) {
    var result;

    /**
     * mobile caps
     */
    if(caps.deviceName) {
        result = [sanitize(caps.deviceName), sanitize(caps.platformName), sanitize(caps.platformVersion)];
    } else {
        result = [sanitize(caps.browserName), sanitize(caps.version), sanitize(caps.platform)];
    }

    result = result.filter(function(n){
        return n !== undefined && n !== '';
    });
    return result.join('.');
};

/**
 * formats arguments into string
 * @param  {Array} args arguments object
 */
exports.args = function(args) {
    return args.map(function(arg) {
        if(typeof arg === 'function' || (typeof arg === 'string' && arg.indexOf('return (function') === 0)) {
            return '<Function>';
        } else if(typeof arg === 'string') {
            return '"' + arg + '"';
        } else if(Array.isArray(arg)) {
            return arg.join(', ');
        }
        return  arg;
    }).join(', ');
};