text:
<?php

namespace Ekyna\Bundle\ProductBundle\Service\Commerce;

use Ekyna\Bundle\ProductBundle\Exception\UnexpectedTypeException;
use Ekyna\Bundle\ProductBundle\Model;
use Ekyna\Bundle\ProductBundle\Model\ProductInterface;
use Ekyna\Bundle\ProductBundle\Model\ProductTypes;
use Ekyna\Component\Commerce\Common\Model\SaleItemInterface;
use Ekyna\Component\Commerce\Exception;

/**
 * Class ItemBuilder
 * @package Ekyna\Bundle\ProductBundle\Service\Commerce
 * @author  Etienne Dauvergne <contact@ekyna.com>
 */
class ItemBuilder
{
    public const VARIANT_ID       = 'variant_id';
    public const BUNDLE_SLOT_ID   = 'bundle_slot_id';
    public const BUNDLE_CHOICE_ID = 'bundle_choice_id';
    public const OPTION_GROUP_ID  = 'option_group_id';
    public const OPTION_ID        = 'option_id';
    public const COMPONENT_ID     = 'component_id';

    /**
     * @var ProductProvider
     */
    protected $provider;

    /**
     * @var ProductFilterInterface
     */
    protected $filter;

    /**
     * Constructor.
     *
     * @param ProductProvider        $provider
     * @param ProductFilterInterface $filter
     */
    public function __construct(ProductProvider $provider, ProductFilterInterface $filter)
    {
        $this->provider = $provider;
        $this->filter = $filter;
    }

    /**
     * Returns the product provider.
     *
     * @return ProductProvider
     */
    public function getProvider(): ProductProvider
    {
        return $this->provider;
    }

    /**
     * Returns the product filter.
     *
     * @return ProductFilterInterface
     */
    public function getFilter(): ProductFilterInterface
    {
        return $this->filter;
    }

    /**
     * Builds the sale item (subject must be assigned).
     *
     * @param SaleItemInterface $item
     */
    public function build(SaleItemInterface $item)
    {
        $product = $this->resolve($item);

        $this->buildFromProduct($item, $product, []);
    }

    /**
     * Builds the sale item from the given product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     * @param array|null        $exclude The option group ids to exclude
     *
     * @throws Exception\InvalidArgumentException If product type is not supported
     */
    protected function buildFromProduct(SaleItemInterface $item, ProductInterface $product, array $exclude = null)
    {
        switch ($product->getType()) {
            case ProductTypes::TYPE_SIMPLE:
                $this->buildFromSimple($item, $product);
                break;
            case ProductTypes::TYPE_VARIANT:
                $this->buildFromVariant($item, $product);
                break;
            case ProductTypes::TYPE_VARIABLE:
                $this->buildFromVariable($item, $product);
                break;
            case ProductTypes::TYPE_BUNDLE:
                $this->buildFromBundle($item, $product, $exclude);
                break;
            case ProductTypes::TYPE_CONFIGURABLE:
                $this->buildFromConfigurable($item, $product, $exclude);
                break;
            default:
                throw new Exception\InvalidArgumentException('Unexpected product type');
        }

        if (!is_null($exclude)) {
            $this->buildOptions($item, $exclude);
        }

        $this->buildComponents($item);
    }

    /**
     * Builds the sale item from the simple product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     */
    protected function buildFromSimple(SaleItemInterface $item, ProductInterface $product)
    {
        ProductTypes::assertChildType($product);

        $this->provider->assign($item, $product);

        $item
            ->setDesignation((string)$product)
            ->setReference($product->getReference())
            ->setNetPrice($product->getNetPrice())
            ->setWeight($product->getPackageWeight())
            ->setTaxGroup($product->getTaxGroup())
            ->setCompound(false)
            ->setConfigurable(false)
            ->setPrivate(!$product->isVisible());

        $this->cleanUpBundleSlots($item, []);
    }

    /**
     * Builds the sale item from the given variable product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     *
     * @throws Exception\LogicException If the product has no variant
     */
    protected function buildFromVariable(SaleItemInterface $item, ProductInterface $product)
    {
        ProductTypes::assertVariable($product);

        $variant = null;
        $variants = $this->filter->getVariants($product);

        if (empty($variants)) {
            throw new Exception\LogicException("Variable product must have at least one variant.");
        }

        if (0 < ($variantId = intval($item->getData(self::VARIANT_ID)))) {
            foreach ($variants as $v) {
                if ($variantId == $v->getId()) {
                    $variant = $v;
                    break;
                }
            }
        }

        if (null === $variant) {
            $variant = reset($variants);
        }

        $this->buildFromVariant($item, $variant);
        // TODO Variable product should always be public (not private) as it is a user choice.
    }

    /**
     * Builds the sale item from the given variant product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $variant
     */
    public function buildFromVariant(SaleItemInterface $item, ProductInterface $variant)
    {
        ProductTypes::assertVariant($variant);

        $this->buildFromSimple($item, $variant);

        $item->setData(self::VARIANT_ID, $variant->getId());
    }

    /**
     * Builds the sale item from the given bundle product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     * @param array|null        $exclude The option groups ids to exclude
     */
    protected function buildFromBundle(SaleItemInterface $item, ProductInterface $product, array $exclude = null): void
    {
        ProductTypes::assertBundle($product);

        $this->provider->assign($item, $product);

        // Bundle root item
        $item
            ->setDesignation((string)$product)
            ->setReference($product->getReference())
            ->setNetPrice(.0)
            ->setTaxGroup($product->getTaxGroup())
            ->setCompound(true)
            ->setConfigurable(false)
            ->setPrivate(!$product->isVisible());

        // (Do not filter bundle slots)
        /** @var \Ekyna\Bundle\ProductBundle\Model\BundleSlotInterface[] $bundlesSlots */
        $bundlesSlots = $product->getBundleSlots()->toArray();

        // Every slot must match a single item
        $bundleSlotIds = [];
        foreach ($bundlesSlots as $bundleSlot) {
            $choices = $this->filter->getSlotChoices($bundleSlot);
            if (empty($choices)) {
                continue;
            }

            $bundleChoice = current($choices);

            // Find matching item
            foreach ($item->getChildren() as $childItem) {
                $bundleSlotId = intval($childItem->getData(self::BUNDLE_SLOT_ID));
                if ($bundleSlotId != $bundleSlot->getId()) {
                    continue;
                }

                // Remove bundle slot duplicates
                if (in_array($bundleSlotId, $bundleSlotIds)) {
                    $item->removeChild($childItem);
                    continue;
                }
                $bundleSlotIds[] = $bundleSlotId;

                // Build the item from the bundle choice's product
                $this->buildFromBundleChoice($childItem, $bundleChoice, $exclude);

                continue 2;
            }

            $bundleSlotIds[] = $bundleSlot->getId();

            // Not found : Create and build the item from the bundle choice's product
            $this->buildFromBundleChoice($item->createChild(), $bundleChoice, $exclude);
        }

        $this->cleanUpBundleSlots($item, $bundleSlotIds);
    }

