<commit_before>package net.glowstone;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.blocktype.BlockTnt;
import net.glowstone.entity.GlowEntity;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.net.message.play.game.ExplosionMessage;
import net.glowstone.net.message.play.game.ExplosionMessage.Record;
import net.glowstone.util.RayUtil;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;

public final class Explosion {

    public static final int POWER_TNT = 4;
    public static final int POWER_BED = 5;
    public static final int POWER_CREEPER = 3;
    public static final int POWER_CHARGED_CREEPER = 6;
    public static final int POWER_GHAST = 1;
    public static final int POWER_WITHER_SKULL = 1;
    public static final int POWER_WITHER_CREATION = 7;
    public static final int POWER_ENDER_CRYSTAL = 6;
    public static final int EXPLOSION_VISIBILITY_RADIUS = 64;
    private static final List<Vector> RAY_DIRECTIONS = new ArrayList<>();

    static {
        int value = 16;
        for (int x = 0; x < value; x++) {
            for (int y = 0; y < value; y++) {
                for (int z = 0; z < value; z++) {
                    if (!(x == 0 || x == value - 1
                            || y == 0 || y == value - 1
                            || z == 0 || z == value - 1)) {
                        continue;
                    }
                    double vx = x / 7.5 - 1;
                    double vy = y / 7.5 - 1;
                    double vz = z / 7.5 - 1;
                    Vector direction = new Vector(vx, vy, vz);
                    direction.normalize();
                    direction.multiply(0.3f); // 0.3 blocks away with each step

                    RAY_DIRECTIONS.add(direction);
                }
            }
        }
    }

    private final Entity source;
    private final Location location;
    private final boolean incendiary;
    private final boolean breakBlocks;
    private final GlowWorld world;
    private float power;
    private float yield = 0.3f;

    /**
     * Creates a new explosion.
     *
     * @param source The entity causing this explosion
     * @param world The world this explosion is in
     * @param x The X location of the explosion
     * @param y The Y location of the explosion
     * @param z The Z location of the explosion
     * @param power The power of the explosion
     * @param incendiary Whether or not blocks should be set on fire
     * @param breakBlocks Whether blocks should break through this explosion
     */
    public Explosion(Entity source, GlowWorld world, double x, double y, double z, float power,
            boolean incendiary, boolean breakBlocks) {
        this(source, new Location(world, x, y, z), power, incendiary, breakBlocks);
    }

    /**
     * Creates a new explosion.
     *
     * @param source The entity causing this explosion
     * @param location The location this explosion is occuring at. Must be in a GlowWorld.
     * @param power The power of the explosion
     * @param incendiary Whether or not blocks should be set on fire
     * @param breakBlocks Whether blocks should break through this explosion
     */
    public Explosion(Entity source, Location location, float power, boolean incendiary,
            boolean breakBlocks) {
        if (!(location.getWorld() instanceof GlowWorld)) {
            throw new IllegalArgumentException("Supplied location does not have a valid GlowWorld");
        }

        this.source = source;
        this.location = location.clone();
        this.power = power;
        this.incendiary = incendiary;
        this.breakBlocks = breakBlocks;
        world = (GlowWorld) location.getWorld();
    }

    /**
     * Explodes, firing a {@link EntityExplodeEvent}, as long as its power is non-negligible.
     * @return false if the explosion was canceled by a plugin; true otherwise
     */
    public boolean explodeWithEvent() {
        if (power < 0.1f) {
            return true;
        }

        Set<BlockVector> droppedBlocks = calculateBlocks();

        List<Block> blocks = toBlockList(droppedBlocks);
        EntityExplodeEvent event = EventFactory.callEvent(
                new EntityExplodeEvent(source, location, blocks, yield));
        if (event.isCancelled()) {
            return false;
        }

        yield = event.getYield();

        playOutSoundAndParticles();

        for (Block block : blocks) {
            handleBlockExplosion((GlowBlock) block);
        }

        if (incendiary) {
            for (Block block : blocks) {
                setBlockOnFire((GlowBlock) block);
            }
        }

        damageEntities();
        Collection<GlowPlayer> affectedPlayers
                = collectPlayersInRadius(EXPLOSION_VISIBILITY_RADIUS);
        for (GlowPlayer player : affectedPlayers) {
            playOutExplosion(player, blocks);
        }

        return true;
    }

