Skip to content

Commit

Permalink
[BE-REFACTOR] 포켓몬 정보 MongoDB 리팩토링 (#336)
Browse files Browse the repository at this point in the history
  • Loading branch information
dwax1324 authored Oct 11, 2024
1 parent 408d042 commit 1b52135
Show file tree
Hide file tree
Showing 42 changed files with 1,458 additions and 884 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ private double getTotalMultiplier(
List<Type> types = new ArrayList<>();
if (!rivalInMemoryPokemon.firstType().isEmpty()) {
types.add(Type.findByEngName(rivalInMemoryPokemon.firstType())
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND)));
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_NOT_FOUND)));
}
if (!rivalInMemoryPokemon.secondType().isEmpty()) {
types.add(Type.findByEngName(rivalInMemoryPokemon.secondType())
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND)));
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_NOT_FOUND)));
}
double typeMatchingMultiplier = getTypeMatchingMultiplier(moveType, types);
double sameTypeBonusMultiplier = getSameTypeAttackBonusMultiplier(moveType, myInMemoryPokemon);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ private String regularizeEmptyField(String field) {

private BattleMove createMove(List<String> fields) {
Type moveType = Type.findByName(fields.get(4))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_NOT_FOUND));
MoveCategory moveCategory = MoveCategory.findByEngName(fields.get(6))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.MOVE_CATEGORY_NOT_FOUND));

Expand All @@ -100,9 +100,9 @@ private BattleMove createMove(List<String> fields) {

private TypeMatching createTypeMatching(List<String> fields) {
Type fromType = Type.findByEngName(fields.get(0))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_NOT_FOUND));
Type toType = Type.findByEngName(fields.get(1))
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.POKEMON_TYPE_NOT_FOUND));
.orElseThrow(() -> new GlobalCustomException(ErrorMessage.TYPE_NOT_FOUND));
double result = convertToDouble(fields.get(2));

return new TypeMatching(fromType, toType, result);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
package com.pokerogue.helper.data;

import static java.lang.Character.isDigit;
import static java.lang.Character.isLowerCase;

import com.pokerogue.helper.global.exception.ErrorMessage;
import com.pokerogue.helper.global.exception.GlobalCustomException;
import com.pokerogue.helper.pokemon.data.Pokemon;
import com.pokerogue.helper.type.data.Type;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
import java.util.stream.Stream;
import org.apache.logging.log4j.util.Strings;