    /**
     * Builds the sale item from the given configurable product.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     * @param array|null        $exclude
     */
    protected function buildFromConfigurable(
        SaleItemInterface $item,
        ProductInterface $product,
        array $exclude = null
    ): void {
        ProductTypes::assertConfigurable($product);

        $this->provider->assign($item, $product);

        // Configurable root item
        $item
            ->setDesignation((string)$product)
            ->setReference($product->getReference())
            ->setNetPrice(.0)
            ->setTaxGroup($product->getTaxGroup())
            ->setCompound(true)
            ->setConfigurable(true)
            ->setPrivate(false);

        // Filter bundle slots
        $bundlesSlots = $this->filter->getBundleSlots($product);

        // Every slot must match a single item
        $bundleSlotIds = [];
        foreach ($bundlesSlots as $bundleSlot) {
            $choices = $this->filter->getSlotChoices($bundleSlot);
            if (empty($choices)) {
                continue;
            }

            $bundleChoice = current($choices);

            // Find matching item
            foreach ($item->getChildren() as $childItem) {
                $bundleSlotId = intval($childItem->getData(self::BUNDLE_SLOT_ID));
                if ($bundleSlotId != $bundleSlot->getId()) {
                    continue;
                }

                // Remove bundle slot duplicates
                if (in_array($bundleSlotId, $bundleSlotIds)) {
                    $item->removeChild($childItem);
                    continue;
                }
                $bundleSlotIds[] = $bundleSlotId;

                // Resolve choice
                if (0 < $bundleChoiceId = intval($childItem->getData(self::BUNDLE_CHOICE_ID))) {
                    foreach ($choices as $choice) {
                        if ($bundleChoiceId === $choice->getId()) {
                            $bundleChoice = $choice;
                            break;
                        }
                    }

                    // Build the item from the bundle choice's product
                    $this->buildFromBundleChoice($childItem, $bundleChoice, $exclude);
                } elseif (!$bundleSlot->isRequired()) {
                    // No choice and not required : remove child item
                    $item->removeChild($childItem);
                }

                continue 2;
            }

            $bundleSlotIds[] = $bundleSlot->getId();

            // Not found
            if ($bundleSlot->isRequired()) {
                // Create and build the item from the bundle choice's product
                $this->buildFromBundleChoice($item->createChild(), $bundleChoice, $exclude);
            }
        }

        $this->cleanUpBundleSlots($item, $bundleSlotIds);

        // Direct children must be public as they are user choices.
        foreach ($item->getChildren() as $child) {
            if ($child->hasData(self::BUNDLE_SLOT_ID)) {
                $child->setPrivate(false);
            }
        }
    }

    /**
     * Builds the sale item from the given bundle choice.
     *
     * @param SaleItemInterface           $item    The sale item
     * @param Model\BundleChoiceInterface $choice  The bundle choice
     * @param array|null                  $exclude The option group ids to exclude
     */
    protected function buildFromBundleChoice(
        SaleItemInterface $item,
        Model\BundleChoiceInterface $choice,
        array $exclude = null
    ): void {
        if (!is_null($exclude)) {
            $exclude = array_unique(array_merge($exclude, $choice->getExcludedOptionGroups()));
        }

        $this->buildFromProduct($item, $choice->getProduct(), $exclude);

        // TODO Use packaging format

        // Normalize quantity
        if ($item->getQuantity() < $choice->getMinQuantity()) {
            $item->setQuantity($choice->getMinQuantity());
        } elseif ($item->getQuantity() > $choice->getMaxQuantity()) {
            $item->setQuantity($choice->getMaxQuantity());
        }

        if ($choice->isHidden()) {
            $item->setPrivate(true);
        }

        // Override item net price (from product) with choice's net price if set
        if (null !== $choice->getNetPrice()) {
            $item->setNetPrice($choice->getNetPrice());
        }

        $item
            ->setData(self::BUNDLE_SLOT_ID, $choice->getSlot()->getId())
            ->setData(self::BUNDLE_CHOICE_ID, $choice->getId())
            ->setImmutable(true);
    }

    /**
     * Builds the item's options.
     *
     * @param SaleItemInterface $item
     * @param array             $exclude The option groups ids to exclude
     *
     * @throws Exception\LogicException If an option group is required but no option is selected
     */
    protected function buildOptions(SaleItemInterface $item, array $exclude): void
    {
        $optionGroups = $this->getOptionGroups($item, $exclude);

        if (empty($optionGroups)) {
            $this->cleanUpOptionGroups($item);

            return;
        }

        // Configurable only if root item
        if ($root = $item->getParent()) {
            $root->setConfigurable(true);
        } else {
            $item->setConfigurable(true);
        }

        $optionGroupIds = [];
        foreach ($optionGroups as $optionGroup) {
            // Skip if group has no options
            $options = $this->filter->getGroupOptions($optionGroup);
            if (empty($options)) {
                continue;
            }

            // Find option group matching item
            if ($item->hasChildren()) {
                foreach ($item->getChildren() as $child) {
                    // Check option group id
                    $optionGroupId = intval($child->getData(self::OPTION_GROUP_ID));
                    if ($optionGroupId != $optionGroup->getId()) {
                        continue;
                    }

                    // Remove option group duplicates
                    if (in_array($optionGroupId, $optionGroupIds)) {
                        $item->removeChild($child);
                        continue;
                    }
                    $optionGroupIds[] = $optionGroupId;

                    $child
                        ->setQuantity(1)
                        ->setPosition($optionGroup->getPosition());

                    // Check option choice
                    $found = false;
                    if (0 < $optionId = intval($child->getData(self::OPTION_ID))) {
                        foreach ($options as $option) {
                            if ($optionId === $option->getId()) {
                                $found = true;

                                $this->buildFromOption($child, $option, count($options));

                                break;
                            }
                        }
                        // Not Found => unset choice
                        if (!$found) {
                            $child->unsetData(self::OPTION_ID);
                        }
                    }

                    if (!$found) {
                        if ($optionGroup->isRequired()) {
                            //$this->buildFromOption($child, reset($options));
                            throw new Exception\LogicException("Option group is required.");
                        } else {
                            $item->removeChild($child);
                        }
                    }

                    // Next option group
                    continue 2;
                }
            }
        }

        $this->cleanUpOptionGroups($item, $optionGroupIds);
    }

    /**
     * Builds the item from the option.
     *
     * @param SaleItemInterface     $item
     * @param Model\OptionInterface $option
     * @param int                   $choiceCount
     */
    public function buildFromOption(SaleItemInterface $item, Model\OptionInterface $option, int $choiceCount): void
    {
        // Reset net price
        $item->setNetPrice(0);

        if (null !== $product = $option->getProduct()) {
            $this->buildFromProduct($item, $product, []); // TODO Cascade / exclude
            $item->unsetData(self::VARIANT_ID); // Not a variant choice
        } else {
            $designation = sprintf(
                '%s : %s',
                $option->getGroup()->getName(),
                $option->getDesignation()
            );

            $item
                ->setDesignation($designation)
                ->setReference($option->getReference())
                ->setWeight($option->getWeight())
                ->setTaxGroup($option->getTaxGroup());
        }

        // Override item net price (from product) with option's net price if set
        if (null !== $option->getNetPrice()) {
            $item->setNetPrice($option->getNetPrice());
        }

        $item
            ->setData(self::OPTION_GROUP_ID, $option->getGroup()->getId())
            ->setData(self::OPTION_ID, $option->getId())
            ->setQuantity(1)
            ->setImmutable(true)
            ->setConfigurable(false);

        // If not private and group is required and group has a single option choice
        if (!$item->isPrivate() && $option->getGroup()->isRequired() && (1 === $choiceCount)) {
            $item->setPrivate(true);
        }
    }

    /**
     * Builds the item from component.
     *
     * @param SaleItemInterface        $item
     * @param Model\ComponentInterface $component
     */
    protected function buildFromComponent(SaleItemInterface $item, Model\ComponentInterface $component): void
    {
        $this->buildFromProduct($item, $component->getChild());

        $item
            ->setQuantity($component->getQuantity())
            ->setPrivate(true)
            ->setImmutable(true)
            ->setConfigurable(false);

        if (!is_null($price = $component->getNetPrice())) {
            $item->setNetPrice($price);
        }
    }

    /**
     * Builds the item's components.
     *
     * @param SaleItemInterface $item
     *
     * @throws Exception\SubjectException
     */
    protected function buildComponents(SaleItemInterface $item): void
    {
        $product = $this->resolve($item);

        // TODO (?) $components = $this->filter->getComponents($product);
        $components = $product->getComponents()->toArray();
        if ($product->getType() === ProductTypes::TYPE_VARIANT) {
            foreach ($product->getParent()->getComponents() as $component) {
                $components[] = $component;
            }
        }

        if (empty($components)) {
            $this->cleanUpComponents($item);

            return;
        }

        $componentIds = [];
        foreach ($components as $component) {
            // Find option group matching item
            foreach ($item->getChildren() as $child) {
                $componentId = intval($child->getData(self::COMPONENT_ID));

                // Component id match
                if ($componentId != $component->getId()) {
                    continue; // Next child
                }

                if (in_array($componentId, $componentIds)) {
                    // Remove component duplicate
                    $item->removeChild($child);
                    continue; // Next child
                }

                $componentIds[] = $componentId;

                $this->buildFromComponent($child, $component);

                continue 2; // Next component
            }

            // Not found -> create it
            $child = $item->createChild();

            $this->buildFromComponent($child, $component);
        }

        $this->cleanUpComponents($item, $componentIds);
    }

