Search in sources :

Example 1 with PhaseContext

use of org.spongepowered.common.event.tracking.PhaseContext in project SpongeCommon by SpongePowered.

the class PlayerPhase method unwind.

@SuppressWarnings("unchecked")
public void unwind(IPhaseState<?> state, PhaseContext<?> phaseContext) {
    // Since currently all we have is PLAYER_LOGOUT, don't care about
    // states.
    final Player player = phaseContext.getSource(Player.class).orElseThrow(TrackingUtil.throwWithContext("Expected to be processing a player leaving, but we're not!", phaseContext));
    try (CauseStackManager.StackFrame frame = Sponge.getCauseStackManager().pushCauseFrame()) {
        Sponge.getCauseStackManager().pushCause(player);
        Sponge.getCauseStackManager().addContext(EventContextKeys.SPAWN_TYPE, SpawnTypes.DISPENSE);
        phaseContext.getCapturedItemsSupplier().acceptAndClearIfNotEmpty(items -> {
            final ArrayList<Entity> entities = new ArrayList<>();
            for (EntityItem item : items) {
                entities.add(EntityUtil.fromNative(item));
            }
            final DropItemEvent.Dispense dispense = SpongeEventFactory.createDropItemEventDispense(Sponge.getCauseStackManager().getCurrentCause(), entities);
            SpongeImpl.postEvent(dispense);
            if (!dispense.isCancelled()) {
                for (Entity entity : dispense.getEntities()) {
                    EntityUtil.getMixinWorld(entity).forceSpawnEntity(entity);
                }
            }
        });
        Sponge.getCauseStackManager().addContext(EventContextKeys.SPAWN_TYPE, SpawnTypes.DROPPED_ITEM);
        phaseContext.getCapturedItemStackSupplier().acceptAndClearIfNotEmpty(items -> {
            final List<EntityItem> drops = items.stream().map(drop -> drop.create(EntityUtil.getMinecraftWorld(player))).collect(Collectors.toList());
            final List<Entity> entities = (List<Entity>) (List<?>) drops;
            if (!entities.isEmpty()) {
                DropItemEvent.Custom event = SpongeEventFactory.createDropItemEventCustom(Sponge.getCauseStackManager().getCurrentCause(), entities);
                SpongeImpl.postEvent(event);
                if (!event.isCancelled()) {
                    for (Entity droppedItem : event.getEntities()) {
                        EntityUtil.getMixinWorld(droppedItem).forceSpawnEntity(droppedItem);
                    }
                }
            }
        });
        phaseContext.getCapturedBlockSupplier().acceptAndClearIfNotEmpty(blocks -> TrackingUtil.processBlockCaptures(blocks, state, phaseContext));
    }
}
Also used : EntityItem(net.minecraft.entity.item.EntityItem) SpongeImpl(org.spongepowered.common.SpongeImpl) IPhaseState(org.spongepowered.common.event.tracking.IPhaseState) EventContextKeys(org.spongepowered.api.event.cause.EventContextKeys) SpongeEventFactory(org.spongepowered.api.event.SpongeEventFactory) Sponge(org.spongepowered.api.Sponge) DropItemEvent(org.spongepowered.api.event.item.inventory.DropItemEvent) Entity(org.spongepowered.api.entity.Entity) Collectors(java.util.stream.Collectors) EntityUtil(org.spongepowered.common.entity.EntityUtil) ArrayList(java.util.ArrayList) SpawnTypes(org.spongepowered.api.event.cause.entity.spawn.SpawnTypes) List(java.util.List) TrackingUtil(org.spongepowered.common.event.tracking.TrackingUtil) GeneralizedContext(org.spongepowered.common.event.tracking.context.GeneralizedContext) PhaseContext(org.spongepowered.common.event.tracking.PhaseContext) Player(org.spongepowered.api.entity.living.player.Player) CauseStackManager(org.spongepowered.api.event.CauseStackManager) Entity(org.spongepowered.api.entity.Entity) DropItemEvent(org.spongepowered.api.event.item.inventory.DropItemEvent) Player(org.spongepowered.api.entity.living.player.Player) ArrayList(java.util.ArrayList) CauseStackManager(org.spongepowered.api.event.CauseStackManager) ArrayList(java.util.ArrayList) List(java.util.List) EntityItem(net.minecraft.entity.item.EntityItem)