    ///////////////////////////////////////////////////
    // Calculate all the dropping blocks

    private Set<BlockVector> calculateBlocks() {
        if (!breakBlocks) {
            return new HashSet<>();
        }

        Set<BlockVector> blocks = new HashSet<>();

        RAY_DIRECTIONS.forEach(v -> calculateRay(v, blocks));

        return blocks;
    }

    private void calculateRay(Vector direction, Collection<BlockVector> result) {
        Location current = location.clone();

        float currentPower = calculateStartPower();

        while (currentPower > 0) {
            GlowBlock block = world.getBlockAt(current);

            if (block.getType() != Material.AIR) {
                double blastDurability = getBlastDurability(block) / 5d;
                blastDurability += 0.3F;
                blastDurability *= 0.3F;
                currentPower -= blastDurability;

                if (currentPower > 0) {
                    result.add(new BlockVector(block.getX(), block.getY(), block.getZ()));
                }
            }

            current.add(direction);
            currentPower -= 0.225f;
        }
    }

    private void handleBlockExplosion(GlowBlock block) {
        if (block.getType() == Material.AIR
                || block.getType() == Material.BARRIER
                || block.getType() == Material.BEDROCK) {
            return;
        } else if (block.getType() == Material.TNT) {
            BlockTnt.igniteBlock(block, true);
            return;
        }

        block.breakNaturally(yield);
    }

    private float calculateStartPower() {
        float rand = ThreadLocalRandom.current().nextFloat();
        rand *= 0.6F; // (max - 0.7)
        rand += 0.7; // min
        return rand * power;
    }

    private double getBlastDurability(GlowBlock block) {
        return block.getMaterialValues().getBlastResistance();
    }

    private List<Block> toBlockList(Collection<BlockVector> locations) {
        List<Block> blocks = new ArrayList<>(locations.size());
        blocks.addAll(locations.stream()
                .map(location -> world.getBlockAt(
                        location.getBlockX(), location.getBlockY(), location.getBlockZ()))
                .collect(Collectors.toList()));
        return blocks;
    }

    private void setBlockOnFire(GlowBlock block) {
        if (ThreadLocalRandom.current().nextInt(3) != 0) {
            return;
        }
        Block below = block.getRelative(BlockFace.DOWN);
        Material belowType = below.getType();
        if (belowType == Material.AIR || belowType == Material.FIRE || !belowType.isFlammable()) {
            return;
        }
        BlockIgniteEvent event = EventFactory.callEvent(
                new BlockIgniteEvent(block, IgniteCause.EXPLOSION, source));
        if (event.isCancelled()) {
            return;
        }

        block.setType(Material.FIRE);
    }

    /////////////////////////////////////////
    // Damage entities

    private void damageEntities() {
        float power = this.power;
        this.power *= 2;

        Collection<Entity> entities = getNearbyEntities();
        for (Entity entity : entities) {
            if (!(entity instanceof GlowEntity)) {
                continue;
            }
            // refine area to sphere, instead of box
            if (distanceToSquared(entity) > power * power) {
                continue;
            }

            double exposure = world.rayTrace(location, (GlowEntity) entity);
            double impact = (1 - (distanceTo(entity) / power / 2)) * exposure;

            double damage = (impact * impact + impact) * 8 * power + 1;
            int epf = getProtectionFactor(entity);
            double reduction = calculateEnchantedReduction(epf);

            damage = damage * reduction;

            exposure -= exposure * epf * 0.15;

            DamageCause damageCause;
            if (source == null || source.getType() == EntityType.PRIMED_TNT) {
                damageCause = DamageCause.BLOCK_EXPLOSION;
            } else {
                damageCause = DamageCause.ENTITY_EXPLOSION;
            }
            ((GlowEntity) entity).damage(damage, source, damageCause);

            if (entity instanceof GlowPlayer && ((GlowPlayer) entity).isFlying()) {
                continue;
            }

            if (entity instanceof LivingEntity) {
                LivingEntity livingEntity = (LivingEntity) entity;
                Vector rayLength = RayUtil.getVelocityRay(distanceToHead(livingEntity));
                rayLength.multiply(exposure);

                Vector currentVelocity = entity.getVelocity();
                currentVelocity.add(rayLength);
                entity.setVelocity(currentVelocity);
            }
        }
    }