    /**
     * Removes irrelevant bundle slot child items.
     *
     * @param SaleItemInterface $item
     * @param array             $bundleSlotIds The bundle slots ids to keep
     */
    private function cleanUpBundleSlots(SaleItemInterface $item, array $bundleSlotIds = []): void
    {
        foreach ($item->getChildren() as $childItem) {
            if (0 < $bundleSlotId = intval($childItem->getData(self::BUNDLE_SLOT_ID))) {
                if (!in_array($bundleSlotId, $bundleSlotIds)) {
                    $item->removeChild($childItem);
                }
            }
        }
    }

    /**
     * Removes irrelevant option groups child item.
     *
     * @param SaleItemInterface $item
     * @param array             $optionGroupIds The option groups ids to keep
     */
    private function cleanUpOptionGroups(SaleItemInterface $item, array $optionGroupIds = []): void
    {
        foreach ($item->getChildren() as $childItem) {
            if (0 < $optionGroupId = intval($childItem->getData(self::OPTION_GROUP_ID))) {
                if (!in_array($optionGroupId, $optionGroupIds)) {
                    $item->removeChild($childItem);
                }
            }
        }
    }

    /**
     * Removes irrelevant component child item.
     *
     * @param SaleItemInterface $item
     * @param array             $componentIds The components ids to keep
     */
    private function cleanUpComponents(SaleItemInterface $item, array $componentIds = []): void
    {
        foreach ($item->getChildren() as $childItem) {
            if (0 < $componentId = intval($childItem->getData(self::COMPONENT_ID))) {
                if (!in_array($componentId, $componentIds)) {
                    $item->removeChild($childItem);
                }
            }
        }
    }

    /**
     * Initializes the sale item (subject must be assigned).
     *
     * @param SaleItemInterface $item    The sale item
     * @param array             $exclude The option group ids to exclude
     */
    public function initialize(SaleItemInterface $item, array $exclude = []): void
    {
        $product = $this->resolve($item);

        // Clear identifiers vars
        $item->unsetData(self::VARIANT_ID);
        $item->unsetData(self::BUNDLE_SLOT_ID);
        $item->unsetData(self::BUNDLE_CHOICE_ID);
        $item->unsetData(self::OPTION_GROUP_ID);
        $item->unsetData(self::OPTION_ID);
        $item->unsetData(self::COMPONENT_ID);

        switch ($product->getType()) {
            case ProductTypes::TYPE_SIMPLE:
                $this->initializeFromSimple($item, $product);
                break;
            case ProductTypes::TYPE_VARIANT:
                $this->initializeFromVariant($item, $product);
                break;
            case ProductTypes::TYPE_VARIABLE:
                $this->initializeFromVariable($item, $product);
                break;
            case ProductTypes::TYPE_BUNDLE:
            case ProductTypes::TYPE_CONFIGURABLE:
                $this->initializeFromBundle($item, $product, $exclude);
                break;
            default:
                throw new Exception\InvalidArgumentException('Unexpected product type');
        }

        $this->initializeOptions($item, $exclude);
    }

    /**
     * Initializes the sale item from the given variable item.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     */
    protected function initializeFromSimple(SaleItemInterface $item, ProductInterface $product): void
    {
        ProductTypes::assertChildType($product);

        $item
            ->setCompound(false)
            ->setConfigurable(false)
            ->setPrivate(!$product->isVisible());
    }

    /**
     * Initializes the sale item from the given variable item.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     */
    protected function initializeFromVariable(SaleItemInterface $item, ProductInterface $product): void
    {
        ProductTypes::assertVariable($product);

        $this->initializeFromVariant($item, $this->fallbackVariableToVariant($product));
    }

    /**
     * Initializes the sale item from the given variant item.
     *
     * @param SaleItemInterface $item
     * @param ProductInterface  $product
     */
    protected function initializeFromVariant(SaleItemInterface $item, ProductInterface $product): void
    {
        ProductTypes::assertVariant($product);

        $item->setData(self::VARIANT_ID, $product->getId());

        $this->initializeFromSimple($item, $product);
    }

    /**
     * Initializes the item from the given bundle (or configurable) product.
     *
     * @param SaleItemInterface $item    The sale item
     * @param ProductInterface  $product The product
     * @param array             $exclude The option group ids to exclude
     */
    protected function initializeFromBundle(SaleItemInterface $item, ProductInterface $product, array $exclude): void
    {
        ProductTypes::assertBundled($product);

        $configurable = $product->getType() === ProductTypes::TYPE_CONFIGURABLE;

        $item
            ->setCompound(true)
            ->setConfigurable($configurable)
            ->setPrivate(!$product->isVisible());

        // Filter bundle slots if configurable
        $bundlesSlots = $configurable
            ? $this->filter->getBundleSlots($product)
            : $product->getBundleSlots()->toArray();

        // For each bundle/configurable slots
        $bundleSlotIds = [];
        foreach ($bundlesSlots as $bundleSlot) {
            // Filter bundle slot choices if configurable
            $bundlesChoices = $configurable
                ? $this->filter->getSlotChoices($bundleSlot)
                : $bundleSlot->getChoices()->toArray();

            /** @var \Ekyna\Bundle\ProductBundle\Model\BundleChoiceInterface $defaultChoice */
            $defaultChoice = current($bundlesChoices);
            $choiceProductIds = [];

            // Valid and default slot product(s)
            foreach ($bundlesChoices as $choice) {
                $choiceProduct = $choice->getProduct();
                if ($choiceProduct->getType() === ProductTypes::TYPE_VARIABLE) {
                    // Variable product can't be assigned, so use variants
                    foreach ($choiceProduct->getVariants() as $variant) {
                        $choiceProductIds[] = $variant->getId();
                    }
                } else {
                    $choiceProductIds[] = $choiceProduct->getId();
                }
            }

            // Find slot matching item
            if ($item->hasChildren()) {
                foreach ($item->getChildren() as $child) {
                    // Check bundle slot id
                    $bundleSlotId = intval($child->getData(self::BUNDLE_SLOT_ID));
                    if ($bundleSlotId != $bundleSlot->getId()) {
                        continue;
                    }

                    // Remove bundle slot duplicates
                    if (in_array($bundleSlotId, $bundleSlotIds)) {
                        $item->removeChild($child);
                        continue;
                    }
                    $bundleSlotIds[] = $bundleSlotId;

                    // Get/resolve item subject
                    $childProduct = $this->resolve($child);

                    // If invalid choice
                    if (!in_array($childProduct->getId(), $choiceProductIds)) {
                        $child->getSubjectIdentity()->clear();

                        // Initialize default choice
                        $this->initializeFromBundleChoice($child, $defaultChoice, $exclude);
                    }

                    // Next bundle slot
                    continue 2;
                }
            }

            // Item not found : create it
            $child = $item->createChild();

            if ($bundleSlot->isRequired()) {
                $this->initializeFromBundleChoice($child, $defaultChoice, $exclude);
            } else {
                $child->setData(self::BUNDLE_SLOT_ID, $bundleSlot->getId());
            }
        }
    }

    /**
     * Initializes the sale item from the given bundle choice.
     *
     * @param SaleItemInterface           $item    The sale item
     * @param Model\BundleChoiceInterface $choice  The bundle choice
     * @param array                       $exclude The option group ids to exclude
     */
    public function initializeFromBundleChoice(
        SaleItemInterface $item,
        Model\BundleChoiceInterface $choice,
        array $exclude = []
    ): void {
        $product = $this->fallbackVariableToVariant($choice->getProduct());
        $this->provider->assign($item, $product);

        $this->initialize($item, array_unique(array_merge($exclude, $choice->getExcludedOptionGroups())));

        // Override item net price (from product) with choice's net price if set
        if (null !== $choice->getNetPrice()) {
            $item->setNetPrice($choice->getNetPrice());
        }

        $item
            ->setQuantity($choice->getMinQuantity())
            ->setPosition($choice->getSlot()->getPosition())
            ->setData(self::BUNDLE_SLOT_ID, $choice->getSlot()->getId())
            ->setData(self::BUNDLE_CHOICE_ID, $choice->getId());
    }