class PokemonValidator {

private static final int POKEMON_SIZE = 1446;
private static final int MIN_GENERATION = 1;
private static final int MAX_GENERATION = 9;
private static final int MIN_TYPE_COUNT = 1;
private static final int MAX_TYPE_COUNT = 2;
private static final int MIN_ABILITY_COUNT = 2;
private static final int MAX_ABILITY_COUNT = 4;
private static final int MIN_NORMAL_ABILITY_COUNT = 1;
private static final int MAX_NORMAL_ABILITY_COUNT = 2;
private static final int MIN_STAT_VALUE = -1;
private static final int MAX_STAT_VALUE = 10000;
private static final String DELIMITER = "_";
private static final String EMPTY_ABILITY = "none";
private static final IntPredicate isExpectedIdLetter = character -> isLowerCase(character)
|| isDigit(character)
|| isDelimiter(character);

private PokemonValidator() {
}

static void validatePokemonSize(int pokemonCount) {
Predicate<Integer> isTotalPokemonSizeMatch = count -> count == POKEMON_SIZE;
String message = String.format("포켓몬의 총 개수가 다릅니다. 예상값: %d, 현재값: %d", POKEMON_SIZE, pokemonCount);

validate(isTotalPokemonSizeMatch, pokemonCount, ErrorMessage.POKEMON_SIZE_MISMATCH, message);
}

static void validatePokemonIdFormat(List<String> pokemonIds) {
Predicate<String> isExpectedLetter = id -> id.codePoints().allMatch(isExpectedIdLetter);
Predicate<String> isDelimiterSeparated = id -> id.contains(DELIMITER + DELIMITER);
Predicate<String> isDelimiterInPlace = id -> id.startsWith(DELIMITER) || id.endsWith(DELIMITER);

String message = "아이디: %s는 아이디 규칙에 맞지 않습니다.";

for (String id : pokemonIds) {
validate(isExpectedLetter, id, ErrorMessage.POKEMON_ID_UNEXPECTED_LETTER, String.format(message, id));
validate(isDelimiterSeparated, id, ErrorMessage.POKEMON_ID_DELIMITER_IS_SEQUENTIAL,
String.format(message, id));
validate(isDelimiterInPlace, id, ErrorMessage.POKEMON_ID_DELIMITER_MISPLACED, String.format(message, id));
}
}

static void validatePokemonsBaseTotal(List<Pokemon> pokemons) {
Predicate<Pokemon> isBaseTotalCorrect = pokemon -> {
int hp = pokemon.getHp();
int attack = pokemon.getAttack();
int specialAttack = pokemon.getSpecialAttack();
int defense = pokemon.getDefense();
int specialDefense = pokemon.getSpecialDefense();
int speed = pokemon.getSpeed();

int baseTotal = pokemon.getBaseTotal();
int summation = hp + attack + specialAttack + defense + specialDefense + speed;

return baseTotal == summation;
};

for (Pokemon pokemon : pokemons) {
validate(isBaseTotalCorrect, pokemon, ErrorMessage.POKEMON_SIZE_MISMATCH);
}
}

static void validatePokemonsGeneration(List<Pokemon> pokemons) {
Predicate<Integer> isValidGeneration = gen -> isInRange(gen, MIN_GENERATION, MAX_GENERATION);

for (Pokemon pokemon : pokemons) {
int generation = pokemon.getGeneration();
validate(isValidGeneration, generation, ErrorMessage.POKEMON_GENERATION_MISMATCH);
}
}

static void validatePokemonFormChanges(List<Pokemon> pokemons) {
Predicate<Pokemon> isFormChangeable = pokemon -> !pokemon.getFormChanges().isEmpty();
List<Pokemon> formChangeablePokemons = pokemons.stream()
.filter(Pokemon::isCanChangeForm)
.toList();

for (Pokemon fomrChangeablePokemon : formChangeablePokemons) {
validate(isFormChangeable, fomrChangeablePokemon, ErrorMessage.POKEMON_FORM_CHANGE_MISMATCH);
}
}

static void validatePokemonRarity(List<Pokemon> pokemons) {
Predicate<Pokemon> isRarityCountLessOrEqualThanOne = pokemon -> {
boolean legendary = pokemon.isLegendary();
boolean mythical = pokemon.isMythical();
boolean subLegendary = pokemon.isSubLegendary();

long rarityCount = Stream.of(legendary, mythical, subLegendary)
.filter(Boolean::booleanValue)
.count();

return rarityCount <= 1;
};

for (Pokemon pokemon : pokemons) {
validate(isRarityCountLessOrEqualThanOne, pokemon, ErrorMessage.POKEMON_RARITY_COUNT_MISMATCH);
}
}

static void validateNormalAbilityCount(List<Pokemon> pokemons) {
Predicate<Integer> isNormalAbilityCountInRange = normalAbilityCount ->
isInRange(normalAbilityCount, MIN_NORMAL_ABILITY_COUNT, MAX_NORMAL_ABILITY_COUNT);

for (Pokemon pokemon : pokemons) {
int abilityCount = pokemon.getNormalAbilityIds().size();

validate(isNormalAbilityCountInRange, abilityCount, ErrorMessage.POKEMON_NORMAL_ABILITY_COUNT);
}
}

static void validateTotalAbilityCount(List<Pokemon> pokemons) {
Predicate<Pokemon> isTotalAbilityCountInRange = pokemon -> {
List<String> totalAbilityIds = pokemon.getNormalAbilityIds();
totalAbilityIds.add(pokemon.getHiddenAbilityId());
totalAbilityIds.add(pokemon.getPassiveAbilityId());

int totalAbilityCount = totalAbilityIds.stream()
.filter(id -> !id.equals(EMPTY_ABILITY))
.mapToInt(id -> 1)
.sum();

return isInRange(totalAbilityCount, MIN_ABILITY_COUNT, MAX_ABILITY_COUNT);
};

for (Pokemon pokemon : pokemons) {
validate(isTotalAbilityCountInRange, pokemon, ErrorMessage.POKEMON_TOTAL_ABILITY_COUNT);
}
}

static void validateTotalAbilityDuplication(List<Pokemon> pokemons) {
Predicate<Pokemon> isAbilityDisjoint = pokemon -> {
List<String> totalAbilityIds = pokemon.getNormalAbilityIds();
totalAbilityIds.add(pokemon.getHiddenAbilityId());
totalAbilityIds.add(pokemon.getPassiveAbilityId());

Set<String> uniqueIds = new HashSet<>(totalAbilityIds);

return totalAbilityIds.size() == uniqueIds.size();
};

for (Pokemon pokemon : pokemons) {
validate(isAbilityDisjoint, pokemon, ErrorMessage.POKEMON_ABILITY_DUPLICATION);
}
}

static void validateStatValueRange(List<Pokemon> pokemons) {
Predicate<Pokemon> isStatsInRange = pokemon -> {
List<Number> stats = List.of(
pokemon.getDefense(),
pokemon.getAttack(),
pokemon.getSpecialAttack(),
pokemon.getSpecialDefense(),
pokemon.getWeight(),
pokemon.getHeight(),
pokemon.getFriendship(),
pokemon.getBaseExp(),
pokemon.getBaseTotal(),
pokemon.getHp(),
pokemon.getSpeed()
);

return stats.stream()
.map(Number::doubleValue)
.map(Double::intValue)
.allMatch(statValue -> isInRange(statValue, MIN_STAT_VALUE, MAX_STAT_VALUE));
};

for (Pokemon pokemon : pokemons) {
validate(isStatsInRange, pokemon, ErrorMessage.POKEMON_STAT_OUT_OF_RANGE);
}
}

static void validatePassiveAbilityExist(List<Pokemon> pokemons) {
Predicate<Pokemon> isPassiveExist = pokemon -> {
String passiveAbilityId = pokemon.getPassiveAbilityId();
return Strings.isNotBlank(passiveAbilityId) && !EMPTY_ABILITY.equals(passiveAbilityId);
};

for (Pokemon pokemon : pokemons) {
validate(isPassiveExist, pokemon, ErrorMessage.POKEMON_PASSIVE_NOT_FOUND);
}
}

static void validateEmptyHiddenAbilityExists(List<Pokemon> pokemons) {
Predicate<Pokemon> isEmptyHiddenExist = pokemon -> {
String hiddenAbilityId = pokemon.getPassiveAbilityId();
return Strings.isNotBlank(hiddenAbilityId) || EMPTY_ABILITY.equals(hiddenAbilityId);
};

boolean isEmptyExist = pokemons.stream().anyMatch(isEmptyHiddenExist);

validate(Predicate.isEqual(true), isEmptyExist, ErrorMessage.POKEMON_SIZE_MISMATCH);
}

static void validateTypeCount(List<Pokemon> pokemons) {
Predicate<Pokemon> isTypeCountInRange = pokemon -> {
int typeCount = pokemon.getTypes().size();
return isInRange(typeCount, MIN_TYPE_COUNT, MAX_TYPE_COUNT);
};

for (Pokemon pokemon : pokemons) {
validate(isTypeCountInRange, pokemon, ErrorMessage.POKEMON_TYPE_COUNT_OUT_OF_RANGE);
}
}

static void validateTypeDuplication(List<Pokemon> pokemons) {
Predicate<Pokemon> isTypeDisjointed = pokemon -> {
List<Type> types = pokemon.getTypes();
Set<Type> uniqueTypes = new HashSet<>(types);
return types.size() == uniqueTypes.size();
};

for (Pokemon pokemon : pokemons) {
validate(isTypeDisjointed, pokemon, ErrorMessage.POKEMON_TYPE_DUPLICATION);
}
}

static void validateEvolutionFromToIsPokemonId(List<Pokemon> pokemons) {
List<String> pokemonIds = pokemons.stream().map(Pokemon::getId).toList();
List<String> evolutionIds = pokemons.stream()
.flatMap(pokemon -> pokemon.getEvolutions().stream())
.flatMap(evolution -> Stream.of(evolution.getFrom(), evolution.getTo()))
.distinct()
.toList();

boolean containsAll = new HashSet<>(pokemonIds).containsAll(evolutionIds);

validate(Predicate.isEqual(true), containsAll, ErrorMessage.POKEMON_EVOLUTION_ID_MISMATCH);
}


private static <T> void validate(Predicate<T> predicate, T data, ErrorMessage errorMessage) {
if (predicate.test(data)) {
return;
}
throw new GlobalCustomException(errorMessage, data.toString());
}

private static <T> void validate(Predicate<T> predicate, T data, ErrorMessage errorMessage,
String detailedMessage) {
if (predicate.test(data)) {
return;
}
throw new GlobalCustomException(errorMessage, detailedMessage);
}

private static boolean isInRange(int target, int min, int max) {
return target >= min && target <= max;
}

private static boolean isDelimiter(int character) {
return DELIMITER.charAt(0) == character;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,41 @@
@RequiredArgsConstructor
public enum ErrorMessage {

POKEMON_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 포켓몬을 찾지 못했습니다."),
POKEMON_ABILITY_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 특성을 찾지 못했습니다."),
POKEMON_TYPE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 타입을 찾지 못했습니다."),
ARTICLE_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 꿀팁을 찾지 못했습니다."),
MOVE_TARGET_NOT_FOUND(HttpStatus.NOT_FOUND, "기술 타켓을 찾지 못했습니다."),
MOVE_FLAG_NOT_FOUND(HttpStatus.NOT_FOUND, "기술 플래그를 찾지 못했습니다."),
WEATHER_NOT_FOUND(HttpStatus.NOT_FOUND, "id에 해당하는 날씨를 찾지 못했습니다."),
EVOLUTION_NOT_FOUND(HttpStatus.NOT_FOUND, "해당하는 진화 정보를 찾지 못했습니다."),
POKEMON_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 포켓몬을 찾지 못했습니다."),
POKEMON_ABILITY_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 특성을 찾지 못했습니다."),
TYPE_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 타입을 찾지 못했습니다."),
ARTICLE_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 꿀팁을 찾지 못했습니다."),
MOVE_TARGET_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "기술 타켓을 찾지 못했습니다."),
MOVE_FLAG_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "기술 플래그를 찾지 못했습니다."),
WEATHER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "id에 해당하는 날씨를 찾지 못했습니다."),
EVOLUTION_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 진화 정보를 찾지 못했습니다."),
ITEM_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 아이템을 찾지 못했습니다."),

