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

GH-835 Add permission-based access to warps #856

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 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
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,16 @@

import org.bukkit.Location;

import java.util.List;

public interface Warp {

Location getLocation();

String getName();

List<String> getPermissions();

void setPermissions(List<String> permissions);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove this method from public API


Comment on lines +15 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Thread-Safety Issue: Concurrent Access to Warp Objects

The setPermissions(List<String> permissions) method allows modifying permissions, which may be accessed concurrently by multiple threads. However, the current implementations of the Warp interface do not employ synchronization mechanisms, posing potential thread-safety risks.

Consider implementing synchronization or using thread-safe data structures to manage permissions safely and prevent race conditions.

🔗 Analysis chain

LGTM: New method setPermissions() is well-defined, but consider thread-safety.

The setPermissions(List<String> permissions) method is a good addition for managing warp-specific permissions.

Consider adding Javadoc to describe the method's purpose and parameters, e.g.:

/**
 * Sets the list of permissions required to access this warp.
 *
 * @param permissions A list of permission strings to be associated with this warp.
 */
void setPermissions(List<String> permissions);

Additionally, consider the thread-safety implications of this setter method. If this interface is implemented by mutable objects that might be accessed concurrently, you may want to ensure thread-safe operations or document any synchronization requirements.

To verify the usage and potential concurrency issues, you can run the following script:

This script will help identify potential concurrency issues and how Warp objects are being used throughout the codebase.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for potential concurrency issues with Warp implementations

# Search for classes implementing the Warp interface
echo "Classes implementing Warp interface:"
rg --type java "class\s+\w+\s+implements\s+.*Warp"

# Search for synchronized methods or blocks in Warp implementations
echo "\nSynchronized methods or blocks in Warp implementations:"
rg --type java "class\s+\w+\s+implements\s+.*Warp" -A 20 | rg "synchronized"

# Search for concurrent access to Warp objects
echo "\nPotential concurrent access to Warp objects:"
rg --type java "getWarp|setWarp|createWarp|deleteWarp"

Length of output: 4222

}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ public interface WarpService {

void removeWarp(String warp);

void addPermissions(String warp, String... permissions);

void removePermission(String warp, String permission);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing return of all permissions

boolean warpExists(String name);

boolean doesWarpPermissionExist(String warp, String permission);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is permission needed here? We can just check it using isEmpty on permissions list in object


Optional<Warp> findWarp(String name);

Collection<String> getNamesOfWarps();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@
import com.eternalcode.multification.cdn.MultificationNoticeCdnComposer;
import com.eternalcode.multification.notice.Notice;
import com.eternalcode.multification.notice.resolver.NoticeResolverRegistry;
import java.io.File;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;
import net.dzikoysk.cdn.Cdn;
import net.dzikoysk.cdn.CdnFactory;
import net.dzikoysk.cdn.reflect.Visibility;
import org.bukkit.Material;

import java.io.File;
import java.time.Duration;
import java.util.HashSet;
import java.util.Set;

@Service
public class ConfigurationManager {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.eternalcode.core.configuration.implementation;

import com.eternalcode.commons.bukkit.position.Position;
import com.eternalcode.core.configuration.ReloadableConfig;
import com.eternalcode.core.injector.annotations.component.ConfigurationFile;
import com.eternalcode.commons.bukkit.position.Position;
import net.dzikoysk.cdn.entity.Description;
import net.dzikoysk.cdn.entity.Exclude;
import net.dzikoysk.cdn.source.Resource;
Expand All @@ -21,7 +21,8 @@ public class LocationsConfiguration implements ReloadableConfig {
@Description("# This is spawn location, for your own safety, please don't touch it.")
public Position spawn = EMPTY_POSITION;

@Description("# These are warp locations, for your own safety, please don't touch it.")
@Description("# Warps now are stored in warps.yml. This is deprecated.")
@Deprecated
Comment on lines +24 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Issues Found: Deprecated warps field is still in use.

  • Multiple usages of the deprecated warps field detected in:

    • WarpConfigRepository.java
    • WarpService.java
    • Various other classes related to warp management
  • Action Items:

    1. Update all instances accessing the deprecated warps field to utilize the new warps.yml storage method.
    2. Ensure that deprecated annotations are appropriately handled to guide future development.
    3. Review and test affected modules to confirm the transition does not introduce regressions.
🔗 Analysis chain

Approved: Warps field deprecated and description updated.

The changes to the warps field are appropriate:

  1. The updated description clearly informs users about the new storage location for warps.
  2. The @Deprecated annotation correctly marks this field as no longer preferred.

These modifications align with the PR objectives of implementing permission-based access to warps.

To ensure a smooth transition, please verify the following:

  1. Check for any code that directly accesses this warps field and update it to use the new storage method.
  2. Consider adding a deprecation warning message to guide users on migrating to the new warp storage system.
  3. Verify that the new warps.yml file is properly implemented and documented.

Run the following script to identify potential usages of the deprecated warps field:

Please review the results and update the affected code as necessary.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usages of the deprecated warps field

# Search for direct accesses to the warps field
echo "Searching for direct accesses to the warps field:"
rg --type java "\.warps\b" --glob '!LocationsConfiguration.java'

# Search for method calls that might be using the warps field
echo "Searching for potential method calls using the warps field:"
rg --type java "\b(get|set|add|remove|clear)Warp(s)?\b" --glob '!LocationsConfiguration.java'

Length of output: 4910

public Map<String, Position> warps = new HashMap<>();
Comment on lines +24 to 26
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Action Required: Deprecated warps Field Still In Use

The deprecated warps field is still being accessed in the following locations:

  • WarpConfigRepository.java:
    • Multiple direct accesses to this.warpsConfiguration.warps.
  • WarpPermissionController.java
  • WarpInventory.java
  • SetWarpCommand.java
  • DelWarpCommand.java
  • WarpTeleportService.java
  • WarpRepository.java
  • WarpServiceImpl.java
  • WarpService.java
  • WarpTeleportEvent.java
  • PreWarpTeleportEvent.java

Please update these usages to utilize the new warps.yml storage system to fully deprecate the warps field and prevent potential issues.

🔗 Analysis chain

Approved: Warps field deprecated and description updated

The changes to the warps field are appropriate:

  1. The updated description clearly informs users about the new storage location for warps.
  2. The @Deprecated annotation correctly marks this field as no longer preferred.

These modifications align with the PR objectives of implementing permission-based access to warps.

However, please ensure the following:

  1. Address the usage of this deprecated field in other parts of the codebase, as mentioned in the previous review comments.
  2. Consider adding a deprecation warning message to guide users on migrating to the new warp storage system.

To verify the current usage of the deprecated warps field, run the following script:

Please review the results and update the affected code as necessary to use the new warps.yml storage method.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usages of the deprecated warps field

# Search for direct accesses to the warps field
echo "Searching for direct accesses to the warps field:"
rg --type java "\.warps\b" --glob '!LocationsConfiguration.java'

# Search for method calls that might be using the warps field
echo "Searching for potential method calls using the warps field:"
rg --type java "\b(get|set|add|remove|clear)Warp(s)?\b" --glob '!LocationsConfiguration.java'

Length of output: 4910


@Description("# This is jail location, for your own safety, please don't touch it.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,13 @@
import com.eternalcode.core.feature.afk.AfkSettings;
import com.eternalcode.core.feature.automessage.AutoMessageSettings;
import com.eternalcode.core.feature.chat.ChatSettings;
import com.eternalcode.core.feature.helpop.HelpOpSettings;
import com.eternalcode.core.feature.jail.JailSettings;
import com.eternalcode.core.feature.randomteleport.RandomTeleportSettings;
import com.eternalcode.core.feature.randomteleport.RandomTeleportType;
import com.eternalcode.core.feature.helpop.HelpOpSettings;
import com.eternalcode.core.feature.spawn.SpawnSettings;
import com.eternalcode.core.injector.annotations.component.ConfigurationFile;
import com.eternalcode.core.feature.teleportrequest.TeleportRequestSettings;
import java.util.LinkedHashMap;
import java.util.Set;
import com.eternalcode.core.injector.annotations.component.ConfigurationFile;
import net.dzikoysk.cdn.entity.Contextual;
import net.dzikoysk.cdn.entity.Description;
import net.dzikoysk.cdn.entity.Exclude;
Expand All @@ -25,7 +23,9 @@

import java.io.File;
import java.time.Duration;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

@ConfigurationFile
public class PluginConfiguration implements ReloadableConfig {
Expand Down Expand Up @@ -395,7 +395,6 @@ public static class Warp {

@Description("# Texture of the item (only for PLAYER_HEAD material)")
public String itemTexture = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzk4ODVlODIzZmYxNTkyNjdjYmU4MDkwOTNlMzNhNDc2ZTI3NDliNjU5OGNhNGEyYTgxZWU2OTczODAzZmI2NiJ9fX0=";

}

@Description({ " ", "# Butcher" })
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.eternalcode.core.configuration.implementation;

import com.eternalcode.core.configuration.ReloadableConfig;
import com.eternalcode.core.feature.warp.WarpConfigEntry;
import com.eternalcode.core.injector.annotations.component.ConfigurationFile;
import net.dzikoysk.cdn.entity.Description;
import net.dzikoysk.cdn.source.Resource;
import net.dzikoysk.cdn.source.Source;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

@ConfigurationFile
public class WarpsConfiguration implements ReloadableConfig {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More like WarpsLocation, configuration is in config


@Description({"# Warps configuration", "# These are warp locations, for your own safety, please don't touch it."})
public Map<String, WarpConfigEntry> warps = new HashMap<>();
Comment on lines +17 to +18
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Improve encapsulation and naming of the warps field.

While the field is appropriately typed and described, there are several improvements that can be made:

  1. As suggested by CitralFlo, consider renaming the field to warpsLocation for clarity.
  2. Implement better encapsulation as suggested in the previous review:
    • Make the field private and final.
    • Provide controlled access methods.
    • Return an unmodifiable view of the map to prevent external modifications.

Here's a suggested refactor:

@Description({"# Warps configuration", "# These are warp locations, for your own safety, please don't modify directly."})
private final Map<String, WarpConfigEntry> warpsLocation = new HashMap<>();

public Map<String, WarpConfigEntry> getWarpsLocation() {
    return Collections.unmodifiableMap(warpsLocation);
}

public void setWarp(String key, WarpConfigEntry value) {
    warpsLocation.put(key, value);
}

public void removeWarp(String key) {
    warpsLocation.remove(key);
}

This approach maintains functionality while providing better control over how the warps are accessed and modified.


@Override
public Resource resource(File folder) {
return Source.of(folder, "warps.yml");
}

}
Comment on lines +1 to +25
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding utility methods and validation logic.

While the current implementation is functional, consider the following enhancements to improve the robustness and usability of the WarpsConfiguration class:

  1. Add a method to validate warp entries, ensuring they meet any required criteria.
  2. Implement a method to merge or update the configuration with new entries.
  3. Consider adding utility methods for common operations, such as checking if a warp exists.

Here are some suggested method signatures:

public boolean isValidWarp(WarpConfigEntry entry) {
    // Implement validation logic
}

public void mergeConfiguration(Map<String, WarpConfigEntry> newWarps) {
    // Implement merge logic
}

public boolean hasWarp(String warpName) {
    return warps.containsKey(warpName);
}

These additions would enhance the functionality and make the class more robust and easier to use in other parts of the system.

Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.eternalcode.core.feature.warp;

import com.eternalcode.commons.bukkit.position.Position;
import net.dzikoysk.cdn.entity.Contextual;

import java.util.List;

@Contextual
public class WarpConfigEntry {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the difference between WarpConfigEntry and WarpImpl ? Why are we using both when this could be one class?

public Position position;
public List<String> permissions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using a Set for permissions and add validation

The current implementation uses a List<String> for storing permissions. This could lead to potential issues:

  1. Duplicate permissions could be added unintentionally.
  2. Permission checks might be case-sensitive, leading to inconsistencies (e.g., "ADMIN" vs "admin").
  3. Checking for the existence of a permission is not as efficient as it could be with a Set.

Consider the following improvements:

  1. Use a Set<String> instead of a List<String> for permissions. This will automatically prevent duplicates and provide more efficient permission checks.
  2. Implement permission normalization (e.g., converting to lowercase) to ensure consistency.
  3. Add validation in the constructor to ensure that the permissions are not null and don't contain empty strings.

Here's an example of how you could implement these changes:

import java.util.HashSet;
import java.util.Set;
import java.util.stream.Collectors;

public class WarpConfigEntry {
    private final Position position;
    private final Set<String> permissions;

    public WarpConfigEntry(Position position, List<String> permissions) {
        this.position = position;
        this.permissions = normalizePermissions(permissions);
    }

    private Set<String> normalizePermissions(List<String> permissions) {
        if (permissions == null) {
            throw new IllegalArgumentException("Permissions list cannot be null");
        }
        return permissions.stream()
            .filter(p -> p != null && !p.trim().isEmpty())
            .map(String::toLowerCase)
            .collect(Collectors.toCollection(HashSet::new));
    }

    // ... getters and other methods
}

This implementation ensures that the permissions are stored efficiently, normalized, and validated.

Comment on lines +10 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Enhance encapsulation and optimize data structures.

The current implementation with public fields violates encapsulation principles and might lead to inconsistent object state. Consider the following improvements:

  1. Make the fields private and provide getter methods (and setter methods if necessary).
  2. Use a Set<String> instead of List<String> for permissions to prevent duplicates and improve lookup efficiency.
  3. Consider making the fields final to ensure immutability.

Example refactoring:

private final Position position;
private final Set<String> permissions;

public Position getPosition() {
    return position;
}

public Set<String> getPermissions() {
    return Collections.unmodifiableSet(permissions);
}

This refactoring improves encapsulation, prevents duplicate permissions, and ensures immutability of the object state.


public WarpConfigEntry() {
}

public WarpConfigEntry(Position position, List<String> permissions) {
this.position = position;
this.permissions = permissions;
}
Comment on lines +13 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Improve constructor implementation and consider removing the default constructor.

The current constructor implementation has potential issues:

  1. The default constructor allows the creation of objects with null fields, which might lead to NullPointerExceptions.
  2. The parameterized constructor doesn't perform any validation or defensive copying.

Consider the following improvements:

  1. Remove the default constructor if it's not necessary for serialization/deserialization.
  2. In the parameterized constructor, add null checks and create defensive copies of mutable objects.

Example refactoring:

public WarpConfigEntry(Position position, Set<String> permissions) {
    this.position = Objects.requireNonNull(position, "Position cannot be null");
    this.permissions = Collections.unmodifiableSet(new HashSet<>(
        Objects.requireNonNull(permissions, "Permissions cannot be null")
    ));
}

This refactoring ensures that the object is always in a valid state and prevents external modification of the permissions set.

}
Comment on lines +8 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider enhancing encapsulation and adding documentation

The WarpConfigEntry class seems to be designed as a data container for warp configurations. While the current structure works, there are a few suggestions to improve it:

  1. Consider using private fields with getter and setter methods to improve encapsulation. This would allow for better control over access and modification of the fields.

  2. Add JavaDoc comments to explain the purpose of the class and its fields. This will help other developers understand how to use this class correctly.

  3. Consider making the class immutable by making the fields final and removing the default constructor. This can help prevent unintended modifications to the warp configurations.

Here's an example of how you could refactor the class:

/**
 * Represents a configuration entry for a warp.
 */
@Contextual
public class WarpConfigEntry {
    private final Position position;
    private final List<String> permissions;

    /**
     * Constructs a new WarpConfigEntry.
     *
     * @param position The position of the warp.
     * @param permissions The list of permissions required to access the warp.
     */
    public WarpConfigEntry(Position position, List<String> permissions) {
        this.position = position;
        this.permissions = List.copyOf(permissions); // Create an immutable copy
    }

    public Position getPosition() {
        return position;
    }

    public List<String> getPermissions() {
        return List.copyOf(permissions); // Return an immutable copy
    }
}

This refactored version provides better encapsulation, immutability, and documentation.

Comment on lines +1 to +20
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codebase verification

Clarify the responsibilities and naming conventions of Warp-related classes.

The current structure shows potential naming inconsistencies and unclear relationships between WarpConfigEntry, WarpImpl, and the Warp interface. To improve clarity and maintainability, please consider the following:

  • Review and possibly rename the Warp interface to better reflect its purpose, avoiding confusion with repository patterns.
  • Clarify the role of WarpConfigEntry in relation to WarpImpl to ensure a clear separation of concerns.
  • Assess the potential for consolidating classes where responsibilities overlap to reduce complexity and enhance cohesion.

This will help in streamlining the warp system's architecture and making the design decisions more transparent.

🔗 Analysis chain

Clarify the purpose and relationship of WarpConfigEntry in the warp system.

The purpose and usage of WarpConfigEntry in the broader context of the warp system are not immediately clear. Please provide clarification on the following points:

  1. What is the specific role of WarpConfigEntry in the warp system?
  2. How does this class relate to other classes like WarpImpl?
  3. Is there a potential for consolidation with other classes to reduce duplication and improve cohesion?

This information will help in understanding the design decisions and potentially identifying opportunities for system-wide improvements.

To help clarify the relationship between WarpConfigEntry and other parts of the warp system, please run the following script:

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Find usages and related classes of WarpConfigEntry

echo "Searching for WarpConfigEntry usages:"
rg --type java -n "WarpConfigEntry"

echo "\nSearching for other Warp-related classes:"
rg --type java -l "class.*Warp"

echo "\nSearching for WarpImpl class definition:"
rg --type java -n "class WarpImpl"

Length of output: 4339


Script:

#!/bin/bash
# Description: Find interactions between WarpImpl and WarpConfigEntry

echo "Searching for WarpImpl usages of WarpConfigEntry:"
rg --type java "WarpConfigEntry" eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpImpl.java

echo "\nSearching for methods in WarpConfigRepository that use WarpConfigEntry:"
rg --type java "WarpConfigEntry" eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpConfigRepository.java

Length of output: 1071


Script:

#!/bin/bash
# Description: Retrieve the definition of the Warp interface

echo "Fetching Warp interface definition:"
rg --type java "interface Warp" eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/

echo "\nFetching all implementations of the Warp interface:"
rg --type java "implements Warp" eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/

echo "\nFinding usages of WarpImpl in the codebase:"
rg --type java "WarpImpl" 

Length of output: 1883

Original file line number Diff line number Diff line change
@@ -1,61 +1,126 @@
package com.eternalcode.core.feature.warp;

import com.eternalcode.commons.bukkit.position.Position;
import com.eternalcode.commons.bukkit.position.PositionAdapter;
import com.eternalcode.core.configuration.ConfigurationManager;
import com.eternalcode.core.configuration.implementation.LocationsConfiguration;
import com.eternalcode.core.configuration.implementation.WarpsConfiguration;
import com.eternalcode.core.injector.annotations.Inject;
import com.eternalcode.core.injector.annotations.component.Service;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import panda.std.Option;

@Service
P1otrulla marked this conversation as resolved.
Show resolved Hide resolved
class WarpConfigRepository implements WarpRepository {

private final LocationsConfiguration locationsConfiguration;
private final WarpsConfiguration warpsConfiguration;
private final ConfigurationManager configurationManager;

@Inject
WarpConfigRepository(ConfigurationManager configurationManager, LocationsConfiguration locationsConfiguration) {
WarpConfigRepository(
ConfigurationManager configurationManager,
LocationsConfiguration locationsConfiguration,
WarpsConfiguration warpsConfiguration
) {
this.locationsConfiguration = locationsConfiguration;
this.configurationManager = configurationManager;
this.warpsConfiguration = warpsConfiguration;

this.migrateWarps();
}

@Override
public void addWarp(Warp warp) {
this.edit(warps -> warps.put(warp.getName(), PositionAdapter.convert(warp.getLocation())));
WarpConfigEntry warpConfigEntry = new WarpConfigEntry(PositionAdapter.convert(warp.getLocation()), warp.getPermissions());
this.edit(warps -> warps.put(
warp.getName(),
warpConfigEntry
));
}

@Override
public void removeWarp(String warp) {
this.edit(warps -> warps.remove(warp));
}

private void edit(Consumer<Map<String, Position>> editor) {
HashMap<String, Position> warps = new HashMap<>(this.locationsConfiguration.warps);
@Override
public void addPermissions(String warp, String... permissions) {
this.edit(warps -> {
WarpConfigEntry warpConfigEntry = warps.get(warp);
if (warpConfigEntry == null) {
return;
}

List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.addAll(List.of(permissions));

warpConfigEntry.permissions = newPermissions;
Comment on lines +61 to +64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent possible NullPointerException when permissions list is null

In the addPermissions method, if warpConfigEntry.permissions is null, creating a new ArrayList from it will throw a NullPointerException. To prevent this, ensure that the permissions list is initialized before adding new permissions.

Consider initializing the permissions list when it's null:

 if (warpConfigEntry == null) {
     return;
 }

+if (warpConfigEntry.permissions == null) {
+    warpConfigEntry.permissions = new ArrayList<>();
+}

 List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
 newPermissions.addAll(List.of(permissions));

 warpConfigEntry.permissions = newPermissions;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.addAll(List.of(permissions));
warpConfigEntry.permissions = newPermissions;
if (warpConfigEntry.permissions == null) {
warpConfigEntry.permissions = new ArrayList<>();
}
List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.addAll(List.of(permissions));
warpConfigEntry.permissions = newPermissions;

});
}
Comment on lines +53 to +66
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Refactor Suggestion: Reduce Code Duplication in Permission Methods

The addPermissions and removePermission methods have similar logic for retrieving the WarpConfigEntry, checking for null, and updating the permissions list. To improve maintainability and adhere to the DRY (Don't Repeat Yourself) principle, consider refactoring the common code into a private method.

You could implement a private method that handles permission updates, parameterized by the action to perform (add or remove):

private void updatePermissions(String warp, Consumer<List<String>> permissionUpdater) {
    this.edit(warps -> {
        WarpConfigEntry warpConfigEntry = warps.get(warp);
        if (warpConfigEntry == null) {
            return;
        }

        List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
        permissionUpdater.accept(newPermissions);
        warpConfigEntry.permissions = newPermissions;
    });
}

Then, refactor addPermissions and removePermission to use this method:

@Override
public void addPermissions(String warp, String... permissions) {
    this.updatePermissions(warp, perms -> perms.addAll(List.of(permissions)));
}

@Override
public void removePermission(String warp, String permission) {
    this.updatePermissions(warp, perms -> perms.remove(permission));
}

Also applies to: 68-82


@Override
public void removePermission(String warp, String permission) {
this.edit(warps -> {
WarpConfigEntry warpConfigEntry = warps.get(warp);

if (warpConfigEntry == null) {
return;
}

List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.remove(permission);

warpConfigEntry.permissions = newPermissions;
Comment on lines +77 to +80
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle null permissions list to avoid NullPointerException

In the removePermission method, if warpConfigEntry.permissions is null, attempting to create a new ArrayList from it will result in a NullPointerException. Ensure the permissions list is initialized before attempting to remove a permission.

Initialize the permissions list if it's null:

 if (warpConfigEntry == null) {
     return;
 }

+if (warpConfigEntry.permissions == null) {
+    warpConfigEntry.permissions = new ArrayList<>();
+}

 List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
 newPermissions.remove(permission);

 warpConfigEntry.permissions = newPermissions;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.remove(permission);
warpConfigEntry.permissions = newPermissions;
if (warpConfigEntry.permissions == null) {
warpConfigEntry.permissions = new ArrayList<>();
}
List<String> newPermissions = new ArrayList<>(warpConfigEntry.permissions);
newPermissions.remove(permission);
warpConfigEntry.permissions = newPermissions;

});
}

private void edit(Consumer<Map<String, WarpConfigEntry>> editor) {
Map<String, WarpConfigEntry> warps = new HashMap<>(this.warpsConfiguration.warps);
editor.accept(warps);

this.locationsConfiguration.warps = warps;
this.configurationManager.save(this.locationsConfiguration);
warps.forEach((key, value) -> System.out.println(key + ": " + value));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it needed here System.out.println ??

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Replace System.out.println with a proper logging framework

Using System.out.println for logging is not recommended in production code, as it doesn't provide log levels or configurable output. Consider using a logging framework like SLF4J or Log4j to handle logging more effectively.

Apply this change to use a logger:

+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;

+private static final Logger logger = LoggerFactory.getLogger(WarpConfigRepository.class);

...

-warps.forEach((key, value) -> System.out.println(key + ": " + value));
+warps.forEach((key, value) -> logger.debug("{}: {}", key, value));

Committable suggestion was skipped due to low confidence.

this.warpsConfiguration.warps.putAll(warps);
this.configurationManager.save(this.warpsConfiguration);
}

@Override
public CompletableFuture<Optional<Warp>> getWarp(String name) {
return CompletableFuture.completedFuture(Optional.of(this.locationsConfiguration.warps.get(name))
.map(location -> new WarpImpl(name, location)));
return CompletableFuture.completedFuture(Optional.of(this.warpsConfiguration.warps.get(name))
.map(warpConfigEntry -> new WarpImpl(name, warpConfigEntry.position, warpConfigEntry.permissions)));
Comment on lines +95 to +96
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Prevent NullPointerException by using Optional.ofNullable

In the getWarp method, using Optional.of(this.warpsConfiguration.warps.get(name)) can cause a NullPointerException if get(name) returns null. Replace Optional.of with Optional.ofNullable to safely handle null values.

Here's the suggested fix:

-return CompletableFuture.completedFuture(Optional.of(this.warpsConfiguration.warps.get(name))
+return CompletableFuture.completedFuture(Optional.ofNullable(this.warpsConfiguration.warps.get(name))
     .map(warpConfigEntry -> new WarpImpl(name, warpConfigEntry.position, warpConfigEntry.permissions)));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return CompletableFuture.completedFuture(Optional.of(this.warpsConfiguration.warps.get(name))
.map(warpConfigEntry -> new WarpImpl(name, warpConfigEntry.position, warpConfigEntry.permissions)));
return CompletableFuture.completedFuture(Optional.ofNullable(this.warpsConfiguration.warps.get(name))
.map(warpConfigEntry -> new WarpImpl(name, warpConfigEntry.position, warpConfigEntry.permissions)));

}

@Override
public CompletableFuture<List<Warp>> getWarps() {
return CompletableFuture.completedFuture(this.locationsConfiguration.warps.entrySet().stream()
.map(entry -> new WarpImpl(entry.getKey(), entry.getValue()))
return CompletableFuture.completedFuture(this.warpsConfiguration.warps.entrySet().stream()
.map(warpConfigEntry -> {
WarpConfigEntry warpContextual = warpConfigEntry.getValue();
return new WarpImpl(warpConfigEntry.getKey(), warpContextual.position, warpContextual.permissions);
})
.collect(Collectors.toList()));
}

private void migrateWarps() {
if (this.locationsConfiguration.warps.isEmpty()) {
return;
}
Comment on lines +110 to +112
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Handle potential NullPointerException when accessing locationsConfiguration.warps

In the migrateWarps method, if this.locationsConfiguration.warps is null, calling isEmpty() will throw a NullPointerException. Ensure that warps is not null before invoking methods on it.

Add a null check before proceeding:

 if (this.locationsConfiguration.warps == null || this.locationsConfiguration.warps.isEmpty()) {
     return;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.locationsConfiguration.warps.isEmpty()) {
return;
}
if (this.locationsConfiguration.warps == null || this.locationsConfiguration.warps.isEmpty()) {
return;
}


this.edit(warps -> warps.putAll(this.locationsConfiguration.warps
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey, entry ->
new WarpConfigEntry(entry.getValue(), new ArrayList<>()))
)
));

this.locationsConfiguration.warps.clear();
this.configurationManager.save(this.locationsConfiguration);
this.configurationManager.save(this.warpsConfiguration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@
import com.eternalcode.commons.bukkit.position.PositionAdapter;
import org.bukkit.Location;

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

class WarpImpl implements Warp {

private final String name;
private final Position position;
private List<String> permissions;

WarpImpl(String name, Position position) {
WarpImpl(String name, Position position, List<String> permissions) {
this.name = name;
this.position = position;
this.permissions = permissions;
Comment on lines +16 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider using defensive copying for the permissions list.

While the constructor correctly initializes the new permissions field, it's advisable to create a defensive copy of the input list to prevent external modifications. This ensures that the internal state of the WarpImpl object remains consistent.

Consider applying this change:

     WarpImpl(String name, Position position, List<String> permissions) {
         this.name = name;
         this.position = position;
-        this.permissions = permissions;
+        this.permissions = new ArrayList<>(permissions);
     }

Don't forget to add the import for java.util.ArrayList if it's not already present.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
WarpImpl(String name, Position position, List<String> permissions) {
this.name = name;
this.position = position;
this.permissions = permissions;
WarpImpl(String name, Position position, List<String> permissions) {
this.name = name;
this.position = position;
this.permissions = new ArrayList<>(permissions);
}

}

@Override
Expand All @@ -23,4 +28,13 @@ public String getName() {
public Location getLocation() {
return PositionAdapter.convert(this.position);
}

@Override
public List<String> getPermissions() {
return Collections.unmodifiableList(this.permissions);
}

public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
Comment on lines +37 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Ensure the permissions list is non-null and make a defensive copy in the setter

To prevent NullPointerException and protect the internal state of the object, consider adding a null check and creating a defensive copy of the permissions list in the setPermissions method.

Apply this diff to enhance the method:

 public void setPermissions(List<String> permissions) {
+    if (permissions == null) {
+        throw new IllegalArgumentException("Permissions list cannot be null");
+    }
-    this.permissions = permissions;
+    this.permissions = new ArrayList<>(permissions);
 }

Don't forget to import java.util.ArrayList if it's not already present.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public void setPermissions(List<String> permissions) {
this.permissions = permissions;
}
public void setPermissions(List<String> permissions) {
if (permissions == null) {
throw new IllegalArgumentException("Permissions list cannot be null");
}
this.permissions = new ArrayList<>(permissions);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@
import dev.triumphteam.gui.builder.item.ItemBuilder;
import dev.triumphteam.gui.guis.Gui;
import dev.triumphteam.gui.guis.GuiItem;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.bukkit.Material;
import org.bukkit.Server;
import org.bukkit.entity.Player;

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

@Service
public class WarpInventory {

Expand Down Expand Up @@ -80,7 +81,6 @@ private Gui createInventory(Language language) {
}
}


Gui gui = Gui.gui()
.title(this.miniMessage.deserialize(warpSection.title()))
.rows(rowsCount)
Expand Down Expand Up @@ -198,13 +198,11 @@ public void openInventory(Player player, Language language) {
}

public void addWarp(Warp warp) {

if (!this.warpManager.warpExists(warp.getName())) {
return;
}

for (Language language : this.translationManager.getAvailableLanguages()) {

AbstractTranslation translation = (AbstractTranslation) this.translationManager.getMessages(language);
Translation.WarpSection.WarpInventorySection warpSection = translation.warp().warpInventory();

Expand Down
Loading
Loading