    /**
     * Initializes the sale item's children regarding to the product's option groups.
     *
     * @param SaleItemInterface $item    The sale item
     * @param array             $exclude The option groups ids to exclude
     */
    protected function initializeOptions(SaleItemInterface $item, array $exclude): void
    {
        $optionGroups = $this->getOptionGroups($item, $exclude);

        $optionGroupIds = [];

        foreach ($optionGroups as $optionGroup) {
            // Skip if group has no options
            $options = $this->filter->getGroupOptions($optionGroup);
            if (empty($options)) {
                continue;
            }

            // Find option group matching item
            if ($item->hasChildren()) {
                foreach ($item->getChildren() as $child) {
                    // Skip if item has no option group data
                    if (!$child->hasData(self::OPTION_GROUP_ID)) {
                        continue;
                    }

                    // Check option group data
                    $optionGroupId = intval($child->getData(self::OPTION_GROUP_ID));
                    if ($optionGroupId != $optionGroup->getId()) {
                        continue;
                    }

                    // Remove option group duplicates
                    if (in_array($optionGroupId, $optionGroupIds)) {
                        $item->removeChild($child);
                        continue;
                    }
                    $optionGroupIds[] = $optionGroupId;

                    $child
                        ->setQuantity(1)
                        ->setPosition($optionGroup->getPosition());

                    // Check option choice
                    $found = false;
                    if (0 < $optionId = intval($child->getData(self::OPTION_ID))) {
                        foreach ($options as $option) {
                            if ($optionId === $option->getId()) {
                                $found = true;
                                break;
                            }
                        }
                    }

                    // Not Found
                    if (!$found) {
                        $child->unsetData(self::OPTION_ID);

                        // Default choice if required
                        if ($optionGroup->isRequired()) {
                            if ($option = current($options)) {
                                $child->setData(self::OPTION_ID, $option->getId());
                            }
                        }
                    }

                    // Next option group
                    continue 2;
                }
            }

            // Item not found : create it
            $child = $item->createChild();

            $this->initializeFromOptionGroup($child, $optionGroup);
        }
    }

    /**
     * Initializes the sale item from the given option group.
     *
     * @param SaleItemInterface          $item
     * @param Model\OptionGroupInterface $optionGroup
     */
    public function initializeFromOptionGroup(SaleItemInterface $item, Model\OptionGroupInterface $optionGroup): void
    {
        $item
            ->setData(self::OPTION_GROUP_ID, $optionGroup->getId())
            ->setQuantity(1)
            ->setPosition($optionGroup->getPosition());

        // Default choice if required
        if ($optionGroup->isRequired()) {
            // Skip if group has no options
            $options = $this->filter->getGroupOptions($optionGroup);
            if (empty($options)) {
                return;
            }

            if ($option = current($options)) {
                $item->setData(self::OPTION_ID, $option->getId());
            }
        }
    }

    /**
     * Returns the available bundle slots for the given sale item.
     *
     * @param SaleItemInterface $item
     *
     * @return Model\BundleSlotInterface[]
     */
    public function getBundleSlots(SaleItemInterface $item): array
    {
        $product = $this->resolve($item);

        return $this->filter->getBundleSlots($product);
    }

    /**
     * Returns the available option groups for the given sale item (merges variable and variant groups).
     *
     * @param SaleItemInterface $item
     * @param array             $exclude = The option group ids to exclude
     *
     * @return Model\OptionGroupInterface[]
     */
    public function getOptionGroups(SaleItemInterface $item, array $exclude = []): array
    {
        $product = $this->resolve($item);

        $groups = $this->filter->getOptionGroups($product, $exclude);

        // Filter product option groups
        if ($product->getType() === ProductTypes::TYPE_VARIANT) {
            $variable = $product->getParent();

            // Filter variant option groups
            $groups = array_merge($this->filter->getOptionGroups($variable, $exclude), $groups);
        }

        return $groups;
    }

    /**
     * Returns the relevant variant if the given product is a variable one.
     *
     * @param ProductInterface       $product
     * @param SaleItemInterface|null $item
     *
     * @return ProductInterface If product has no variant
     */
    private function fallbackVariableToVariant(
        ProductInterface $product,
        SaleItemInterface $item = null
    ): ProductInterface {
        if ($product->getType() === ProductTypes::TYPE_VARIABLE) {
            $variants = $this->filter->getVariants($product);

            if (empty($variants)) {
                throw new Exception\InvalidArgumentException("Variable product must have at least one variant.");
            }

            if ($item && 0 < ($variantId = intval($item->getData(self::VARIANT_ID)))) {
                foreach ($variants as $v) {
                    if ($variantId == $v->getId()) {
                        return $v;
                    }
                }
            }

            return reset($variants);
        }

        return $product;
    }

    /**
     * Resolves the sale item's product.
     *
     * @param SaleItemInterface $item
     *
     * @return ProductInterface
     * @throws Exception\SubjectException
     */
    private function resolve(SaleItemInterface $item): ProductInterface
    {
        $product = $this->provider->resolve($item);

        if (!$product instanceof ProductInterface) {
            throw new UnexpectedTypeException($product, ProductInterface::class);
        }

        return $product;
    }
}


text:
ALTER TABLE jetpack_module CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE jetpack_attachment CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE jetpack_package CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;
ALTER TABLE jetpack_packagerevision CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;

redpajama_set_name: RedPajamaGithub


text:
<?php

namespace OrganisationApiTest\Factory\Service;

use Doctrine\ORM\EntityManager;
use DvsaCommonTest\TestUtils\XMock;
use DvsaEntities\Repository\OrganisationRepository;
use OrganisationApi\Factory\Service\OrganisationServiceFactory;
use OrganisationApi\Service\OrganisationService;

use Zend\ServiceManager\ServiceManager;

class OrganisationServiceFactoryTest extends \PHPUnit\Framework\TestCase
{
    public function testFactory()
    {
        $serviceManager = new ServiceManager();
        $entityManager = XMock::of(EntityManager::class);
        $organisation = XMock::of(OrganisationRepository::class);

        $serviceManager->setService(EntityManager::class, $entityManager);

        $entityManager->expects($this->at(0))->method('getRepository')->willReturn($organisation);

        // Create the factory
        $factory = new OrganisationServiceFactory();

        $this->assertInstanceOf(OrganisationService::class, $factory->createService($serviceManager));
    }
}


text:
'use strict';
var util = require('util'),
  ScriptBase = require('../script-base.js');

var Generator = module.exports = function Generator() {
  ScriptBase.apply(this, arguments);

  // if the controller name is suffixed with ctrl, remove the suffix
  // if the controller name is just "ctrl," don't append/remove "ctrl"
  if (this.name && this.name.toLowerCase() !== 'ctrl' && this.name.substr(-4).toLowerCase() === 'ctrl') {
    this.name = this.name.slice(0, -4);
  }
};

util.inherits(Generator, ScriptBase);

Generator.prototype.askForConstantValue = function askFor() {
  var cb = this.async();

  this.prompt([{
    type: "input",
    name: "module",
    message: "Which module does this controller belong to? '" + this.name + "' : "
  }], function (props) {

    this.module_value = props.module;

    cb();
  }.bind(this));
};

Generator.prototype.createControllerFiles = function createControllerFiles() {

  this.generateSourceAndTest(
    'controller',
    'spec/controller',
    'app/' + this.module_value + '/',
    this.options['add-index'] || true,
    this.module
  );
};


text:
using System;
using Newtonsoft.Json;
using Types.Hexadecimal;

namespace Cryptocurrency.Blockchain.Serialization.Converters
{
    internal class HexJsonConverter : JsonConverter
    {
        public override bool CanConvert(Type objectType)
        {
            return objectType == typeof(Hex);
        }

        public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer)
        {
            return (Hex) reader.Value.ToString();
        }

        public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
        {
            serializer.Serialize(writer, value.ToString());
        }
    }
}


text:
package edu.harvard.gis.hhypermap.bop;

import com.google.common.base.Throwables;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.impl.ConcurrentUpdateSolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.params.ModifiableSolrParams;

import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.Date;
import java.util.Random;