    private double calculateEnchantedReduction(int epf) {
        // TODO: move this to damage main (in entity)
        double reduction = 1;
        if (epf > 0) {
            reduction = (1 - epf / 25);
        }
        return reduction;
    }

    private int getProtectionFactor(Entity entity) {
        if (!(entity instanceof LivingEntity)) {
            return 0;
        }

        LivingEntity livingEntity = (LivingEntity) entity;
        int level = 0;
        if (livingEntity.getEquipment() != null) {
            for (ItemStack stack : livingEntity.getEquipment().getArmorContents()) {
                if (stack != null) {
                    int stackLevel = stack.getEnchantmentLevel(Enchantment.PROTECTION_EXPLOSIONS);
                    if (stackLevel > level) {
                        level = stackLevel;
                    }
                }
            }
        }

        return level << 1;
    }

    private Collection<Entity> getNearbyEntities() {
        return location.getWorld().getNearbyEntities(location, power, power, power);
    }

    private double distanceTo(Entity entity) {
        return RayUtil.getRayBetween(location, entity.getLocation()).length();
    }

    private double distanceToSquared(Entity entity) {
        return RayUtil.getRayBetween(location, entity.getLocation()).lengthSquared();
    }

    private Vector distanceToHead(LivingEntity entity) {
        return RayUtil.getRayBetween(entity.getEyeLocation(), location);
    }

    ///////////////////////////////////////
    // Visualize
    private void playOutSoundAndParticles() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        world.playSound(location, Sound.ENTITY_GENERIC_EXPLODE, 4,
                (1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F) * 0.7F);

        if (power >= 2.0F && breakBlocks) {
            // send huge explosion
            world.spigot().playEffect(location, Effect.EXPLOSION_HUGE);
        } else {
            // send large explosion
            world.spigot().playEffect(location, Effect.EXPLOSION_LARGE);
        }
    }

    private void playOutExplosion(GlowPlayer player, Iterable<Block> blocks) {
        Collection<Record> records = new ArrayList<>();

        for (Block block : blocks) {
            Location blockLocation = block.getLocation();
            byte x = (byte) (blockLocation.getBlockX() - (int) this.location.getX());
            byte y = (byte) (blockLocation.getBlockY() - (int) this.location.getY());
            byte z = (byte) (blockLocation.getBlockZ() - (int) this.location.getZ());
            records.add(new Record(x, y, z));
        }

        Vector velocity = player.getVelocity();
        ExplosionMessage message = new ExplosionMessage(
                (float) location.getX(), (float) location.getY(), (float) location.getZ(), power,
                (float) velocity.getX(), (float) velocity.getY(), (float) velocity.getZ(), records);

        player.getSession().send(message);
    }

    private Collection<GlowPlayer> collectPlayersInRadius(int radius) {
        int radiusSquared = radius * radius;
        return world.getRawPlayers().stream()
                .filter(player -> player.getLocation().distanceSquared(location) <= radiusSquared)
                .collect(Collectors.toList());
    }
}
<commit_msg>Distribute not in boolean check in Explosion<commit_after>package net.glowstone;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
import net.glowstone.block.GlowBlock;
import net.glowstone.block.blocktype.BlockTnt;
import net.glowstone.entity.GlowEntity;
import net.glowstone.entity.GlowPlayer;
import net.glowstone.net.message.play.game.ExplosionMessage;
import net.glowstone.net.message.play.game.ExplosionMessage.Record;
import net.glowstone.util.RayUtil;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.LivingEntity;
import org.bukkit.event.block.BlockIgniteEvent;
import org.bukkit.event.block.BlockIgniteEvent.IgniteCause;
import org.bukkit.event.entity.EntityDamageEvent.DamageCause;
import org.bukkit.event.entity.EntityExplodeEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.util.BlockVector;
import org.bukkit.util.Vector;