Example 2 with PhaseContext

use of org.spongepowered.common.event.tracking.PhaseContext in project SpongeCommon by SpongePowered.

the class ContainerBasedTransaction method generateEvent.

@Override
public Optional<ClickContainerEvent> generateEvent(final PhaseContext<@NonNull ?> context, @Nullable final GameTransaction<@NonNull ?> parent, final ImmutableList<GameTransaction<ClickContainerEvent>> gameTransactions, final Cause currentCause) {
    final ImmutableList<ContainerBasedTransaction> containerBasedTransactions = gameTransactions.stream().filter(tx -> tx instanceof ContainerBasedTransaction).map(tx -> (ContainerBasedTransaction) tx).filter(tx -> !tx.used).collect(ImmutableList.toImmutableList());
    if (containerBasedTransactions.stream().map(c -> c.isContainerEventAllowed(context)).filter(b -> !b).findAny().orElse(false)) {
        SpongeCommon.logger().warn("No event will be fired for existing ContainerBasedTransactions: {}", containerBasedTransactions.size());
        return Optional.empty();
    }
    if (!((TrackedContainerBridge) this.menu).bridge$capturePossible()) {
    // if (ContainerBasedTransaction.containersFailedCapture.add(this.menu.getClass())) {
    // SpongeCommon.logger()
    // .warn("Changes in modded Container were not captured. Inventory events will not fire for this. Container: " + this.menu.getClass());
    // }
    }
    final List<Entity> entities = containerBasedTransactions.stream().map(ContainerBasedTransaction::getEntitiesSpawned).flatMap(List::stream).collect(Collectors.toList());
    final List<SlotTransaction> slotTransactions = containerBasedTransactions.stream().map(ContainerBasedTransaction::getSlotTransactions).flatMap(List::stream).collect(Collectors.toList());
    if (this.craftingInventory != null) {
        // Event with Preview transaction on crafting inventory?
        Slot slot = this.craftingInventory.result();
        @Nullable final SlotTransaction preview = this.findPreviewTransaction(this.craftingInventory.result(), slotTransactions);
        final ItemStackSnapshot previewItem = ItemStackUtil.snapshotOf(this.craftingInventory.peek());
        if (preview != null) {
            slot = preview.slot();
            // Check if preview transaction is correct
            if (!preview.defaultReplacement().equals(previewItem)) {
                slotTransactions.remove(preview);
                slotTransactions.add(new SlotTransaction(slot, preview.original(), previewItem));
            }
        } else if (!previewItem.isEmpty()) {
            slotTransactions.add(new SlotTransaction(slot, previewItem, previewItem));
        }
    }
    for (final ContainerBasedTransaction transaction : containerBasedTransactions) {
        transaction.used = true;
    }
    final Optional<ClickContainerEvent> event = containerBasedTransactions.stream().map(t -> t.createInventoryEvent(slotTransactions, entities, context, currentCause)).filter(Optional::isPresent).map(Optional::get).findFirst();
    if (!event.isPresent() && !slotTransactions.isEmpty()) {
        SpongeCommon.logger().warn("Logged slot transactions without event! {} {}", gameTransactions.size(), this.menu.getClass().getName(), new Exception(""));
        for (final SlotTransaction slotTransaction : slotTransactions) {
            SpongeCommon.logger().warn(slotTransaction);
        }
    }
    return event;
}
Also used : NonNull(org.checkerframework.checker.nullness.qual.NonNull) ClientboundContainerSetSlotPacket(net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket) AbstractContainerMenu(net.minecraft.world.inventory.AbstractContainerMenu) SpawnEntityTransaction(org.spongepowered.common.event.tracking.context.transaction.world.SpawnEntityTransaction) ItemStackSnapshot(org.spongepowered.api.item.inventory.ItemStackSnapshot) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) ClickContainerEvent(org.spongepowered.api.event.item.inventory.container.ClickContainerEvent) TransactionTypes(org.spongepowered.common.event.tracking.context.transaction.type.TransactionTypes) ArrayList(java.util.ArrayList) MonotonicNonNull(org.checkerframework.checker.nullness.qual.MonotonicNonNull) ServerPlayer(net.minecraft.server.level.ServerPlayer) SlotTransaction(org.spongepowered.api.item.inventory.transaction.SlotTransaction) TrackedContainerBridge(org.spongepowered.common.bridge.world.inventory.container.TrackedContainerBridge) ImmutableList(com.google.common.collect.ImmutableList) ItemStackUtil(org.spongepowered.common.item.util.ItemStackUtil) CraftItemEvent(org.spongepowered.api.event.item.inventory.CraftItemEvent) BiConsumer(java.util.function.BiConsumer) CauseStackManager(org.spongepowered.api.event.CauseStackManager) LinkedList(java.util.LinkedList) RecipeType(net.minecraft.world.item.crafting.RecipeType) Nullable(org.checkerframework.checker.nullness.qual.Nullable) SpongeEventFactory(org.spongepowered.api.event.SpongeEventFactory) Set(java.util.Set) SpawnEntityEvent(org.spongepowered.api.event.entity.SpawnEntityEvent) ContainerUtil(org.spongepowered.common.inventory.util.ContainerUtil) Slot(org.spongepowered.api.item.inventory.Slot) SpongeCommon(org.spongepowered.common.SpongeCommon) PhaseTracker(org.spongepowered.common.event.tracking.PhaseTracker) CraftingOutput(org.spongepowered.api.item.inventory.crafting.CraftingOutput) Entity(org.spongepowered.api.entity.Entity) Collectors(java.util.stream.Collectors) Player(net.minecraft.world.entity.player.Player) Cause(org.spongepowered.api.event.Cause) EntityUtil(org.spongepowered.common.entity.EntityUtil) List(java.util.List) CraftingContainer(net.minecraft.world.inventory.CraftingContainer) GameTransaction(org.spongepowered.common.event.tracking.context.transaction.GameTransaction) Container(org.spongepowered.api.item.inventory.Container) ReferenceOpenHashSet(it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet) PhaseContext(org.spongepowered.common.event.tracking.PhaseContext) CraftingInventory(org.spongepowered.api.item.inventory.crafting.CraftingInventory) CraftingRecipe(org.spongepowered.api.item.recipe.crafting.CraftingRecipe) PacketPhaseUtil(org.spongepowered.common.event.tracking.phase.packet.PacketPhaseUtil) Optional(java.util.Optional) ItemStack(net.minecraft.world.item.ItemStack) Collections(java.util.Collections) Entity(org.spongepowered.api.entity.Entity) Optional(java.util.Optional) ClickContainerEvent(org.spongepowered.api.event.item.inventory.container.ClickContainerEvent) SlotTransaction(org.spongepowered.api.item.inventory.transaction.SlotTransaction) Slot(org.spongepowered.api.item.inventory.Slot) ItemStackSnapshot(org.spongepowered.api.item.inventory.ItemStackSnapshot) Nullable(org.checkerframework.checker.nullness.qual.Nullable)