TIER_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 티어를 찾지 못했습니다."),
BIOME_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "해당하는 바이옴을 찾지 못했습니다."),
MOVE_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "id에 해당하는 기술을 찾지 못했습니다."),
MOVE_CATEGORY_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "기술 카테고리를 찾지 못했습니다."),
PARSE_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "파싱에 실패했습니다."),
TYPE_MATCHING_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "타입 상성 찾기에 실패했습니다."),
POKEMON_SIZE_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR, "예상 포켓몬 수와 실제 데이터가 일치하지 않습니다."),
POKEMON_BASE_TOTAL_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR, "종족값이 기본 능력치의 합과 다릅니다."),
POKEMON_ID_UNEXPECTED_LETTER(HttpStatus.INTERNAL_SERVER_ERROR, "아이디에 허용되지 않은 문자가 포함되어 있습니다."),
POKEMON_ID_DELIMITER_MISPLACED(HttpStatus.INTERNAL_SERVER_ERROR, "아이디에 구분자가 처음이나 끝에 올 수 없습니다."),
POKEMON_ID_DELIMITER_IS_SEQUENTIAL(HttpStatus.INTERNAL_SERVER_ERROR, "아이디에 구분자가 연속으로 배치되어 있습니다."),
POKEMON_GENERATION_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR,"적절하지 않은 포켓몬 세대입니다."),
POKEMON_FORM_CHANGE_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR,"폼변환이 가능하지만 변환 가능한 포켓몬이 없습니다."),
POKEMON_RARITY_COUNT_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR,"전설, 준전설, 미신은 셋 중 하나 이하만 선택될 수 있습니다."),
POKEMON_NORMAL_ABILITY_COUNT(HttpStatus.INTERNAL_SERVER_ERROR,"포켓몬 기본 특성 개수가 유효범위 내에 있지 않습니다."),
POKEMON_TOTAL_ABILITY_COUNT(HttpStatus.INTERNAL_SERVER_ERROR,"포켓몬 특성의 총 개수가 유효범위 내에 있지 않습니다."),
POKEMON_ABILITY_DUPLICATION(HttpStatus.INTERNAL_SERVER_ERROR,"중복되는 특성이 존재합니다."),
POKEMON_STAT_OUT_OF_RANGE(HttpStatus.INTERNAL_SERVER_ERROR, "포켓몬 능력 수치가 예상 범위 내에 있지 않습니다."),
POKEMON_PASSIVE_NOT_FOUND(HttpStatus.INTERNAL_SERVER_ERROR, "패시브 특성은 반드시 존재해야합니다."),
POKEMON_TYPE_COUNT_OUT_OF_RANGE(HttpStatus.INTERNAL_SERVER_ERROR,"포켓몬의 특성의 개수가 유효범위 내에 있지 않습니다."),
POKEMON_TYPE_DUPLICATION(HttpStatus.INTERNAL_SERVER_ERROR,"포켓몬의 타입은 중복될 수 없습니다."),
POKEMON_EVOLUTION_ID_MISMATCH(HttpStatus.INTERNAL_SERVER_ERROR, "포켓몬과 진화 포켓몬 아이디가 서로 일치하지 않습니다"),




FILE_ACCESS_FAILED(HttpStatus.BAD_REQUEST, "파일 정보 접근에 실패했습니다."),
FILE_EXTENSION_NOT_APPLY(HttpStatus.BAD_REQUEST, "지원하지 않는 파일 형식입니다."),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,9 @@ public GlobalCustomException(ErrorMessage errorMessage) {
super(errorMessage.getMessage());
this.httpStatus = errorMessage.getHttpStatus();
}

public GlobalCustomException(ErrorMessage errorMessage, String message) {
super(errorMessage.getMessage() + message);
this.httpStatus = errorMessage.getHttpStatus();
}
}
Loading

0 comments on commit 1b52135

Please sign in to comment.