public class SolrUpdateSlammer {
  private static volatile Throwable failedException;
  // sent to the /update? with params
  private static ModifiableSolrParams updateParams = new ModifiableSolrParams();
  static {
    //updateParams.set("_route_", "RT"); // ensures only one shard (that won't be deleted) manages other shards
    updateParams.set("shardMaxAge", "+10SECOND");
    updateParams.set("docMaxAge", "/SECOND-50SECOND");
  }

  private Random random = new Random(0L);
  private SolrClient client;
  private Instant startTime;
  private long nextShiftBack;
  private long nextRandomMillis;
  private String collection;
  Instant endInstant;

  public static void main(String[] args) throws Exception {

    int queueSize = 1;
    int threadCount = 1;
    try (SolrClient client = newClient("http://localhost:8983/solr", queueSize, threadCount)) {
      SolrUpdateSlammer me = new SolrUpdateSlammer();
      me.client = client;
      me.collection = "bop";
      me.startTime = Instant.now().minus(5, ChronoUnit.MINUTES);
      me.nextRandomMillis = 5000;
      me.nextShiftBack = 1000;
      me.endInstant = Instant.now().plusSeconds(30);
      me.slam();
    }
  }

  private void slam() throws IOException, SolrServerException {
    int docCounter = 0;
    long nanosStart = System.nanoTime();
    for (; Instant.now().isBefore(endInstant); docCounter++) {
      throwIfError();
      Instant time = Instant.now().plusMillis(Math.abs(random.nextLong() % nextRandomMillis) - nextShiftBack);
      SolrInputDocument doc = new SolrInputDocument();
      doc.setField("id", docCounter);
      doc.setField("created_at", Date.from(time));
      sendDoc(doc);

      // update time for next lap:

    }
    client.commit(collection);
    throwIfError();
    long nanosEnd = System.nanoTime();
    System.out.println("Added " + docCounter + " docs in " + Duration.ofNanos(nanosEnd - nanosStart));
    SolrQuery solrQuery = new SolrQuery();
    solrQuery.setQuery("*:*").setRows(0);
    QueryResponse queryResponse = client.query(collection, solrQuery);
    System.out.println("Found " + queryResponse.getResults().getNumFound());
  }

  private void sendDoc(SolrInputDocument doc) throws IOException, SolrServerException {
    UpdateRequest req = new UpdateRequest();
    req.add(doc);
    req.setParams(updateParams);
    req.setCommitWithin(1000);
    req.process(client, collection);
  }

  private void throwIfError() {
    if (failedException != null) {
      if (failedException instanceof SolrException) {
        SolrException exception = (SolrException) failedException;
        System.err.println(exception.getMetadata());
      }
      throw Throwables.propagate(failedException);
    }
  }

  private static SolrClient newClient(final String solrServerUrl, int queueSize, final int threadCount) {
    if (queueSize == 1 && threadCount == 1) {
      return new HttpSolrClient.Builder(solrServerUrl).build();
    }
    return new ConcurrentUpdateSolrClient(solrServerUrl, queueSize, threadCount) {
      @Override
      public void handleError(Throwable ex) {
        failedException = ex;
      }
    };
  }
}


text:
package com.madgnome.jira.plugins.jirachievements.data.ao;

public interface ProjectStatistic extends KeyableEntity
{
  StatisticRef getStatisticRef();
  void setStatisticRef(StatisticRef statisticRef);

  UserWrapper getUserWrapper();
  void setUserWrapper(UserWrapper userWrapper);

  String getProjectKey();
  void setProjectKey(String projectKey);

  int getValue();
  void setValue(int value);
}


text:
/**
 * Created by Andrey Khalzov
 * 10.03.2009
 */
package org.wannatrak.client.state;

import org.wannatrak.client.state.command.StateCommand;

import java.util.Map;

public class HowtoSetupState implements State {

    private static HowtoSetupState instance = new HowtoSetupState();

    public static HowtoSetupState getInstance() {
        return instance;
    }

    private HowtoSetupState() {
    }

    public void executeCommand(StateCommand stateCommand, Map<String, String> params) {
        stateCommand.executeForHowtoSetupState(params);
    }
}


text:
package com.simpleCharacterControl;

import SimpleChaseCamera.SimpleCameraState;
import com.basics.*;
import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.BoxCollisionShape;
import com.jme3.bullet.collision.shapes.CapsuleCollisionShape;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.collision.shapes.MeshCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.font.BitmapText;
import com.jme3.input.ChaseCamera;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.math.*;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;

public class CharacterTest extends SimpleApplication {

    public static void main(String[] args) {
        CharacterTest app = new CharacterTest();
        app.start();
    }

    @Override
    public void simpleInitApp() {

        starting();
        flyCam.setEnabled(false);

        // set physics for the client
        BulletAppState bulletAppState = new BulletAppState(PhysicsSpace.BroadphaseType.DBVT); // DBVT is dynamic scale of World Size
        bulletAppState.setThreadingType(BulletAppState.ThreadingType.PARALLEL);  // use physics on a separete thread
        stateManager.attach(bulletAppState);

        Node scene = (Node) assetManager.loadModel("Models/test/testScene.j3o");

//        SimpleCameraState camera = new SimpleCameraState(this);
//        stateManager.attach(camera);
//            camera.getChState().setVerticalDownLimit(0.1f);

        // setup scene
        for (Spatial sp : scene.getChildren()) {
            Node nd = (Node) sp;

            // setup character
            if (nd.getName().equals("character")) {

                nd.setLocalTranslation(nd.getLocalTranslation().addLocal(new Vector3f(0, 20, 0)));
                Geometry geo = (Geometry) nd.getChild(0);
                CapsuleCollisionShape enShape = new CapsuleCollisionShape(0.5f, 1, 1);
                SimpleCharacterControl physContr = new SimpleCharacterControl(this, enShape.getHeight(), enShape, 40f);
                physContr.setPhysicsLocation(nd.getWorldTranslation());
                physContr.setPhysicsRotation(nd.getLocalRotation());
                physContr.setDamping(0.5f, 0.5f);
                physContr.setSleepingThresholds(0.7f, 0.7f);
                physContr.setFriction(1f);
                physContr.setAngularFactor(0f); // this is for better collisions

                nd.addControl(physContr);
                stateManager.getState(BulletAppState.class).getPhysicsSpace().add(physContr);
                physContr.setGravity(Vector3f.UNIT_Y.negate().mult(20));

                System.out.println(enShape.getHeight());
//                charControl.setJumpSpeed(8.0f);
                physContr.setMoveSpeed(4.5f);
                physContr.setMoveSlopeSpeed(0.3f);

//                camera.getChState().setSpatialToFollow(sp);
                ChaseCamera chCam = new ChaseCamera(cam, sp, inputManager);
                nd.addControl(physContr);

                CharacterController charController = new CharacterController(this, physContr);
                stateManager.attach(charController);

            } // setup other objects
            else {
                Geometry geo = (Geometry) nd.getChild(0);
                CollisionShape mshShape = CollisionShapeFactory.createMeshShape(nd);
                float mass = 0;

                if (nd.getName().indexOf("Box") == 0) {
                    mshShape = new BoxCollisionShape(new Vector3f(0.5f, 0.5f, 0.5f));
                    mass = 70;
                }

                mshShape.setScale(nd.getLocalScale());

                RigidBodyControl physContr = new RigidBodyControl(mshShape, mass);
                physContr.setPhysicsLocation(nd.getWorldTranslation());
                physContr.setPhysicsRotation(nd.getLocalRotation());
                physContr.setFriction(0.1f);

                if (nd.getName().indexOf("Box") == 0) {
                    physContr.setFriction(0.2f);
                    physContr.setSleepingThresholds(0.7f, 0.7f);
                    physContr.setDamping(1.0f, 1.0f);
                    physContr.setAngularFactor(0.3f); // this is for better collisions
                }

                nd.addControl(physContr);
                stateManager.getState(BulletAppState.class).getPhysicsSpace().add(physContr);
            }
        }

        Node root = (Node) viewPort.getScenes().get(0);
        root.attachChild(scene);

        DirectionalLight dl = new DirectionalLight();
        root.addLight(dl);
        AmbientLight al = new AmbientLight();
        al.setColor(new ColorRGBA(1, 1, 1.5f, 1));
        root.addLight(al);
        stateManager.getState(BulletAppState.class).setDebugEnabled(true);

    }

    @Override
    public void simpleUpdate(float tpf) {
    }