Example 3 with PhaseContext

use of org.spongepowered.common.event.tracking.PhaseContext in project SpongeCommon by SpongePowered.

the class TransactionalCaptureSupplier method generateEventForTransaction.

@SuppressWarnings("unchecked")
private static <E extends Event & Cancellable> void generateEventForTransaction(@NonNull final GameTransaction<E> pointer, @Nullable final GameTransaction<@NonNull ?> parent, final PhaseContext<@NonNull ?> context, final ImmutableList.Builder<EventByTransaction<@NonNull ?>> builder, final ImmutableList<GameTransaction<E>> transactions, final ImmutableMultimap.Builder<TransactionType, ? extends Event> transactionPostEventBuilder) {
    final Optional<BiConsumer<PhaseContext<@NonNull ?>, CauseStackManager.StackFrame>> frameMutator = pointer.getFrameMutator(parent);
    final PhaseTracker instance = PhaseTracker.getInstance();
    try (final CauseStackManager.StackFrame frame = frameMutator.map(mutator -> {
        final CauseStackManager.StackFrame transactionFrame = instance.pushCauseFrame();
        mutator.accept(context, transactionFrame);
        return transactionFrame;
    }).orElseGet(instance::pushCauseFrame)) {
        final Optional<E> generatedEvent = pointer.generateEvent(context, parent, transactions, instance.currentCause());
        generatedEvent.ifPresent(e -> {
            final EventByTransaction<E> element = new EventByTransaction<>(e, transactions, parent, pointer);
            builder.add(element);
            ((ImmutableMultimap.Builder) transactionPostEventBuilder).put(pointer.getTransactionType(), e);
        });
        for (final GameTransaction<E> transaction : transactions) {
            if (transaction.sideEffects == null || transaction.sideEffects.isEmpty()) {
                continue;
            }
            generatedEvent.ifPresent(frame::pushCause);
            for (final ResultingTransactionBySideEffect sideEffect : transaction.sideEffects) {
                if (sideEffect.head == null) {
                    continue;
                }
                builder.addAll(TransactionalCaptureSupplier.batchTransactions(sideEffect.head, pointer, context, transactionPostEventBuilder));
            }
        }
    }
}
Also used : NonNull(org.checkerframework.checker.nullness.qual.NonNull) Iterator(java.util.Iterator) Event(org.spongepowered.api.event.Event) Spliterators(java.util.Spliterators) Sponge(org.spongepowered.api.Sponge) SpongeCommon(org.spongepowered.common.SpongeCommon) PhaseTracker(org.spongepowered.common.event.tracking.PhaseTracker) MonotonicNonNull(org.checkerframework.checker.nullness.qual.MonotonicNonNull) PrepareBlockDrops(org.spongepowered.common.event.tracking.context.transaction.effect.PrepareBlockDrops) Objects(java.util.Objects) ImmutableList(com.google.common.collect.ImmutableList) Cancellable(org.spongepowered.api.event.Cancellable) ICaptureSupplier(org.spongepowered.common.event.tracking.context.ICaptureSupplier) PhaseContext(org.spongepowered.common.event.tracking.PhaseContext) StringJoiner(java.util.StringJoiner) BiConsumer(java.util.function.BiConsumer) Optional(java.util.Optional) TransactionType(org.spongepowered.common.event.tracking.context.transaction.type.TransactionType) ImmutableMultimap(com.google.common.collect.ImmutableMultimap) CauseStackManager(org.spongepowered.api.event.CauseStackManager) Collections(java.util.Collections) Spliterator(java.util.Spliterator) Nullable(org.checkerframework.checker.nullness.qual.Nullable) PhaseTracker(org.spongepowered.common.event.tracking.PhaseTracker) CauseStackManager(org.spongepowered.api.event.CauseStackManager) BiConsumer(java.util.function.BiConsumer)

