Skip to content

Commit

Permalink
refactor: remove pattern interface
Browse files Browse the repository at this point in the history
It's not needed.
Make it a record and validate the state properly.
Add more validations for Ingredient.
  • Loading branch information
raoulvdberge committed Dec 31, 2024
1 parent 6db0078 commit e7b6412
Show file tree
Hide file tree
Showing 37 changed files with 755 additions and 836 deletions.
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.core.CoreValidations;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.Collections;
import java.util.List;

public class Ingredient {
private final long amount;
private final List<ResourceKey> inputs;

public Ingredient(final long amount, final List<? extends ResourceKey> inputs) {
CoreValidations.validateLargerThanZero(amount, "Amount must be larger than zero");
CoreValidations.validateNotEmpty(inputs, "Inputs cannot be empty");
this.amount = amount;
this.inputs = Collections.unmodifiableList(inputs);
}

public boolean isEmpty() {
return inputs.isEmpty();
this.inputs = List.copyOf(inputs);
}

public int size() {
Expand All @@ -30,6 +28,10 @@ public ResourceKey get(final int index) {
return inputs.get(index);
}

public List<ResourceKey> getAll() {
return inputs;
}

@Override
public String toString() {
return "Ingredient{"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.core.CoreValidations;
import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.List;
import java.util.Set;

import org.apiguardian.api.API;

@API(status = API.Status.STABLE, since = "2.0.0-milestone.4.6")
public interface Pattern {
Set<ResourceKey> getInputResources();

Set<ResourceKey> getOutputResources();

List<Ingredient> getIngredients();

List<ResourceAmount> getOutputs();
public record Pattern(List<Ingredient> ingredients, List<ResourceAmount> outputs) {
public Pattern(final List<Ingredient> ingredients, final List<ResourceAmount> outputs) {
CoreValidations.validateNotEmpty(ingredients, "Ingredients cannot be empty");
CoreValidations.validateNotEmpty(outputs, "Outputs cannot be empty");
this.ingredients = List.copyOf(ingredients);
this.outputs = List.copyOf(outputs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ public PatternBuilder output(final ResourceKey output, final long amount) {
}

public Pattern build() {
return new PatternImpl(ingredients, outputs.toArray(new ResourceAmount[0]));
return new Pattern(ingredients, outputs);
}

public class IngredientBuilder {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.resource.ResourceAmount;
import com.refinedmods.refinedstorage.api.resource.ResourceKey;

import java.util.Collections;
Expand All @@ -13,25 +14,26 @@

public class PatternRepositoryImpl implements PatternRepository {
private final Set<Pattern> patterns = new HashSet<>();
private final Map<ResourceKey, PriorityQueue<PatternHolder>> patternsByOutput = new HashMap<>();
private final Set<Pattern> patternsView = Collections.unmodifiableSet(patterns);
private final Map<ResourceKey, PriorityQueue<PatternHolder>> patternsByOutput = new HashMap<>();
private final Set<ResourceKey> outputs = new HashSet<>();
private final Set<ResourceKey> outputsView = Collections.unmodifiableSet(outputs);

@Override
public void add(final Pattern pattern, final int priority) {
patterns.add(pattern);
outputs.addAll(pattern.getOutputResources());
for (final ResourceKey output : pattern.getOutputResources()) {
patternsByOutput.computeIfAbsent(output, k -> new PriorityQueue<>(
pattern.outputs().forEach(output -> outputs.add(output.resource()));
for (final ResourceAmount output : pattern.outputs()) {
patternsByOutput.computeIfAbsent(output.resource(), k -> new PriorityQueue<>(
Comparator.comparingInt(PatternHolder::priority).reversed()
)).add(new PatternHolder(pattern, priority));
}
}

@Override
public void update(final Pattern pattern, final int priority) {
for (final ResourceKey output : pattern.getOutputResources()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output);
for (final ResourceAmount output : pattern.outputs()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output.resource());
if (holders == null) {
continue;
}
Expand All @@ -43,19 +45,20 @@ public void update(final Pattern pattern, final int priority) {
@Override
public void remove(final Pattern pattern) {
patterns.remove(pattern);
for (final ResourceKey output : pattern.getOutputResources()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output);
for (final ResourceAmount output : pattern.outputs()) {
final PriorityQueue<PatternHolder> holders = patternsByOutput.get(output.resource());
if (holders == null) {
continue;
}
holders.removeIf(holder -> holder.pattern.equals(pattern));
if (holders.isEmpty()) {
patternsByOutput.remove(output);
patternsByOutput.remove(output.resource());
}
final boolean noOtherPatternHasThisOutput = patterns.stream()
.noneMatch(otherPattern -> otherPattern.getOutputResources().contains(output));
.noneMatch(otherPattern -> otherPattern.outputs().stream()
.anyMatch(o -> o.resource().equals(output.resource())));
if (noOtherPatternHasThisOutput) {
outputs.remove(output);
outputs.remove(output.resource());
}
}
}
Expand All @@ -71,7 +74,7 @@ public List<Pattern> getByOutput(final ResourceKey output) {

@Override
public Set<ResourceKey> getOutputs() {
return outputs;
return outputsView;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public long getTotal() {
}

static Amount of(final Pattern pattern, final ResourceKey resource, final long requestedAmount) {
final long amountPerIteration = pattern.getOutputs()
final long amountPerIteration = pattern.outputs()
.stream()
.filter(output -> output.resource().equals(resource))
.mapToLong(ResourceAmount::amount)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,7 @@ void extractFromStorage(final ResourceKey resource, final long amount) {
}

void addOutputsToInternalStorage(final Pattern pattern, final Amount amount) {
pattern.getOutputs().forEach(
output -> addOutputToInternalStorage(amount, output)
);
pattern.outputs().forEach(output -> addOutputToInternalStorage(amount, output));
}

private void addOutputToInternalStorage(final Amount amount, final ResourceAmount output) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,7 @@ CalculationResult calculate() {
throw new PatternCycleDetectedException(pattern);
}
CalculationResult result = CalculationResult.SUCCESS;
for (final Ingredient ingredient : pattern.getIngredients()) {
if (ingredient.isEmpty()) {
continue;
}
for (final Ingredient ingredient : pattern.ingredients()) {
final IngredientState ingredientState = new IngredientState(ingredient, craftingState);
final CalculationResult ingredientResult = calculateIngredient(ingredientState);
if (ingredientResult == CalculationResult.MISSING_RESOURCES) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ private MutablePreviewItem get(final ResourceKey resource) {
}

public PreviewBuilder withPatternWithCycle(final Pattern pattern) {
this.outputsOfPatternWithCycle = pattern.getOutputs();
this.outputsOfPatternWithCycle = pattern.outputs();
return this;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public static Preview calculatePreview(final CraftingCalculator calculator,
try {
calculator.calculate(resource, amount, listener);
} catch (final PatternCycleDetectedException e) {
return new Preview(PreviewType.CYCLE_DETECTED, Collections.emptyList(), e.getPattern().getOutputs());
return new Preview(PreviewType.CYCLE_DETECTED, Collections.emptyList(), e.getPattern().outputs());
} catch (final NumberOverflowDuringCalculationException e) {
return new Preview(PreviewType.OVERFLOW, Collections.emptyList(), Collections.emptyList());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package com.refinedmods.refinedstorage.api.autocrafting;

import com.refinedmods.refinedstorage.api.resource.ResourceKey;

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

import org.assertj.core.api.ThrowableAssert;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.A;
import static com.refinedmods.refinedstorage.api.autocrafting.ResourceFixtures.B;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

class IngredientTest {
@Test
void testIngredient() {
// Act
final Ingredient sut = new Ingredient(1, List.of(A, B));

// Assert
assertThat(sut.size()).isEqualTo(2);
assertThat(sut.getAmount()).isEqualTo(1);
assertThat(sut.get(0)).isEqualTo(A);
assertThat(sut.get(1)).isEqualTo(B);
assertThat(sut.getAll()).containsExactly(A, B);
}

@Test
void shouldCopyIngredients() {
// Arrange
final List<ResourceKey> outputs = new ArrayList<>();
outputs.add(A);

// Act
final Ingredient sut = new Ingredient(1, outputs);
outputs.add(B);

// Assert
assertThat(sut.size()).isEqualTo(1);
assertThat(sut.get(0)).isEqualTo(A);
assertThat(sut.getAll()).containsExactly(A);
}

@Test
void shouldNotBeAbleToModifyIngredients() {
// Arrange
final Ingredient sut = new Ingredient(1, List.of(A));
final List<ResourceKey> items = sut.getAll();

// Act
final ThrowableAssert.ThrowingCallable action = () -> items.add(B);

// Assert
assertThatThrownBy(action).isInstanceOf(UnsupportedOperationException.class);
}

@ParameterizedTest
@ValueSource(longs = {0, -1})
void shouldNotCreateIngredientWithInvalidAmount(final long amount) {
// Arrange
final ThrowableAssert.ThrowingCallable action = () -> new Ingredient(amount, List.of(A, B));

// Assert
assertThatThrownBy(action).isInstanceOf(IllegalArgumentException.class);
}

@Test
void shouldNotCreateIngredientWithEmptyInputs() {
// Act
final ThrowableAssert.ThrowingCallable action = () -> new Ingredient(1, List.of());

// Assert
assertThatThrownBy(action).isInstanceOf(IllegalArgumentException.class);
}
}

This file was deleted.

Loading

0 comments on commit e7b6412

Please sign in to comment.