public final class Explosion {

    public static final int POWER_TNT = 4;
    public static final int POWER_BED = 5;
    public static final int POWER_CREEPER = 3;
    public static final int POWER_CHARGED_CREEPER = 6;
    public static final int POWER_GHAST = 1;
    public static final int POWER_WITHER_SKULL = 1;
    public static final int POWER_WITHER_CREATION = 7;
    public static final int POWER_ENDER_CRYSTAL = 6;
    public static final int EXPLOSION_VISIBILITY_RADIUS = 64;
    private static final List<Vector> RAY_DIRECTIONS = new ArrayList<>();

    static {
        int value = 16;
        for (int x = 0; x < value; x++) {
            for (int y = 0; y < value; y++) {
                for (int z = 0; z < value; z++) {
                    if (x != 0 && x != value - 1
                            && y != 0 && y != value - 1
                            && z != 0 && z != value - 1) {
                        continue;
                    }
                    double vx = x / 7.5 - 1;
                    double vy = y / 7.5 - 1;
                    double vz = z / 7.5 - 1;
                    Vector direction = new Vector(vx, vy, vz);
                    direction.normalize();
                    direction.multiply(0.3f); // 0.3 blocks away with each step

                    RAY_DIRECTIONS.add(direction);
                }
            }
        }
    }

    private final Entity source;
    private final Location location;
    private final boolean incendiary;
    private final boolean breakBlocks;
    private final GlowWorld world;
    private float power;
    private float yield = 0.3f;

    /**
     * Creates a new explosion.
     *
     * @param source The entity causing this explosion
     * @param world The world this explosion is in
     * @param x The X location of the explosion
     * @param y The Y location of the explosion
     * @param z The Z location of the explosion
     * @param power The power of the explosion
     * @param incendiary Whether or not blocks should be set on fire
     * @param breakBlocks Whether blocks should break through this explosion
     */
    public Explosion(Entity source, GlowWorld world, double x, double y, double z, float power,
            boolean incendiary, boolean breakBlocks) {
        this(source, new Location(world, x, y, z), power, incendiary, breakBlocks);
    }

    /**
     * Creates a new explosion.
     *
     * @param source The entity causing this explosion
     * @param location The location this explosion is occuring at. Must be in a GlowWorld.
     * @param power The power of the explosion
     * @param incendiary Whether or not blocks should be set on fire
     * @param breakBlocks Whether blocks should break through this explosion
     */
    public Explosion(Entity source, Location location, float power, boolean incendiary,
            boolean breakBlocks) {
        if (!(location.getWorld() instanceof GlowWorld)) {
            throw new IllegalArgumentException("Supplied location does not have a valid GlowWorld");
        }

        this.source = source;
        this.location = location.clone();
        this.power = power;
        this.incendiary = incendiary;
        this.breakBlocks = breakBlocks;
        world = (GlowWorld) location.getWorld();
    }

    /**
     * Explodes, firing a {@link EntityExplodeEvent}, as long as its power is non-negligible.
     * @return false if the explosion was canceled by a plugin; true otherwise
     */
    public boolean explodeWithEvent() {
        if (power < 0.1f) {
            return true;
        }

        Set<BlockVector> droppedBlocks = calculateBlocks();

        List<Block> blocks = toBlockList(droppedBlocks);
        EntityExplodeEvent event = EventFactory.callEvent(
                new EntityExplodeEvent(source, location, blocks, yield));
        if (event.isCancelled()) {
            return false;
        }

        yield = event.getYield();

        playOutSoundAndParticles();

        for (Block block : blocks) {
            handleBlockExplosion((GlowBlock) block);
        }

        if (incendiary) {
            for (Block block : blocks) {
                setBlockOnFire((GlowBlock) block);
            }
        }

        damageEntities();
        Collection<GlowPlayer> affectedPlayers
                = collectPlayersInRadius(EXPLOSION_VISIBILITY_RADIUS);
        for (GlowPlayer player : affectedPlayers) {
            playOutExplosion(player, blocks);
        }

        return true;
    }