    public void starting() {
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText ch = new BitmapText(guiFont, false);
        ch.setSize(guiFont.getCharSet().getRenderedSize());
        ch.setText("WASD, LeftClick - This is SimpleCharacterControl"); // crosshairs
        ch.setColor(new ColorRGBA(1f, 0.8f, 0.1f, 1f));
        ch.setLocalTranslation(settings.getWidth() * 0.3f, settings.getHeight() * 0.1f, 0);
        guiNode.attachChild(ch);

        viewPort.setBackgroundColor(ColorRGBA.Gray);
        flyCam.setMoveSpeed(30);
    }
}


text:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="fi.aalto.trafficsense.regularroutes" >

    <uses-feature android:name="android.hardware.location" />

    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.BATTERY_STATS" />
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!--
     To retrieve OAuth 2.0 tokens or invalidate tokens to disconnect a user. This disconnect
     option is required to comply with the Google+ Sign-In developer policies
    -->
    <uses-permission android:name="android.permission.USE_CREDENTIALS" />
    <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application
        android:name=".RegularRoutesApplication"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme"
        tools:replace="android:label" >
        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />

        <activity
            android:name=".ui.MainActivity"
            android:label="@string/app_name"
            android:launchMode="singleTask" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity
            android:name=".ui.LoginActivity"
            android:label="@string/title_activity_login"
            android:launchMode="singleTask"
            android:parentActivityName=".ui.MainActivity"
            android:windowSoftInputMode="adjustResize|stateHidden" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="fi.aalto.trafficsense.regularroutes.ui.MainActivity" />
        </activity>

        <service
            android:name=".backend.BackendService"
            android:enabled="true"
            android:exported="false" />
        <service
            android:name="edu.mit.media.funf.FunfManager"
            android:enabled="true"
            android:exported="false" >
            <meta-data
                android:name="default"
                android:value="@string/pipeline" />
        </service>

        <activity
            android:name=".ui.AboutActivity"
            android:configChanges="orientation|keyboardHidden|screenSize"
            android:label="@string/title_activity_about"
            android:parentActivityName=".ui.MainActivity"
            android:theme="@style/FullscreenTheme" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="fi.aalto.trafficsense.regularroutes.ui.MainActivity" />
        </activity>
        <receiver android:name=".util.RRBootReceiver">
            <intent-filter >
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
            </intent-filter>
        </receiver>

        <activity
            android:name=".ui.EnergyCertificateActivity"
            android:label="@string/title_activity_energy_certificate"
            android:parentActivityName=".ui.MainActivity" >
            <meta-data
                android:name="android.support.PARENT_ACTIVITY"
                android:value="fi.aalto.trafficsense.regularroutes.ui.MainActivity" />
        </activity>
    </application>

</manifest>


text:
import React from 'react';

function withDebugger(ContentToDebug){
  return class WithDebugger{
    render(){
      console.dir(this);
      return <ContentToDebug {...this.props}/>;
    }
  };
}

export default withDebugger;


text:
package io.reactivex.internal.operators;

import java.util.Queue;
import java.util.concurrent.atomic.*;
import java.util.function.Function;

import org.reactivestreams.*;

import io.reactivex.internal.queue.*;
import io.reactivex.internal.subscriptions.EmptySubscription;
import io.reactivex.internal.util.*;
import io.reactivex.plugins.RxJavaPlugins;

public final class PublisherZip<T, R> implements Publisher<R> {

    final Publisher<? extends T>[] sources;
    final Iterable<? extends Publisher<? extends T>> sourcesIterable;
    final Function<? super Object[], ? extends R> zipper;
    final int bufferSize;
    final boolean delayError;

    public PublisherZip(Publisher<? extends T>[] sources,
            Iterable<? extends Publisher<? extends T>> sourcesIterable,
            Function<? super Object[], ? extends R> zipper,
            int bufferSize,
            boolean delayError) {
        this.sources = sources;
        this.sourcesIterable = sourcesIterable;
        this.zipper = zipper;
        this.bufferSize = bufferSize;
        this.delayError = delayError;
    }

    @Override
    @SuppressWarnings("unchecked")
    public void subscribe(Subscriber<? super R> s) {
        Publisher<? extends T>[] sources = this.sources;
        int count = 0;
        if (sources == null) {
            sources = new Publisher[8];
            for (Publisher<? extends T> p : sourcesIterable) {
                if (count == sources.length) {
                    Publisher<? extends T>[] b = new Publisher[count + (count >> 2)];
                    System.arraycopy(sources, 0, b, 0, count);
                    sources = b;
                }
                sources[count++] = p;
            }
        } else {
            count = sources.length;
        }

        if (count == 0) {
            EmptySubscription.complete(s);
            return;
        }

        ZipCoordinator<T, R> zc = new ZipCoordinator<>(s, zipper, count, delayError);
        zc.subscribe(sources, bufferSize);
    }

    static final class ZipCoordinator<T, R> extends AtomicInteger implements Subscription {
        /** */
        private static final long serialVersionUID = 2983708048395377667L;
        final Subscriber<? super R> actual;
        final Function<? super Object[], ? extends R> zipper;
        final ZipSubscriber<T, R>[] subscribers;
        final Object[] row;
        final boolean delayError;

        volatile long requested;
        @SuppressWarnings("rawtypes")
        static final AtomicLongFieldUpdater<ZipCoordinator> REQUESTED =
                AtomicLongFieldUpdater.newUpdater(ZipCoordinator.class, "requested");

        volatile boolean cancelled;

        @SuppressWarnings("unchecked")
        public ZipCoordinator(Subscriber<? super R> actual,
                Function<? super Object[], ? extends R> zipper,
                int count, boolean delayError) {
            this.actual = actual;
            this.zipper = zipper;
            this.subscribers = new ZipSubscriber[count];
            this.row = new Object[count];
            this.delayError = delayError;
        }

        public void subscribe(Publisher<? extends T>[] sources, int bufferSize) {
            ZipSubscriber<T, R>[] s = subscribers;
            int len = s.length;
            for (int i = 0; i < len; i++) {
                s[i] = new ZipSubscriber<>(this, bufferSize);
            }
            // this makes sure the contents of the subscribers array is visible
            REQUESTED.lazySet(this, 0);
            actual.onSubscribe(this);
            for (int i = 0; i < len; i++) {
                if (cancelled) {
                    return;
                }
                sources[i].subscribe(s[i]);
            }
        }

        @Override
        public void request(long n) {
            if (n <= 0) {
                RxJavaPlugins.onError(new IllegalArgumentException("n > required but it was " + n));
                return;
            }
            BackpressureHelper.add(REQUESTED, this, n);
            drain();
        }

        @Override
        public void cancel() {
            if (!cancelled) {
                cancelled = true;
                if (getAndIncrement() == 0) {
                    clear();
                }
            }
        }

        void clear() {
            for (ZipSubscriber<?, ?> zs : subscribers) {
                zs.cancel();
                zs.queue.clear();
            }
        }

        public void drain() {
            if (getAndIncrement() != 0) {
                return;
            }

            int missing = 1;

            final ZipSubscriber<T, R>[] zs = subscribers;
            final Subscriber<? super R> a = actual;
            final Object[] os = row;
            final boolean delayError = this.delayError;

            for (;;) {

                long r = requested;
                boolean unbounded = r == Long.MAX_VALUE;
                long e = 0;

                while (r != 0) {
                    int i = 0;
                    int emptyCount = 0;
                    for (ZipSubscriber<T, R> z : zs) {
                        boolean d = z.done;
                        T v = z.queue.peek();
                        boolean empty = v == null;

                        if (checkTerminated(d, empty, a, delayError, z)) {
                            return;
                        }

                        if (empty) {
                            emptyCount++;
                            continue;
                        }

                        os[i] = v;
                        i++;
                    }

                    if (emptyCount != 0) {
                        break;
                    }
                    // consume the row
                    for (ZipSubscriber<T, R> z : zs) {
                        z.queue.poll();
                    }

                    R v;
                    try {
                        v = zipper.apply(os.clone());
                    } catch (Throwable ex) {
                        clear();
                        a.onError(ex);
                        return;
                    }

                    a.onNext(v);

                    r--;
                    e++;
                }

                if (e != 0) {
                    if (!unbounded) {
                        REQUESTED.addAndGet(this, -e);
                    }
                    for (ZipSubscriber<T, R> z : zs) {
                        z.request(e);
                    }
                }

                missing = addAndGet(-missing);
                if (missing == 0) {
                    return;
                }
            }
        }