Example 4 with PhaseContext

use of org.spongepowered.common.event.tracking.PhaseContext in project SpongeCommon by SpongePowered.

the class BlockTransactionType method consumeEventsAndMarker.

@Override
protected void consumeEventsAndMarker(PhaseContext<@NonNull ?> context, final Collection<? extends ChangeBlockEvent.All> changeBlockEvents) {
    final Multimap<ResourceKey, ChangeBlockEvent.All> eventsByWorld = LinkedListMultimap.create();
    changeBlockEvents.forEach(event -> eventsByWorld.put(event.world().key(), event));
    eventsByWorld.asMap().forEach((key, events) -> {
        final Optional<ServerWorld> serverWorld = ((SpongeServer) SpongeCommon.server()).worldManager().world(key);
        if (!serverWorld.isPresent()) {
            return;
        }
        final ListMultimap<BlockPos, SpongeBlockSnapshot> positions = LinkedListMultimap.create();
        // Gather transactions that were valid
        events.stream().filter(event -> !event.isCancelled()).flatMap(event -> event.transactions().stream()).filter(BlockTransaction::isValid).forEach(transactions -> {
            // Then "put" the most recent transactions such that we have a complete rebuild of
            // each position according to what originally existed and then
            // the ultimate final block on that position
            final SpongeBlockSnapshot original = (SpongeBlockSnapshot) transactions.original();
            positions.put(original.getBlockPos(), original);
            positions.put(original.getBlockPos(), (SpongeBlockSnapshot) transactions.finalReplacement());
        });
        // just return.
        if (positions.isEmpty()) {
            return;
        }
        final ImmutableList<BlockTransactionReceipt> transactions = positions.asMap().values().stream().map(spongeBlockSnapshots -> {
            final List<SpongeBlockSnapshot> snapshots = new ArrayList<>(spongeBlockSnapshots);
            if (snapshots.isEmpty() || snapshots.size() < 2) {
                // Error case
                return Optional.<BlockTransactionReceipt>empty();
            }
            final SpongeBlockSnapshot original = snapshots.get(0);
            final SpongeBlockSnapshot result = snapshots.get(snapshots.size() - 1);
            final Operation operation = context.getBlockOperation(original, result);
            final BlockTransactionReceipt eventTransaction = new BlockTransactionReceipt(original, result, operation);
            context.postBlockTransactionApplication(original.blockChange, eventTransaction);
            return Optional.of(eventTransaction);
        }).filter(Optional::isPresent).map(Optional::get).collect(ImmutableList.toImmutableList());
        final Cause cause = PhaseTracker.getInstance().currentCause();
        SpongeCommon.post(SpongeEventFactory.createChangeBlockEventPost(cause, transactions, serverWorld.get()));
    });
}
Also used : LinkedListMultimap(com.google.common.collect.LinkedListMultimap) NonNull(org.checkerframework.checker.nullness.qual.NonNull) ServerWorld(org.spongepowered.api.world.server.ServerWorld) ListMultimap(com.google.common.collect.ListMultimap) ChangeBlockEvent(org.spongepowered.api.event.block.ChangeBlockEvent) SpongeServer(org.spongepowered.common.SpongeServer) SpongeEventFactory(org.spongepowered.api.event.SpongeEventFactory) Collection(java.util.Collection) SpongeBlockSnapshot(org.spongepowered.common.block.SpongeBlockSnapshot) SpongeCommon(org.spongepowered.common.SpongeCommon) PhaseTracker(org.spongepowered.common.event.tracking.PhaseTracker) Multimap(com.google.common.collect.Multimap) Cause(org.spongepowered.api.event.Cause) ArrayList(java.util.ArrayList) BlockTransactionReceipt(org.spongepowered.api.block.transaction.BlockTransactionReceipt) List(java.util.List) ImmutableList(com.google.common.collect.ImmutableList) BlockPos(net.minecraft.core.BlockPos) PhaseContext(org.spongepowered.common.event.tracking.PhaseContext) BlockTransaction(org.spongepowered.api.block.transaction.BlockTransaction) ResourceKey(org.spongepowered.api.ResourceKey) Optional(java.util.Optional) Operation(org.spongepowered.api.block.transaction.Operation) BlockTransactionReceipt(org.spongepowered.api.block.transaction.BlockTransactionReceipt) Optional(java.util.Optional) Operation(org.spongepowered.api.block.transaction.Operation) ResourceKey(org.spongepowered.api.ResourceKey) ServerWorld(org.spongepowered.api.world.server.ServerWorld) SpongeBlockSnapshot(org.spongepowered.common.block.SpongeBlockSnapshot) Cause(org.spongepowered.api.event.Cause) BlockPos(net.minecraft.core.BlockPos) ArrayList(java.util.ArrayList) List(java.util.List) ImmutableList(com.google.common.collect.ImmutableList)

