Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix XSkull#applySkin with name or UUID inputs (#254) #256

Merged
merged 4 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/main/java/com/cryptomorin/xseries/XItemStack.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
*/
package com.cryptomorin.xseries;

import com.cryptomorin.xseries.skull.XSkull;
import com.google.common.base.Enums;
import com.google.common.base.Strings;
import com.google.common.collect.Multimap;
Expand Down Expand Up @@ -652,7 +653,7 @@ public static ItemStack edit(@Nonnull ItemStack item,
// Special Items
if (meta instanceof SkullMeta) {
String skull = config.getString("skull");
if (skull != null) XSkull.applySkin(meta, skull);
if (skull != null) XSkull.of(meta).profile(skull).apply();
} else if (meta instanceof BannerMeta) {
BannerMeta banner = (BannerMeta) meta;
ConfigurationSection patterns = config.getConfigurationSection("patterns");
Expand Down
639 changes: 0 additions & 639 deletions src/main/java/com/cryptomorin/xseries/XSkull.java

This file was deleted.

81 changes: 81 additions & 0 deletions src/main/java/com/cryptomorin/xseries/skull/Action.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package com.cryptomorin.xseries.skull;

import com.mojang.authlib.GameProfile;

import javax.annotation.Nonnull;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

/**
* The {@code Action} class represents an action that can be performed on a {@link GameProfile}.
* It uses an {@link Instruction} to define how the action is applied.
*
* @param <T> The type of the result produced by the action.
*/
public class Action<T> {
Condordito marked this conversation as resolved.
Show resolved Hide resolved

/**
* An executor service with a fixed thread pool of size 2, used for asynchronous operations.
*/
private static final ExecutorService EXECUTOR = Executors.newFixedThreadPool(2, new ThreadFactory() {
private final AtomicInteger count = new AtomicInteger();

@Override
public Thread newThread(final @Nonnull Runnable run) {
final Thread thread = new Thread(run);
thread.setName("Profile Lookup Executor #" + this.count.getAndIncrement());
thread.setUncaughtExceptionHandler((t, throwable) ->
XSkull.LOGGER.debug("Uncaught exception in thread {}", t.getName(), throwable));
return thread;
}
Condordito marked this conversation as resolved.
Show resolved Hide resolved
});

/**
* The instruction that defines how the action is applied.
*/
private final Instruction<T> instruction;

/**
* Constructs an {@code Action} with the specified instruction.
*
* @param instruction The instruction that defines how the action is applied.
*/
protected Action(Instruction<T> instruction) {
this.instruction = instruction;
}

/**
* Applies the instruction to a {@link GameProfile} and returns the result.
*
* @return The result of applying the instruction to the {@link GameProfile}.
*/
public T apply() {
GameProfile profile = instruction.type != null
? XSkull.getProfile(instruction.type, instruction.input)
: XSkull.getProfile(InputType.get(instruction.input), instruction.input);
return instruction.setter.apply(profile);
}

/**
* Asynchronously applies the instruction to a {@link GameProfile} and returns a {@link CompletableFuture}
* that will complete with the result. This method is designed for non-blocking execution,
* allowing tasks to be performed in the background without slowing down the server's main thread.
*
* <p>Usage example:</p>
* <pre>{@code
* XSkull.of(XMaterial.PLAYER_HEAD.parseItem())
* .profile("75714f9975d9934f062129b154aed949ce52df10c03099b60803cdb5485c2917").applyAsync()
* .thenAcceptAsync(itemStack -> {
* // Additional processing...
* }, runnable -> Bukkit.getScheduler().runTask(plugin, runnable));
* }</pre>
*
* @return A {@link CompletableFuture} that will complete with the result of applying the instruction.
*/
public CompletableFuture<T> applyAsync() {
return CompletableFuture.supplyAsync(this::apply, EXECUTOR);
}
}
65 changes: 65 additions & 0 deletions src/main/java/com/cryptomorin/xseries/skull/InputType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.cryptomorin.xseries.skull;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Arrays;
import java.util.regex.Pattern;

/**
* The {@code InputType} enum represents different types of input patterns that can be used for identifying
* and validating various formats such as texture hashes, URLs, Base64 encoded strings, UUIDs, and usernames.
*/
public enum InputType {

/**
* Represents a texture hash pattern.
*/
TEXTURE_HASH(Pattern.compile("[0-9a-z]{55,70}")),

/**
* Represents a texture URL pattern that includes the base URL followed by the texture hash pattern.
*/
TEXTURE_URL(Pattern.compile(Pattern.quote(XSkull.TEXTURES) + TEXTURE_HASH.pattern)),

/**
* Represents a Base64 encoded string pattern.
*/
BASE64(Pattern.compile("[-A-Za-z0-9+/]{100,}={0,3}")),

/**
* Represents a UUID pattern, following the standard UUID format.
*/
UUID(Pattern.compile("[A-F\\d]{8}-[A-F\\d]{4}-4[A-F\\d]{3}-([89AB])[A-F\\d]{3}-[A-F\\d]{12}", Pattern.CASE_INSENSITIVE)),

/**
* Represents a username pattern, allowing alphanumeric characters and underscores, with a length of 1 to 16 characters.
*/
USERNAME(Pattern.compile("[A-Za-z0-9_]{1,16}"));

/**
* The regex pattern associated with the input type.
*/
private final Pattern pattern;

/**
* Constructs an {@code InputType} with the specified regex pattern.
*
* @param pattern The regex pattern associated with the input type.
*/
InputType(Pattern pattern) {
this.pattern = pattern;
}

/**
* Returns the corresponding {@code InputType} for the given identifier, if it matches any pattern.
*
* @param identifier The string to be checked against the patterns.
* @return The matching {@code InputType}, or {@code null} if no match is found.
*/
@Nullable
public static InputType get(@Nonnull String identifier) {
return Arrays.stream(InputType.values())
Condordito marked this conversation as resolved.
Show resolved Hide resolved
.filter(value -> value.pattern.matcher(identifier).matches())
.findFirst().orElse(null);
}
}
62 changes: 62 additions & 0 deletions src/main/java/com/cryptomorin/xseries/skull/Instruction.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package com.cryptomorin.xseries.skull;

import com.mojang.authlib.GameProfile;

import java.util.function.Function;

/**
* The {@code Instruction} class represents an instruction that sets a property of a {@link GameProfile}.
* It uses a {@link Function} to define how to set the property.
*
* @param <T> The type of the result produced by the setter function.
*/
public class Instruction<T> {

/**
* A function that takes a {@link GameProfile} and produces a result of type {@code T}.
*/
final protected Function<GameProfile, T> setter;

/**
* The input value to be used in the profile setting operation.
*/
protected String input = null;

/**
* The type of the input value.
*/
protected InputType type = null;

/**
* Constructs an {@code Instruction} with the specified setter function.
*
* @param setter A function that sets a property of a {@link GameProfile} and returns a result of type {@code T}.
*/
Instruction(Function<GameProfile, T> setter) {
this.setter = setter;
}

/**
* Sets the input value to be used in the profile setting operation and returns a new {@link Action} instance.
*
* @param input The input value to be used in the profile setting operation.
* @return A new {@link Action} instance configured with this {@code Instruction}.
*/
public Action<T> profile(String input) {
this.input = input;
return new Action<>(this);
}

/**
* Sets the input type and value to be used in the profile setting operation and returns a new {@link Action} instance.
*
* @param type The type of the input value.
* @param input The input value to be used in the profile setting operation.
* @return A new {@link Action} instance configured with this {@code Instruction}.
*/
public Action<T> profile(InputType type, String input) {
Condordito marked this conversation as resolved.
Show resolved Hide resolved
this.type = type;
this.input = input;
return new Action<>(this);
}
}
Loading