Skip to content

Commit

Permalink
Minor profile API additions
Browse files Browse the repository at this point in the history
  • Loading branch information
CryptoMorin committed Sep 23, 2024
1 parent fe818e8 commit 95b0796
Show file tree
Hide file tree
Showing 10 changed files with 119 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.ArrayList;
Expand Down Expand Up @@ -96,8 +97,8 @@ public String getProfileString() {
* Sets the texture profile to be set to the item/block. Use one of the
* static methods of {@link Profileable} class.
*/
public ProfileInstruction<T> profile(Profileable profileable) {
this.profileable = profileable;
public ProfileInstruction<T> profile(@NotNull Profileable profileable) {
this.profileable = Objects.requireNonNull(profileable, "Profileable is null");
return this;
}

Expand All @@ -107,7 +108,8 @@ public ProfileInstruction<T> profile(Profileable profileable) {
* also if any of the fallback profiles are used, {@link #onFallback} will be called too.
* @see #apply()
*/
public ProfileInstruction<T> fallback(Profileable... fallbacks) {
public ProfileInstruction<T> fallback(@NotNull Profileable... fallbacks) {
Objects.requireNonNull(fallbacks, "fallbacks array is null");
this.fallbacks.addAll(Arrays.asList(fallbacks));
return this;
}
Expand All @@ -117,15 +119,16 @@ public ProfileInstruction<T> fallback(Profileable... fallbacks) {
* this is also called if no fallback profile is provided, but the main one {@link #profile(Profileable)} fails.
* @see #onFallback(Runnable)
*/
public ProfileInstruction<T> onFallback(Consumer<ProfileFallback<T>> onFallback) {
public ProfileInstruction<T> onFallback(@Nullable Consumer<ProfileFallback<T>> onFallback) {
this.onFallback = onFallback;
return this;
}

/**
* @see #onFallback(Consumer)
*/
public ProfileInstruction<T> onFallback(Runnable onFallback) {
public ProfileInstruction<T> onFallback(@NotNull Runnable onFallback) {
Objects.requireNonNull(onFallback, "onFallback runnable is null");
this.onFallback = (fallback) -> onFallback.run();
return this;
}
Expand Down Expand Up @@ -168,7 +171,7 @@ public T apply() {
if (exception == null) {
exception = new ProfileChangeException("Could not set the profile for " + profileContainer);
}
exception.addSuppressed(new InvalidProfileException("Profile doesn't have a value: " + profileable));
exception.addSuppressed(new InvalidProfileException(profileable.toString(), "Profile doesn't have a value: " + profileable));
tryingFallbacks = true;
}
} catch (ProfileException ex) {
Expand Down
17 changes: 12 additions & 5 deletions src/main/java/com/cryptomorin/xseries/profiles/builder/XSkull.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.NotNull;

/**
* A cross-version way to apply skin texture from different sources to items and blocks.
Expand Down Expand Up @@ -71,7 +72,7 @@
* I don't know if this cache system works across other servers or is just specific to one server.
*
* @author Crypto Morin, Erick Alexander
* @version 11.2.0
* @version 11.2.1
* @see XMaterial
* @see XReflection
*/
Expand All @@ -82,6 +83,7 @@ public final class XSkull {
*
* @return A {@link ProfileInstruction} that sets the profile for the generated {@link ItemStack}.
*/
@NotNull
public static ProfileInstruction<ItemStack> createItem() {
return of(XMaterial.PLAYER_HEAD.parseItem());
}
Expand All @@ -92,7 +94,8 @@ public static ProfileInstruction<ItemStack> createItem() {
* @param stack The {@link ItemStack} to set the profile for.
* @return A {@link ProfileInstruction} that sets the profile for the given {@link ItemStack}.
*/
public static ProfileInstruction<ItemStack> of(ItemStack stack) {
@NotNull
public static ProfileInstruction<ItemStack> of(@NotNull ItemStack stack) {
return new ProfileInstruction<>(new ProfileContainer.ItemStackProfileContainer(stack));
}

Expand All @@ -102,7 +105,8 @@ public static ProfileInstruction<ItemStack> of(ItemStack stack) {
* @param meta The {@link ItemMeta} to set the profile for.
* @return An {@link ProfileInstruction} that sets the profile for the given {@link ItemMeta}.
*/
public static ProfileInstruction<ItemMeta> of(ItemMeta meta) {
@NotNull
public static ProfileInstruction<ItemMeta> of(@NotNull ItemMeta meta) {
return new ProfileInstruction<>(new ProfileContainer.ItemMetaProfileContainer((SkullMeta) meta));
}

Expand All @@ -112,7 +116,8 @@ public static ProfileInstruction<ItemMeta> of(ItemMeta meta) {
* @param block The {@link Block} to set the profile for.
* @return An {@link ProfileInstruction} that sets the profile for the given {@link Block}.
*/
public static ProfileInstruction<Block> of(Block block) {
@NotNull
public static ProfileInstruction<Block> of(@NotNull Block block) {
return new ProfileInstruction<>(new ProfileContainer.BlockProfileContainer(block));
}

Expand All @@ -122,7 +127,8 @@ public static ProfileInstruction<Block> of(Block block) {
* @param state The {@link BlockState} to set the profile for.
* @return An {@link ProfileInstruction} that sets the profile for the given {@link BlockState}.
*/
public static ProfileInstruction<Skull> of(BlockState state) {
@NotNull
public static ProfileInstruction<Skull> of(@NotNull BlockState state) {
return new ProfileInstruction<>(new ProfileContainer.BlockStateProfileContainer((Skull) state));
}

Expand All @@ -143,6 +149,7 @@ public static ProfileInstruction<Skull> of(BlockState state) {
*
* @return A clone of the default {@link GameProfile}.
*/
@NotNull
protected static Profileable getDefaultProfile() {
// We copy this just in case something changes the GameProfile properties.
GameProfile clone = PlayerProfiles.createGameProfile(DEFAULT_PROFILE.getId(), DEFAULT_PROFILE.getName());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
package com.cryptomorin.xseries.profiles.exceptions;

import org.jetbrains.annotations.NotNull;

/**
* When a provided item/block cannot contain skull textures.
*/
public final class InvalidProfileContainerException extends ProfileException {
public InvalidProfileContainerException(String message) {
private final Object container;

public InvalidProfileContainerException(Object container, String message) {
super(message);
this.container = container;
}

public InvalidProfileContainerException(String message, Throwable cause) {
super(message, cause);
/**
* A {@link org.bukkit.inventory.ItemStack} or {@link org.bukkit.block.Block}.
*/
@NotNull
public Object getContainer() {
return container;
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,35 @@
package com.cryptomorin.xseries.profiles.exceptions;

import com.cryptomorin.xseries.profiles.objects.Profileable;
import org.jetbrains.annotations.NotNull;

/**
* if a given {@link Profileable} has incorrect value (more general than {@link UnknownPlayerException})
* If a given {@link Profileable} has incorrect value (more general than {@link UnknownPlayerException})
* which mostly happens when a string value is passed to be handled by {@link com.cryptomorin.xseries.profiles.objects.ProfileInputType}.
* E.g. invalid Base64, texture URLs, username, UUID, etc.
* Though most of these invalid values are just recognized as an unknown string instead
* of being invalid values of a specific input type.
* <p>
* Note: Unknown usernames and UUIDs are handled by {@link UnknownPlayerException} instead.
*/
public class InvalidProfileException extends ProfileException {
public InvalidProfileException(String message) {
private final String value;

public InvalidProfileException(String value, String message) {
super(message);
this.value = value;
}

public InvalidProfileException(String message, Throwable cause) {
public InvalidProfileException(String value, String message, Throwable cause) {
super(message, cause);
this.value = value;
}

/**
* The invalid value.
*/
@NotNull
public String getValue() {
return value;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@

/**
* Aggregate error container for {@link ProfileInstruction#apply()}.
* All issues are added as suppressed exceptions ({@link Throwable#getSuppressed()}).
*/
public final class ProfileChangeException extends ProfileException {
public ProfileChangeException(String message) {
super(message);
}

public ProfileChangeException(String message, Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
package com.cryptomorin.xseries.profiles.exceptions;

import com.cryptomorin.xseries.profiles.objects.Profileable;
import org.jetbrains.annotations.NotNull;

/**
* If a specific player-identifying {@link Profileable} is not found.
* This can be an unknown username or UUID. (Invalid usernames and UUIDs are handled by {@link InvalidProfileException}).
*/
public final class UnknownPlayerException extends InvalidProfileException {
public UnknownPlayerException(String message) {
super(message);
private final Object unknownObject;

public UnknownPlayerException(Object unknownObject, String message) {
super(unknownObject.toString(), message);
this.unknownObject = unknownObject;
}

/**
* An invalid or unknown username ({@link String}) or an unknown {@link java.util.UUID}.
*/
@NotNull
public Object getUnknownObject() {
return unknownObject;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ public static GameProfile getOrFetchProfile(@Nonnull final GameProfile profile)
} else {
realUUID = PlayerUUIDs.getRealUUIDOfPlayer(profile.getName(), profile.getId());
if (realUUID == null) {
throw new UnknownPlayerException("Player with the given properties not found: " + profile);
throw new UnknownPlayerException(profile.getName(), "Player with the given properties not found: " + profile);
}
}

Expand All @@ -286,14 +286,14 @@ public static GameProfile getOrFetchProfile(@Nonnull final GameProfile profile)
if (cached != null) {
ProfileLogger.debug("Found cached profile from UUID ({}): {} -> {}", realUUID, profile, cached);
if (cached.isPresent()) return cached.get();
else throw new UnknownPlayerException("Player with the given properties not found: " + profile);
else throw new UnknownPlayerException(realUUID, "Player with the given properties not found: " + profile);
}

Optional<GameProfile> mojangCache = MOJANG_PROFILE_CACHE.get(realUUID, profile);
if (mojangCache != null) {
INSECURE_PROFILES.put(realUUID, mojangCache);
if (mojangCache.isPresent()) return mojangCache.get();
else throw new UnknownPlayerException("Player with the given properties not found: " + profile);
else throw new UnknownPlayerException(realUUID, "Player with the given properties not found: " + profile);
}

JsonElement request;
Expand All @@ -307,7 +307,7 @@ public static GameProfile getOrFetchProfile(@Nonnull final GameProfile profile)
if (request == null) {
INSECURE_PROFILES.put(realUUID, Optional.empty());
MOJANG_PROFILE_CACHE.cache(new PlayerProfile(realUUID, profile, null, null));
throw new UnknownPlayerException("Player with the given properties not found: " + profile);
throw new UnknownPlayerException(realUUID, "Player with the given properties not found: " + profile);
}
JsonObject profileData = request.getAsJsonObject();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.annotation.Nonnull;
Expand All @@ -25,6 +26,7 @@ public abstract class ProfileContainer<T> implements Profileable {
@Nonnull
public abstract void setProfile(@Nullable GameProfile profile);

@NotNull
public abstract T getObject();

@Override
Expand All @@ -35,11 +37,11 @@ public final String toString() {
public static final class ItemStackProfileContainer extends ProfileContainer<ItemStack> {
private final ItemStack itemStack;

public ItemStackProfileContainer(ItemStack itemStack) {this.itemStack = Objects.requireNonNull(itemStack);}
public ItemStackProfileContainer(ItemStack itemStack) {this.itemStack = Objects.requireNonNull(itemStack, "ItemStack is null");}

private ItemMetaProfileContainer getMetaContainer(ItemMeta meta) {
if (!(meta instanceof SkullMeta))
throw new InvalidProfileContainerException("Item can't contain texture: " + itemStack);
throw new InvalidProfileContainerException(itemStack, "Item can't contain texture: " + itemStack);
return new ItemMetaProfileContainer((SkullMeta) meta);
}

Expand All @@ -64,7 +66,7 @@ public GameProfile getProfile() {
public static final class ItemMetaProfileContainer extends ProfileContainer<ItemMeta> {
private final ItemMeta meta;

public ItemMetaProfileContainer(SkullMeta meta) {this.meta = Objects.requireNonNull(meta);}
public ItemMetaProfileContainer(SkullMeta meta) {this.meta = Objects.requireNonNull(meta, "ItemMeta is null");}

@Override
public void setProfile(GameProfile profile) {
Expand Down Expand Up @@ -93,12 +95,12 @@ public GameProfile getProfile() {
public static final class BlockProfileContainer extends ProfileContainer<Block> {
private final Block block;

public BlockProfileContainer(Block block) {this.block = Objects.requireNonNull(block);}
public BlockProfileContainer(Block block) {this.block = Objects.requireNonNull(block, "Block is null");}

private Skull getBlockState() {
BlockState state = block.getState();
if (!(state instanceof Skull))
throw new InvalidProfileContainerException("Block can't contain texture: " + block);
throw new InvalidProfileContainerException(block, "Block can't contain texture: " + block);
return (Skull) state;
}

Expand All @@ -123,7 +125,7 @@ public GameProfile getProfile() {
public static final class BlockStateProfileContainer extends ProfileContainer<Skull> {
private final Skull state;

public BlockStateProfileContainer(Skull state) {this.state = Objects.requireNonNull(state);}
public BlockStateProfileContainer(Skull state) {this.state = Objects.requireNonNull(state, "Skull BlockState is null");}

@Override
public void setProfile(GameProfile profile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,11 +61,11 @@ public GameProfile getProfile(String base64) {
// (from authlib's MinecraftProfileTexture which exists in all versions 1.8-1.21)
String decodedBase64 = PlayerProfiles.decodeBase64(base64);
if (decodedBase64 == null)
throw new InvalidProfileException("Not a base64 string: " + base64);
throw new InvalidProfileException(base64, "Not a base64 string: " + base64);

String textureHash = extractTextureHash(decodedBase64);
if (textureHash == null)
throw new InvalidProfileException("Can't extract texture hash from base64: " + decodedBase64);
throw new InvalidProfileException(decodedBase64, "Can't extract texture hash from base64: " + decodedBase64);

return PlayerProfiles.profileFromHashAndBase64(textureHash, base64);
}
Expand All @@ -78,7 +78,14 @@ public GameProfile getProfile(String base64) {
// Case-insensitive flag doesn't work for some UUIDs.
@Override
public GameProfile getProfile(String uuidString) {
return Profileable.of(java.util.UUID.fromString(uuidString)).getProfile();
java.util.UUID uuid;
try {
uuid = java.util.UUID.fromString(uuidString);
} catch (IllegalArgumentException ex) {
// This should technically never happen.
throw new InvalidProfileException(uuidString, "Invalid UUID string: " + uuidString, ex);
}
return Profileable.of(uuid).getProfile();
}
},

Expand Down
Loading

0 comments on commit 95b0796

Please sign in to comment.