        boolean checkTerminated(boolean d, boolean empty, Subscriber<? super R> a, boolean delayError, ZipSubscriber<?, ?> source) {
            if (cancelled) {
                clear();
                return true;
            }

            if (d) {
                if (delayError) {
                    if (empty) {
                        Throwable e = source.error;
                        clear();
                        if (e != null) {
                            a.onError(e);
                        } else {
                            a.onComplete();
                        }
                        return true;
                    }
                } else {
                    Throwable e = source.error;
                    if (e != null) {
                        clear();
                        a.onError(e);
                        return true;
                    } else
                    if (empty) {
                        clear();
                        a.onComplete();
                        return true;
                    }
                }
            }

            return false;
        }
    }

    static final class ZipSubscriber<T, R> extends AtomicLong implements Subscriber<T>, Subscription {
        /** */
        private static final long serialVersionUID = -4627193790118206028L;

        final ZipCoordinator<T, R> parent;
        final int bufferSize;
        final Queue<T> queue;

        volatile boolean done;
        Throwable error;

        volatile Subscription s;
        @SuppressWarnings("rawtypes")
        static final AtomicReferenceFieldUpdater<ZipSubscriber, Subscription> S =
                AtomicReferenceFieldUpdater.newUpdater(ZipSubscriber.class, Subscription.class, "s");

        Subscription cachedS;

        static final Subscription CANCELLED = new Subscription() {
            @Override
            public void request(long n) {

            }

            @Override
            public void cancel() {

            }
        };

        public ZipSubscriber(ZipCoordinator<T, R> parent, int bufferSize) {
            this.parent = parent;
            this.bufferSize = bufferSize;
            Queue<T> q;
            if (Pow2.isPowerOfTwo(bufferSize)) {
                q = new SpscArrayQueue<>(bufferSize);
            } else {
                q = new SpscExactArrayQueue<>(bufferSize);
            }
            this.queue = q;
        }
        @Override
        public void onSubscribe(Subscription s) {

            for (;;) {
                Subscription current = this.s;
                if (current == CANCELLED) {
                    s.cancel();
                    return;
                }
                if (current != null) {
                    s.cancel();
                    RxJavaPlugins.onError(new IllegalStateException("Subscription already set!"));
                    return;
                }
                if (S.compareAndSet(this, null, s)) {
                    lazySet(bufferSize);
                    s.request(bufferSize);
                    return;
                } else {
                    s.cancel();
                }
            }

        }

        @Override
        public void onNext(T t) {
            if (t == null) {
                s.cancel();
                onError(new NullPointerException());
                return;
            }
            if (!queue.offer(t)) {
                s.cancel();
                onError(new IllegalStateException("Queue full?!"));
                return;
            }
            parent.drain();
        }

        @Override
        public void onError(Throwable t) {
            error = t;
            done = true;
            parent.drain();
        }

        @Override
        public void onComplete() {
            done = true;
            parent.drain();
        }

        @Override
        public void request(long n) {
            lazySet(BackpressureHelper.addCap(get(), n));
            // this method is only called if s is no longer null;
            if (cachedS == null) {
                cachedS = s;
            }
            cachedS.request(n);
        }

        @Override
        public void cancel() {
            Subscription s = this.s;
            if (s != CANCELLED) {
                s = S.getAndSet(this, CANCELLED);
                if (s != CANCELLED && s != null) {
                    s.cancel();
                }
            }
        }

        public void produced(long n) {
            long v = get() - n;
            if (v < 0L) {
                RxJavaPlugins.onError(new IllegalArgumentException("More produced than requested: " + v));
                v = 0;
            }
            lazySet(v);
        }
    }
}


text:
    <title>Hello Google</title>

Hello,

    Email :


text:
package org.apache.spark.ml.feature

import org.scalatest.Assertions._

import org.apache.spark.ml.linalg.{Vector, VectorUDT}
import org.apache.spark.ml.util.{MLTestingUtils, SchemaUtils}
import org.apache.spark.sql.Dataset
import org.apache.spark.sql.functions._
import org.apache.spark.sql.types.DataTypes

private[ml] object LSHTest {
  /**
   * For any locality sensitive function h in a metric space, we meed to verify whether
   * the following property is satisfied.
   *
   * There exist dist1, dist2, p1, p2, so that for any two elements e1 and e2,
   * If dist(e1, e2) is less than or equal to dist1, then Pr{h(x) == h(y)} is greater than
   * or equal to p1
   * If dist(e1, e2) is greater than or equal to dist2, then Pr{h(x) == h(y)} is less than
   * or equal to p2
   *
   * This is called locality sensitive property. This method checks the property on an
   * existing dataset and calculate the probabilities.
   * (https://en.wikipedia.org/wiki/Locality-sensitive_hashing#Definition)
   *
   * This method hashes each elements to hash buckets using LSH, and calculate the false positive
   * and false negative:
   * False positive: Of all (e1, e2) sharing any bucket, the probability of dist(e1, e2) is greater
   * than distFP
   * False negative: Of all (e1, e2) not sharing buckets, the probability of dist(e1, e2) is less
   * than distFN
   *
   * @param dataset The dataset to verify the locality sensitive hashing property.
   * @param lsh The lsh instance to perform the hashing
   * @param distFP Distance threshold for false positive
   * @param distFN Distance threshold for false negative
   * @tparam T The class type of lsh
   * @return A tuple of two doubles, representing the false positive and false negative rate
   */
  def calculateLSHProperty[T <: LSHModel[T]](
      dataset: Dataset[_],
      lsh: LSH[T],
      distFP: Double,
      distFN: Double): (Double, Double) = {
    val model = lsh.fit(dataset)
    val inputCol = model.getInputCol
    val outputCol = model.getOutputCol
    val transformedData = model.transform(dataset)

    MLTestingUtils.checkCopyAndUids(lsh, model)

    // Check output column type
    SchemaUtils.checkColumnType(
      transformedData.schema, model.getOutputCol, DataTypes.createArrayType(new VectorUDT))

    // Check output column dimensions
    val headHashValue = transformedData.select(outputCol).head().get(0).asInstanceOf[Seq[Vector]]
    assert(headHashValue.length == model.getNumHashTables)

    // Perform a cross join and label each pair of same_bucket and distance
    val pairs = transformedData.as("a").crossJoin(transformedData.as("b"))
    val distUDF = udf((x: Vector, y: Vector) => model.keyDistance(x, y))
    val sameBucket = udf((x: Seq[Vector], y: Seq[Vector]) => model.hashDistance(x, y) == 0.0)
    val result = pairs
      .withColumn("same_bucket", sameBucket(col(s"a.$outputCol"), col(s"b.$outputCol")))
      .withColumn("distance", distUDF(col(s"a.$inputCol"), col(s"b.$inputCol")))

    // Compute the probabilities based on the join result
    val positive = result.filter(col("same_bucket"))
    val negative = result.filter(!col("same_bucket"))
    val falsePositiveCount = positive.filter(col("distance") > distFP).count().toDouble
    val falseNegativeCount = negative.filter(col("distance") < distFN).count().toDouble
    (falsePositiveCount / positive.count(), falseNegativeCount / negative.count())
  }

  /**
   * Compute the precision and recall of approximate nearest neighbors
   * @param lsh The lsh instance
   * @param dataset the dataset to look for the key
   * @param key The key to hash for the item
   * @param k The maximum number of items closest to the key
   * @param singleProbe True for using single-probe; false for multi-probe
   * @tparam T The class type of lsh
   * @return A tuple of two doubles, representing precision and recall rate
   */
  def calculateApproxNearestNeighbors[T <: LSHModel[T]](
      lsh: LSH[T],
      dataset: Dataset[_],
      key: Vector,
      k: Int,
      singleProbe: Boolean): (Double, Double) = {
    val model = lsh.fit(dataset)

    // Compute expected
    val distUDF = udf((x: Vector) => model.keyDistance(x, key))
    val expected = dataset.sort(distUDF(col(model.getInputCol))).limit(k)

    // Compute actual
    val actual = model.approxNearestNeighbors(dataset, key, k, singleProbe, "distCol")

    assert(actual.schema.sameType(model
      .transformSchema(dataset.schema)
      .add("distCol", DataTypes.DoubleType))
    )

    if (!singleProbe) {
      assert(actual.count() == k)
    }

    // Compute precision and recall
    val correctCount = expected.join(actual, model.getInputCol).count().toDouble
    (correctCount / actual.count(), correctCount / expected.count())
  }

  /**
   * Compute the precision and recall of approximate similarity join
   * @param lsh The lsh instance
   * @param datasetA One of the datasets to join
   * @param datasetB Another dataset to join
   * @param threshold The threshold for the distance of record pairs
   * @tparam T The class type of lsh
   * @return A tuple of two doubles, representing precision and recall rate
   */
  def calculateApproxSimilarityJoin[T <: LSHModel[T]](
      lsh: LSH[T],
      datasetA: Dataset[_],
      datasetB: Dataset[_],
      threshold: Double): (Double, Double) = {
    val model = lsh.fit(datasetA)
    val inputCol = model.getInputCol

    // Compute expected
    val distUDF = udf((x: Vector, y: Vector) => model.keyDistance(x, y))
    val expected = datasetA.as("a").crossJoin(datasetB.as("b"))
      .filter(distUDF(col(s"a.$inputCol"), col(s"b.$inputCol")) < threshold)

    // Compute actual
    val actual = model.approxSimilarityJoin(datasetA, datasetB, threshold)

    SchemaUtils.checkColumnType(actual.schema, "distCol", DataTypes.DoubleType)
    assert(actual.schema.apply("datasetA").dataType
      .sameType(model.transformSchema(datasetA.schema)))
    assert(actual.schema.apply("datasetB").dataType
      .sameType(model.transformSchema(datasetB.schema)))

    // Compute precision and recall
    val correctCount = actual.filter(col("distCol") < threshold).count().toDouble
    (correctCount / actual.count(), correctCount / expected.count())
  }
}