Example 5 with PhaseContext

use of org.spongepowered.common.event.tracking.PhaseContext in project SpongeCommon by SpongePowered.

the class InventoryBasedTransaction method generateEvent.

@Override
public Optional<ChangeInventoryEvent> generateEvent(final PhaseContext<@NonNull ?> context, @Nullable final GameTransaction<@NonNull ?> parent, final ImmutableList<GameTransaction<ChangeInventoryEvent>> gameTransactions, final Cause currentCause) {
    final ImmutableList<InventoryBasedTransaction> containerBasedTransactions = gameTransactions.stream().filter(tx -> tx instanceof InventoryBasedTransaction).map(tx -> (InventoryBasedTransaction) tx).filter(tx -> !tx.used).collect(ImmutableList.toImmutableList());
    final List<SlotTransaction> slotTransactions = containerBasedTransactions.stream().map(InventoryBasedTransaction::getSlotTransactions).flatMap(List::stream).collect(Collectors.toList());
    for (InventoryBasedTransaction transaction : containerBasedTransactions) {
        transaction.used = true;
    }
    final List<Entity> entities = containerBasedTransactions.stream().map(InventoryBasedTransaction::getEntitiesSpawned).flatMap(List::stream).collect(Collectors.toList());
    // TODO on pickup grouping does not work?
    final Map<Slot, List<SlotTransaction>> collected = slotTransactions.stream().collect(Collectors.groupingBy(SlotTransaction::slot));
    slotTransactions.clear();
    collected.values().forEach(list -> {
        final SlotTransaction first = list.get(0);
        if (list.size() > 1) {
            final ItemStackSnapshot last = list.get(list.size() - 1).defaultReplacement();
            slotTransactions.add(new SlotTransaction(first.slot(), first.original(), last));
        } else {
            slotTransactions.add(first);
        }
    });
    return containerBasedTransactions.stream().map(t -> t.createInventoryEvent(slotTransactions, entities, context, currentCause)).filter(Optional::isPresent).map(Optional::get).findFirst();
}
Also used : Inventory(org.spongepowered.api.item.inventory.Inventory) NonNull(org.checkerframework.checker.nullness.qual.NonNull) SpawnEntityTransaction(org.spongepowered.common.event.tracking.context.transaction.world.SpawnEntityTransaction) ItemStackSnapshot(org.spongepowered.api.item.inventory.ItemStackSnapshot) PrettyPrinter(org.spongepowered.common.util.PrettyPrinter) TransactionTypes(org.spongepowered.common.event.tracking.context.transaction.type.TransactionTypes) ArrayList(java.util.ArrayList) MonotonicNonNull(org.checkerframework.checker.nullness.qual.MonotonicNonNull) SlotTransaction(org.spongepowered.api.item.inventory.transaction.SlotTransaction) ImmutableList(com.google.common.collect.ImmutableList) Map(java.util.Map) BiConsumer(java.util.function.BiConsumer) CauseStackManager(org.spongepowered.api.event.CauseStackManager) LinkedList(java.util.LinkedList) Nullable(org.checkerframework.checker.nullness.qual.Nullable) SpawnEntityEvent(org.spongepowered.api.event.entity.SpawnEntityEvent) Slot(org.spongepowered.api.item.inventory.Slot) Entity(org.spongepowered.api.entity.Entity) Collectors(java.util.stream.Collectors) Player(net.minecraft.world.entity.player.Player) Cause(org.spongepowered.api.event.Cause) EntityUtil(org.spongepowered.common.entity.EntityUtil) List(java.util.List) GameTransaction(org.spongepowered.common.event.tracking.context.transaction.GameTransaction) PhaseContext(org.spongepowered.common.event.tracking.PhaseContext) ChangeInventoryEvent(org.spongepowered.api.event.item.inventory.ChangeInventoryEvent) PacketPhaseUtil(org.spongepowered.common.event.tracking.phase.packet.PacketPhaseUtil) Optional(java.util.Optional) Collections(java.util.Collections) Entity(org.spongepowered.api.entity.Entity) Optional(java.util.Optional) Slot(org.spongepowered.api.item.inventory.Slot) ItemStackSnapshot(org.spongepowered.api.item.inventory.ItemStackSnapshot) ArrayList(java.util.ArrayList) ImmutableList(com.google.common.collect.ImmutableList) LinkedList(java.util.LinkedList) List(java.util.List) SlotTransaction(org.spongepowered.api.item.inventory.transaction.SlotTransaction)

Aggregations

PhaseContext (org.spongepowered.common.event.tracking.PhaseContext)7 ArrayList (java.util.ArrayList)6 List (java.util.List)6 NonNull (org.checkerframework.checker.nullness.qual.NonNull)6 ImmutableList (com.google.common.collect.ImmutableList)5 Optional (java.util.Optional)5 Cause (org.spongepowered.api.event.Cause)5 CauseStackManager (org.spongepowered.api.event.CauseStackManager)5 SpongeEventFactory (org.spongepowered.api.event.SpongeEventFactory)5 SpongeCommon (org.spongepowered.common.SpongeCommon)5 Collectors (java.util.stream.Collectors)4 Nullable (org.checkerframework.checker.nullness.qual.Nullable)4 PhaseTracker (org.spongepowered.common.event.tracking.PhaseTracker)4 Collections (java.util.Collections)3 BiConsumer (java.util.function.BiConsumer)3 Player (net.minecraft.world.entity.player.Player)3 ServerWorld (org.spongepowered.api.world.server.ServerWorld)3 LinkedListMultimap (com.google.common.collect.LinkedListMultimap)2 ListMultimap (com.google.common.collect.ListMultimap)2 LinkedList (java.util.LinkedList)2