    ///////////////////////////////////////////////////
    // Calculate all the dropping blocks

    private Set<BlockVector> calculateBlocks() {
        if (!breakBlocks) {
            return new HashSet<>();
        }

        Set<BlockVector> blocks = new HashSet<>();

        RAY_DIRECTIONS.forEach(v -> calculateRay(v, blocks));

        return blocks;
    }

    private void calculateRay(Vector direction, Collection<BlockVector> result) {
        Location current = location.clone();

        float currentPower = calculateStartPower();

        while (currentPower > 0) {
            GlowBlock block = world.getBlockAt(current);

            if (block.getType() != Material.AIR) {
                double blastDurability = getBlastDurability(block) / 5d;
                blastDurability += 0.3F;
                blastDurability *= 0.3F;
                currentPower -= blastDurability;

                if (currentPower > 0) {
                    result.add(new BlockVector(block.getX(), block.getY(), block.getZ()));
                }
            }

            current.add(direction);
            currentPower -= 0.225f;
        }
    }

    private void handleBlockExplosion(GlowBlock block) {
        if (block.getType() == Material.AIR
                || block.getType() == Material.BARRIER
                || block.getType() == Material.BEDROCK) {
            return;
        } else if (block.getType() == Material.TNT) {
            BlockTnt.igniteBlock(block, true);
            return;
        }

        block.breakNaturally(yield);
    }

    private float calculateStartPower() {
        float rand = ThreadLocalRandom.current().nextFloat();
        rand *= 0.6F; // (max - 0.7)
        rand += 0.7; // min
        return rand * power;
    }

    private double getBlastDurability(GlowBlock block) {
        return block.getMaterialValues().getBlastResistance();
    }

    private List<Block> toBlockList(Collection<BlockVector> locations) {
        List<Block> blocks = new ArrayList<>(locations.size());
        blocks.addAll(locations.stream()
                .map(location -> world.getBlockAt(
                        location.getBlockX(), location.getBlockY(), location.getBlockZ()))
                .collect(Collectors.toList()));
        return blocks;
    }

    private void setBlockOnFire(GlowBlock block) {
        if (ThreadLocalRandom.current().nextInt(3) != 0) {
            return;
        }
        Block below = block.getRelative(BlockFace.DOWN);
        Material belowType = below.getType();
        if (belowType == Material.AIR || belowType == Material.FIRE || !belowType.isFlammable()) {
            return;
        }
        BlockIgniteEvent event = EventFactory.callEvent(
                new BlockIgniteEvent(block, IgniteCause.EXPLOSION, source));
        if (event.isCancelled()) {
            return;
        }

        block.setType(Material.FIRE);
    }

    /////////////////////////////////////////
    // Damage entities

    private void damageEntities() {
        float power = this.power;
        this.power *= 2;

        Collection<Entity> entities = getNearbyEntities();
        for (Entity entity : entities) {
            if (!(entity instanceof GlowEntity)) {
                continue;
            }
            // refine area to sphere, instead of box
            if (distanceToSquared(entity) > power * power) {
                continue;
            }

            double exposure = world.rayTrace(location, (GlowEntity) entity);
            double impact = (1 - (distanceTo(entity) / power / 2)) * exposure;

            double damage = (impact * impact + impact) * 8 * power + 1;
            int epf = getProtectionFactor(entity);
            double reduction = calculateEnchantedReduction(epf);

            damage = damage * reduction;

            exposure -= exposure * epf * 0.15;

            DamageCause damageCause;
            if (source == null || source.getType() == EntityType.PRIMED_TNT) {
                damageCause = DamageCause.BLOCK_EXPLOSION;
            } else {
                damageCause = DamageCause.ENTITY_EXPLOSION;
            }
            ((GlowEntity) entity).damage(damage, source, damageCause);

            if (entity instanceof GlowPlayer && ((GlowPlayer) entity).isFlying()) {
                continue;
            }

            if (entity instanceof LivingEntity) {
                LivingEntity livingEntity = (LivingEntity) entity;
                Vector rayLength = RayUtil.getVelocityRay(distanceToHead(livingEntity));
                rayLength.multiply(exposure);

                Vector currentVelocity = entity.getVelocity();
                currentVelocity.add(rayLength);
                entity.setVelocity(currentVelocity);
            }
        }
    }