text:
import { ParamDefinition as PD } from '../../../mol-util/param-definition';
import { Structure, Unit, StructureElement } from '../../../mol-model/structure';
import { FeaturesBuilder, Features } from './features';
import { typeSymbol, compId, atomId } from '../chemistry/util';
import { Elements, isTransitionMetal, isHalogen } from '../../../mol-model/structure/model/properties/atomic/types';
import { FeatureType, FeatureGroup, InteractionType } from './common';
import { ContactProvider } from './contacts';
import { ElementSymbol, AminoAcidNames, BaseNames, ProteinBackboneAtoms, NucleicBackboneAtoms } from '../../../mol-model/structure/model/types';

export const MetalCoordinationParams = {
    distanceMax: PD.Numeric(3.0, { min: 1, max: 5, step: 0.1 }),
};
export type MetalCoordinationParams = typeof MetalCoordinationParams
export type MetalCoordinationProps = PD.Values<MetalCoordinationParams>

const IonicTypeMetals = [
    Elements.LI, Elements.NA, Elements.K, Elements.RB, Elements.CS,
    Elements.MG, Elements.CA, Elements.SR, Elements.BA, Elements.AL,
    Elements.GA, Elements.IN, Elements.TL, Elements.SC, Elements.SN,
    Elements.PB, Elements.BI, Elements.SB, Elements.HG
] as ElementSymbol[];

function addMetal(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
    const { elements } = unit;
    const { x, y, z } = unit.model.atomicConformation;

    for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
        const element = typeSymbol(unit, i);

        let type = FeatureType.None;
        if (IonicTypeMetals.includes(element)) {
            type = FeatureType.IonicTypeMetal;
        } else if (isTransitionMetal(element) || element === Elements.ZN || element === Elements.CD) {
            type = FeatureType.TransitionMetal;
        }

        if (type) {
            builder.add(type, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
        }
    }
}

function isProteinSidechain(atomname: string) {
    return !ProteinBackboneAtoms.has(atomname);
}

function isProteinBackbone(atomname: string) {
    return ProteinBackboneAtoms.has(atomname);
}

function isNucleicBackbone(atomname: string) {
    return NucleicBackboneAtoms.has(atomname);
}

/**
 * Metal binding partners (dative bond or ionic-type interaction)
 */
function addMetalBinding(structure: Structure, unit: Unit.Atomic, builder: FeaturesBuilder) {
    const { elements } = unit;
    const { x, y, z } = unit.model.atomicConformation;

    for (let i = 0 as StructureElement.UnitIndex, il = elements.length; i < il; ++i) {
        const element = typeSymbol(unit, i);
        const resname = compId(unit, i);
        const atomname = atomId(unit, i);
        let dative = false;
        let ionic = false;

        const isStandardAminoacid = AminoAcidNames.has(resname);
        const isStandardBase = BaseNames.has(resname);

        if (!isStandardAminoacid && !isStandardBase) {
            if (isHalogen(element) || element === Elements.O || element === Elements.S) {
                dative = true;
                ionic = true;
            } else if (element === Elements.N) {
                dative = true;
            }
        } else if (isStandardAminoacid) {
            // main chain oxygen atom or oxygen, nitrogen and sulfur from specific amino acids
            if (element === Elements.O) {
                if (['ASP', 'GLU', 'SER', 'THR', 'TYR', 'ASN', 'GLN'].includes(resname) && isProteinSidechain(atomname)) {
                    dative = true;
                    ionic = true;
                } else if (isProteinBackbone(atomname)) {
                    dative = true;
                    ionic = true;
                }
            } else if (element === Elements.S && (resname === 'CYS' || resname === 'MET')) {
                dative = true;
                ionic = true;
            } else if (element === Elements.N) {
                if (resname === 'HIS' && isProteinSidechain(atomname)) {
                    dative = true;
                }
            }
        } else if (isStandardBase) {
            // http://pubs.acs.org/doi/pdf/10.1021/acs.accounts.6b00253
            // http://onlinelibrary.wiley.com/doi/10.1002/anie.200900399/full
            if (element === Elements.O && isNucleicBackbone(atomname)) {
                dative = true;
                ionic = true;
            } else if (['N3', 'N4', 'N7'].includes(atomname)) {
                dative = true;
            } else if (['O2', 'O4', 'O6'].includes(atomname)) {
                dative = true;
                ionic = true;
            }
        }

        if (dative) {
            builder.add(FeatureType.DativeBondPartner, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
        }
        if (ionic) {
            builder.add(FeatureType.IonicTypePartner, FeatureGroup.None, x[elements[i]], y[elements[i]], z[elements[i]], i);
        }
    }
}

function isMetalCoordination(ti: FeatureType, tj: FeatureType) {
    if (ti === FeatureType.TransitionMetal) {
        return (
            tj === FeatureType.DativeBondPartner ||
            tj === FeatureType.TransitionMetal
        );
    } else if (ti === FeatureType.IonicTypeMetal) {
        return (
            tj === FeatureType.IonicTypePartner
        );
    }
}

function testMetalCoordination(structure: Structure, infoA: Features.Info, infoB: Features.Info, distanceSq: number): InteractionType | undefined {
    const typeA = infoA.types[infoA.feature];
    const typeB = infoB.types[infoB.feature];

    if (!isMetalCoordination(typeA, typeB) && !isMetalCoordination(typeB, typeA)) return;

    return InteractionType.MetalCoordination;
}

//

export const MetalProvider = Features.Provider([FeatureType.IonicTypeMetal, FeatureType.TransitionMetal], addMetal);
export const MetalBindingProvider = Features.Provider([FeatureType.IonicTypePartner, FeatureType.DativeBondPartner], addMetalBinding);

export const MetalCoordinationProvider: ContactProvider<MetalCoordinationParams> = {
    name: 'metal-coordination',
    params: MetalCoordinationParams,
    createTester: (props: MetalCoordinationProps) => {
        return {
            maxDistance: props.distanceMax,
            requiredFeatures: new Set([FeatureType.IonicTypeMetal, FeatureType.TransitionMetal, FeatureType.IonicTypePartner, FeatureType.DativeBondPartner]),
            getType: (structure, infoA, infoB, distanceSq) => testMetalCoordination(structure, infoA, infoB, distanceSq)
        };
    }
};