    private double calculateEnchantedReduction(int epf) {
        // TODO: move this to damage main (in entity)
        double reduction = 1;
        if (epf > 0) {
            reduction = (1 - epf / 25);
        }
        return reduction;
    }

    private int getProtectionFactor(Entity entity) {
        if (!(entity instanceof LivingEntity)) {
            return 0;
        }

        LivingEntity livingEntity = (LivingEntity) entity;
        int level = 0;
        if (livingEntity.getEquipment() != null) {
            for (ItemStack stack : livingEntity.getEquipment().getArmorContents()) {
                if (stack != null) {
                    int stackLevel = stack.getEnchantmentLevel(Enchantment.PROTECTION_EXPLOSIONS);
                    if (stackLevel > level) {
                        level = stackLevel;
                    }
                }
            }
        }

        return level << 1;
    }

    private Collection<Entity> getNearbyEntities() {
        return location.getWorld().getNearbyEntities(location, power, power, power);
    }

    private double distanceTo(Entity entity) {
        return RayUtil.getRayBetween(location, entity.getLocation()).length();
    }

    private double distanceToSquared(Entity entity) {
        return RayUtil.getRayBetween(location, entity.getLocation()).lengthSquared();
    }

    private Vector distanceToHead(LivingEntity entity) {
        return RayUtil.getRayBetween(entity.getEyeLocation(), location);
    }

    ///////////////////////////////////////
    // Visualize
    private void playOutSoundAndParticles() {
        ThreadLocalRandom random = ThreadLocalRandom.current();
        world.playSound(location, Sound.ENTITY_GENERIC_EXPLODE, 4,
                (1.0F + (random.nextFloat() - random.nextFloat()) * 0.2F) * 0.7F);

        if (power >= 2.0F && breakBlocks) {
            // send huge explosion
            world.spigot().playEffect(location, Effect.EXPLOSION_HUGE);
        } else {
            // send large explosion
            world.spigot().playEffect(location, Effect.EXPLOSION_LARGE);
        }
    }

    private void playOutExplosion(GlowPlayer player, Iterable<Block> blocks) {
        Collection<Record> records = new ArrayList<>();

        for (Block block : blocks) {
            Location blockLocation = block.getLocation();
            byte x = (byte) (blockLocation.getBlockX() - (int) this.location.getX());
            byte y = (byte) (blockLocation.getBlockY() - (int) this.location.getY());
            byte z = (byte) (blockLocation.getBlockZ() - (int) this.location.getZ());
            records.add(new Record(x, y, z));
        }

        Vector velocity = player.getVelocity();
        ExplosionMessage message = new ExplosionMessage(
                (float) location.getX(), (float) location.getY(), (float) location.getZ(), power,
                (float) velocity.getX(), (float) velocity.getY(), (float) velocity.getZ(), records);

        player.getSession().send(message);
    }

    private Collection<GlowPlayer> collectPlayersInRadius(int radius) {
        int radiusSquared = radius * radius;
        return world.getRawPlayers().stream()
                .filter(player -> player.getLocation().distanceSquared(location) <= radiusSquared)
                .collect(Collectors.toList());
    }
}

{'fraction_non_alphanumeric': 0.10886048526863085, 'fraction_numerical': 0.005921432697862507, 'mean_word_length': 2.0614568365203936, 'pattern_counts': {'":': 0, '<': 57, '<?xml version=': 0, '>': 61, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6975715', 'n_tokens_mistral': 7842, 'n_tokens_neox': 7316, 'n_words': 1906}