Input:
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.csse3200.game.components.inventory.InventoryDisplayManager;
import com.csse3200.game.components.placeables.FenceComponent;
import com.csse3200.game.components.placeables.PlaceableEvents;
import com.csse3200.game.components.placeables.SprinklerComponent;
import com.csse3200.game.entities.Entity;
import com.csse3200.game.entities.EntityService;
import com.csse3200.game.entities.EntityType;
import com.csse3200.game.extensions.GameExtension;
import com.csse3200.game.physics.PhysicsService;
import com.csse3200.game.physics.components.ColliderComponent;
import com.csse3200.game.physics.components.HitboxComponent;
import com.csse3200.game.physics.components.PhysicsComponent;
import com.csse3200.game.rendering.RenderService;
import com.csse3200.game.services.ResourceService;
import com.csse3200.game.services.ServiceLocator;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

Context:
source/core/src/test/com/csse3200/game/entities/factories/PlaceableFactoryTest.java
package com.csse3200.game.entities.factories;

@ExtendWith(GameExtension.class)
class PlaceableFactoryTest {

	@BeforeEach
	void beforeEach() {
		ServiceLocator.registerPhysicsService(new PhysicsService());
		ServiceLocator.registerEntityService(new EntityService());
		ServiceLocator.registerRenderService(new RenderService());
		ServiceLocator.registerInventoryDisplayManager(new InventoryDisplayManager(null));

		/* Mock our ResourceService for the textures */

		ResourceService mockResourceService = mock(ResourceService.class);
		when(mockResourceService.getAsset(anyString(), any())).thenReturn(null);
		TextureAtlas mockTextureAtlas = mock(TextureAtlas.class);
		when(mockTextureAtlas.findRegion(anyString())).thenReturn(null);
		when(mockResourceService.getAsset(anyString(), eq(TextureAtlas.class))).thenReturn(mockTextureAtlas);
		ServiceLocator.registerResourceService(mockResourceService);

	}

	void baseComponentsAssertion(Entity e) {
		assertNotNull(e.getComponent(PlaceableEvents.class));
		assertNotNull(e.getComponent(PhysicsComponent.class));
		assertNotNull(e.getComponent(HitboxComponent.class));
		assertNotNull(e.getComponent(ColliderComponent.class));
	}

	@Test
	void shouldCreateBase() {
		Entity e = PlaceableFactory.createBasePlaceable(EntityType.TRACTOR);
		this.baseComponentsAssertion(e);
	}

	@Test
	void shouldCreateFence() {
		Entity e = PlaceableFactory.createFence();

		assertSame(EntityType.FENCE, e.getType());
		assertNotNull(e.getComponent(FenceComponent.class));
		this.baseComponentsAssertion(e);
	}

	@Test
	void shouldCreateGate() {
		Entity e = PlaceableFactory.createGate();

		assertSame(EntityType.GATE, e.getType());

source/core/src/main/com/csse3200/game/physics/components/ColliderComponent.java
public class ColliderComponent extends Component {
	private static final Logger logger = LoggerFactory.getLogger(ColliderComponent.class);

	private final FixtureDef fixtureDef;
	private Fixture fixture;

	public ColliderComponent() {
		fixtureDef = new FixtureDef();
	}

	@Override
	public void create() {
		if (fixtureDef.shape == null) {
			logger.trace("{} Setting default bounding box", this);
			fixtureDef.shape = makeBoundingBox();
		}

		Body physBody = entity.getComponent(PhysicsComponent.class).getBody();
		fixture = physBody.createFixture(fixtureDef);
	}

	/**
	 * Set physics as a box with a given size. Box is centered around the entity.
	 *
	 * @param size size of the box
	 * @return self
	 */
	public ColliderComponent setAsBox(Vector2 size) {
		return setAsBox(size, entity.getCenterPosition());
	}

	/**
	 * Set physics as a box with a given size. Box is aligned based on alignment.
	 *
	 * @param size   size of the box
	 * @param alignX how to align x relative to entity
	 * @param alignY how to align y relative to entity
	 * @return self
	 */
	public ColliderComponent setAsBoxAligned(Vector2 size, AlignX alignX, AlignY alignY) {
		Vector2 position = new Vector2();
		switch (alignX) {
			case LEFT:
				position.x = size.x / 2;
				break;
			case CENTER:
				position.x = entity.getCenterPosition().x;
				break;
			case RIGHT:
				position.x = entity.getScale().x - (size.x / 2);
				break;
		}

		switch (alignY) {
			case BOTTOM:
				position.y = size.y / 2;
				break;
			case CENTER:
				position.y = entity.getCenterPosition().y;
				break;
			case TOP:
				position.y = entity.getScale().y - (size.y / 2);
				break;
		}

		return setAsBox(size, position);
	}

	/**
	 * Set physics as a box with a given size and local position. Box is centered around the position.
	 *
	 * @param size     size of the box
	 * @param position position of the box center relative to the entity.
	 * @return self
	 */
	public ColliderComponent setAsBox(Vector2 size, Vector2 position) {
		PolygonShape bbox = new PolygonShape();
		bbox.setAsBox(size.x / 2, size.y / 2, position, 0f);
		setShape(bbox);
		return this;
	}

	/**
	 * Set friction. This affects the object when touching other objects, but does not affect friction
	 * with the ground.
	 *
	 * @param friction friction, default = 0
	 * @return self
	 */
	public ColliderComponent setFriction(float friction) {
		if (fixture == null) {
			fixtureDef.friction = friction;
		} else {
			fixture.setFriction(friction);
		}
		return this;
	}

	/**
	 * Set whether this physics component is a sensor. Sensors don't collide with other objects but
	 * still trigger collision events. See: https://www.iforce2d.net/b2dtut/sensors
	 *
	 * @param isSensor true if sensor, false if not. default = false.
	 * @return self
	 */
	public ColliderComponent setSensor(boolean isSensor) {
		if (fixture == null) {
			fixtureDef.isSensor = isSensor;
		} else {
			fixture.setSensor(isSensor);
		}
		return this;
	}

	/**
	 * Set density
	 *
	 * @param density Density and size of the physics component determine the object's mass. default =
	 *                0
	 * @return self
	 */
	public ColliderComponent setDensity(float density) {
		if (fixture == null) {
			fixtureDef.density = density;
		} else {
			fixture.setDensity(density);
		}
		return this;
	}

	/**
	 * Set restitution
	 *
	 * @param restitution restitution is the 'bounciness' of an object, default = 0
	 * @return self
	 */
	public ColliderComponent setRestitution(float restitution) {
		if (fixture == null) {
			fixtureDef.restitution = restitution;
		} else {
			fixture.setRestitution(restitution);
		}
		return this;
	}

	/**
	 * Set shape
	 *
	 * @param shape shape, default = bounding box the same size as the entity
	 * @return self
	 */
	public ColliderComponent setShape(Shape shape) {
		if (fixture == null) {
			fixtureDef.shape = shape;
		} else {
			logger.error("{} Cannot set Collider shape after create(), ignoring.", this);
		}
		return this;
	}

	/**
	 * @return Physics fixture of this collider. Null before created()
	 */
	public Fixture getFixture() {
		return fixture;
	}

	/**
	 * Set the collider layer, used in collision logic
	 *
	 * @param layerMask Bitmask of {@link PhysicsLayer} this collider belongs to
	 * @return self
	 */
	public ColliderComponent setLayer(short layerMask) {
		if (fixture == null) {
			fixtureDef.filter.categoryBits = layerMask;
		} else {
			Filter filter = fixture.getFilterData();
			filter.categoryBits = layerMask;
			fixture.setFilterData(filter);
		}
		return this;
	}

	/**
	 * @return The {@link PhysicsLayer} this collider belongs to
	 */
	public short getLayer() {
		if (fixture == null) {
			return fixtureDef.filter.categoryBits;
		}
		return fixture.getFilterData().categoryBits;
	}

	@Override
	public void dispose() {
		super.dispose();
		Body physBody = entity.getComponent(PhysicsComponent.class).getBody();
		if (physBody.getFixtureList().contains(fixture, true)) {
			physBody.destroyFixture(fixture);
		}
	}

	private Shape makeBoundingBox() {
		PolygonShape bbox = new PolygonShape();
		Vector2 center = entity.getScale().scl(0.5f);
		bbox.setAsBox(center.x, center.y, center, 0f);
		return bbox;
	}
}

source/core/src/main/com/csse3200/game/services/ResourceService.java
public class ResourceService implements Disposable {

	private static final Logger logger = LoggerFactory.getLogger(ResourceService.class);
	private final AssetManager assetManager;

	public ResourceService() {
		this(new AssetManager());
	}

	/**
	 * Initialise this ResourceService to use the provided AssetManager.
	 *
	 * @param assetManager AssetManager to use in this service.
	 * @requires assetManager != null
	 */
	public ResourceService(AssetManager assetManager) {
		this.assetManager = assetManager;
	}

	/**
	 * Load an asset from a file.
	 *
	 * @param filename Asset path
	 * @param type     Class to load into
	 * @param <T>      Type of class to load into
	 * @return Instance of class loaded from path
	 * @see AssetManager#get(String, Class)
	 */
	public <T> T getAsset(String filename, Class<T> type) {
		return assetManager.get(filename, type);
	}

	/**
	 * Check if an asset has been loaded already
	 *
	 * @param resourceName path of the asset
	 * @param type         Class type of the asset
	 * @param <T>          Type of the asset
	 * @return true if asset has been loaded, false otherwise
	 * @see AssetManager#contains(String)
	 */
	public <T> boolean containsAsset(String resourceName, Class<T> type) {
		return assetManager.contains(resourceName, type);
	}

	/**
	 * Returns the loading completion progress as a percentage.
	 *
	 * @return progress
	 */
	public int getProgress() {
		return (int) (assetManager.getProgress() * 100);
	}

	/**
	 * Blocking call to load all assets.
	 *
	 * @see AssetManager#finishLoading()
	 */
	public void loadAll() {
		logger.debug("Loading all assets");
		try {
			assetManager.finishLoading();
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
	}

	/**
	 * Loads assets for the specified duration in milliseconds.
	 *
	 * @param duration duration to load for
	 * @return finished loading
	 * @see AssetManager#update(int)
	 */
	public boolean loadForMillis(int duration) {
		logger.debug("Loading assets for {} ms", duration);
		try {
			return assetManager.update(duration);
		} catch (Exception e) {
			logger.error(e.getMessage());
		}
		return assetManager.isFinished();
	}

	/**
	 * Clears all loaded assets and assets in the preloading queue.
	 *
	 * @see AssetManager#clear()
	 */
	public void clearAllAssets() {
		logger.debug("Clearing all assets");
		assetManager.clear();
	}

	/**
	 * Loads a single asset into the asset manager.
	 *
	 * @param assetName asset name
	 * @param type      asset type
	 * @param <T>       type
	 */
	private <T> void loadAsset(String assetName, Class<T> type) {
		logger.debug("Loading {}: {}", type.getSimpleName(), assetName);
		try {
			assetManager.load(assetName, type);
		} catch (Exception e) {
			logger.error("Could not load {}: {}", type.getSimpleName(), assetName);
		}
	}

	/**
	 * Loads multiple assets into the asset manager.
	 *
	 * @param assetNames list of asset names
	 * @param type       asset type
	 * @param <T>        type
	 */
	private <T> void loadAssets(String[] assetNames, Class<T> type) {
		for (String resource : assetNames) {
			loadAsset(resource, type);
		}
	}

	/**
	 * Loads a list of texture assets into the asset manager.
	 *
	 * @param textureNames texture filenames
	 */
	public void loadTextures(String[] textureNames) {
		loadAssets(textureNames, Texture.class);
	}

	/**
	 * Loads a list of texture atlas assets into the asset manager.
	 *
	 * @param textureAtlasNames texture atlas filenames
	 */
	public void loadTextureAtlases(String[] textureAtlasNames) {
		loadAssets(textureAtlasNames, TextureAtlas.class);
	}

	/**
	 * Loads a list of sounds into the asset manager.
	 *
	 * @param soundNames sound filenames
	 */
	public void loadSounds(String[] soundNames) {
		loadAssets(soundNames, Sound.class);
	}

	/**
	 * Loads a list of music assets into the asset manager.
	 *
	 * @param musicNames music filenames
	 */
	public void loadMusic(String[] musicNames) {
		loadAssets(musicNames, Music.class);
	}

	/**
	 * Loads a list of particle effect assets into the asset manager
	 *
	 * @param particleNames particle effect filenames
	 */
	public void loadParticleEffects(String[] particleNames) {
		loadAssets(particleNames, ParticleEffect.class);
	}

	public void loadSkins(String[] skinNames) {
		loadAssets(skinNames, Skin.class);
	}

	/**
	 * Disposes of assets and all of their dependencies from the resource manager
	 *
	 * @param assetNames list of asset names to dispose of
	 */
	public void unloadAssets(String[] assetNames) {
		for (String assetName : assetNames) {
			logger.debug("Unloading {}", assetName);
			try {
				assetManager.unload(assetName);
			} catch (Exception e) {
				logger.error("Could not unload {}", assetName);
			}
		}
	}

	/**
	 * Disposes of the resource service, clearing the asset manager which clears and disposes of all assets and clears
	 * the preloading queue
	 */
	@Override
	public void dispose() {
		assetManager.clear();
	}
}

source/core/src/test/com/csse3200/game/extensions/GameExtension.java
public class GameExtension implements AfterEachCallback, BeforeAllCallback {
	private Application game;

	@Override
	public void beforeAll(ExtensionContext context) {
		// 'Headless' back-end, so no rendering happens
		game = new HeadlessApplication(new ApplicationAdapter() {
		});

		// Mock any calls to OpenGL
		Gdx.gl20 = Mockito.mock(GL20.class);
		Gdx.gl = Gdx.gl20;
	}

	@Override
	public void afterEach(ExtensionContext context) {
		// Clear the global state from the service locator
		ServiceLocator.clear();
	}
}

source/core/src/main/com/csse3200/game/rendering/RenderService.java
public class RenderService implements Disposable {
	private static final int INITIAL_LAYER_CAPACITY = 4;
	private static final int INITIAL_CAPACITY = 4;
	private Stage stage;
	private DebugRenderer debugRenderer;

	/**
	 * Map from layer to list of renderables, allows us to render each layer in the correct order
	 */
	private final SortedIntMap<Array<Renderable>> renderables =
			new SortedIntMap<>(INITIAL_LAYER_CAPACITY);

	/**
	 * Register a new renderable.
	 *
	 * @param renderable new renderable.
	 */
	public void register(Renderable renderable) {
		int layerIndex = renderable.getLayer();
		if (!renderables.containsKey(layerIndex)) {
			renderables.put(layerIndex, new Array<>(INITIAL_CAPACITY));
		}
		Array<Renderable> layer = renderables.get(layerIndex);
		layer.add(renderable);
	}

	/**
	 * Unregister a renderable.
	 *
	 * @param renderable renderable to unregister.
	 */
	public void unregister(Renderable renderable) {
		Array<Renderable> layer = renderables.get(renderable.getLayer());
		if (layer != null) {
			layer.removeValue(renderable, true);
		}
	}

	/**
	 * Trigger rendering on the given batch. This should be called only from the main renderer.
	 *
	 * @param batch batch to render to.
	 */
	public void render(SpriteBatch batch) {
		for (Array<Renderable> layer : renderables) {
			// Sort into rendering order
			layer.sort();

			for (Renderable renderable : layer) {
				renderable.render(batch);
			}
		}
	}

	public void setStage(Stage stage) {
		this.stage = stage;
	}

	public Stage getStage() {
		return stage;
	}

	public void setDebug(DebugRenderer debugRenderer) {
		this.debugRenderer = debugRenderer;
	}

	public DebugRenderer getDebug() {
		return debugRenderer;
	}

	@Override
	public void dispose() {
		renderables.clear();
	}
}

source/core/src/main/com/csse3200/game/physics/PhysicsService.java
public class PhysicsService {
	private final PhysicsEngine engine;

	public PhysicsService() {
		this(new PhysicsEngine());
	}

	public PhysicsService(PhysicsEngine engine) {
		this.engine = engine;
	}

	public PhysicsEngine getPhysics() {
		return engine;
	}
}

source/core/src/main/com/csse3200/game/physics/components/HitboxComponent.java
public class HitboxComponent extends ColliderComponent {
	@Override
	public void create() {
		setSensor(true);
		super.create();
	}
}

source/core/src/main/com/csse3200/game/entities/EntityType.java
public enum EntityType {
	PLAYER(0),
	TRACTOR(0),
	PLANT(10),
	DECAYING_PLANT(-10),
	TILE(0),  // This is team 7 stuff
	COW(0),
	CHICKEN(0),
	ASTROLOTL(0),
	OXYGEN_EATER(-10),
	DRAGONFLY(0),
	BAT(0),
	ITEM(0),
	QUESTGIVER(0),
	QUESTGIVER_INDICATOR(0),
	CHEST(0),
	FENCE(0),
	GATE(0),
	SPRINKLER(0),
	PUMP(0),
	LIGHT(0),
	SHIP(0),
	SHIP_DEBRIS(0),
	SHIP_PART_TILE(0),
	SHIP_EATER(0),
	DUMMY(0), // Used for testing
	FIRE_FLIES(0),
	GOLDEN_STATUE(0);

	// Negative rate for consumption, positive for production of oxygen
	private final float hourlyOxygenRate;

	/**
	 * A parent like category for Placeable types.
	 * Allows other Placeable entities to query if this is of the same placeable category.
	 */
	private PlaceableCategory placeableCategory;

	EntityType(float hourlyOxygenRate) {
		this.hourlyOxygenRate = hourlyOxygenRate;
	}

	/**
	 * Getter method for the oxygen consumption/production rate
	 * of a given entity type.
	 *
	 * @return the hourly oxygen rate of the entity type.
	 */
	public float getOxygenRate() {
		return hourlyOxygenRate;
	}

	/**
	 * Getter for the placeableCategory
	 *
	 * @return this placeableCategory
	 */
	public PlaceableCategory getPlaceableCategory() {
		return placeableCategory;
	}

	/**
	 * Setter for the placeableCategory
	 *
	 * @param placeableCategory A category that encompasses the Placeable type.
	 */
	public void setPlaceableCategory(PlaceableCategory placeableCategory) {
		this.placeableCategory = placeableCategory;
	}
}

source/core/src/main/com/csse3200/game/components/placeables/FenceComponent.java
public class FenceComponent extends Component {

	private ConnectedEntityUtility connectedEntityUtility;

	/**
	 * Texture paths for different fence orientation.
	 * The order of this array is very important, correct order ensures a fence gets the correct texture.
	 */
	private static final String[] textures_fence = {
			"images/placeable/fences/f.png",
			"images/placeable/fences/f_l.png",
			"images/placeable/fences/f_r.png",
			"images/placeable/fences/f_r_l.png",
			"images/placeable/fences/f_d.png",
			"images/placeable/fences/f_d_l.png",
			"images/placeable/fences/f_r_d.png",
			"images/placeable/fences/f_r_d_l.png",
			"images/placeable/fences/f_u.png",
			"images/placeable/fences/f_l_u.png",
			"images/placeable/fences/f_r_u.png",
			"images/placeable/fences/f_r_l_u.png",
			"images/placeable/fences/f_d_u.png",
			"images/placeable/fences/f_d_l_u.png",
			"images/placeable/fences/f_r_d_u.png",
			"images/placeable/fences/f_r_d_l_u.png",
	};

	private static final String IMAGE_GATE_OPEN = "images/placeable/fences/g_r_l_o.png";
	private static final String IMAGE_GATE_DUPLICATE = "images/placeable/fences/g_d_u_o.png";

	private static final String[] textures_gate_open = {
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_DUPLICATE,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_DUPLICATE,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_DUPLICATE,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
			IMAGE_GATE_OPEN,
	};

	private static final String GATE_RIGHT_LEFT_CLOSED = "images/placeable/fences/g_r_l.png";
	private static final String GATE_DOWN_UP_CLOSED = "images/placeable/fences/g_d_u.png";

	private static final String[] textures_gate_closed = {
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_DOWN_UP_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_DOWN_UP_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_DOWN_UP_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
			GATE_RIGHT_LEFT_CLOSED,
	};

	private boolean isGate = false;
	private boolean isOpen = false;

	public FenceComponent(boolean isGate) {
		this.isGate = isGate;
	}

	public FenceComponent() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void create() {
		this.connectedEntityUtility = new ConnectedEntityUtility(entity);
		entity.getEvents().addListener("onDestroy", this::onDestroy);

		if (isGate) {
			configGate();
			entity.getEvents().addListener("interact", this::toggleGate);
			entity.getEvents().addListener("reconfigure", this::configGate);
			this.connectedEntityUtility.notifyAdjacent();
			return;
		}

		configFence();
		entity.getEvents().addListener("reconfigure", this::configFence);

		// Notify all adjacent of this placement
		this.connectedEntityUtility.notifyAdjacent();
	}

	/**
	 * Sets this fence's texture orientation based off the adjacent fences.
	 */
	public void configFence() {
		// get index into texture array based on surrounding sprinklers
		byte orientation = this.connectedEntityUtility.getAdjacentBitmap();
		// now set the texture.
		entity.getComponent(DynamicTextureRenderComponent.class).setTexture(textures_fence[orientation]);
	}

	/**
	 * Sets this gate's texture orientation based off the adjacent fences.
	 */
	public void configGate() {
		// get index into texture array based on surrounding sprinklers
		byte orientation = this.connectedEntityUtility.getAdjacentBitmap();
		// now set the texture.
		if (isOpen) {
			entity.getComponent(DynamicTextureRenderComponent.class).setTexture(textures_gate_open[orientation]);
			return;
		}
		entity.getComponent(DynamicTextureRenderComponent.class).setTexture(textures_gate_closed[orientation]);
	}

	private void toggleGate() {

		/* Play the interact sound */
		try {
			ServiceLocator.getSoundService().getEffectsMusicService().play(EffectSoundFile.GATE_INTERACT);
		} catch (Exception e) {
			/* Catch exception when sound service not available */
		}

		isOpen = !isOpen;

		if (isOpen) {
			this.entity.getComponent(ColliderComponent.class).setSensor(true);
			configGate();
			return;
		}
		this.entity.getComponent(ColliderComponent.class).setSensor(false);
		// Update our texture accordingly
		configGate();
	}

	/**
	 * Destroys the connections to neighbouring fences or gates
	 */
	private void onDestroy() {
		entity.getEvents().trigger("destroyConnections");
	}
}

Answer: assertNotNull(e.getComponent(FenceComponent.class));

<think>
The test method shouldCreateGate mirrors shouldCreateFence: after checking the entity type, it verifies that the gate entity has a FenceComponent (used for gates) and then checks base components. Therefore the next line is the assertion that the FenceComponent is present.
</think>
assertNotNull(e.getComponent(FenceComponent.class));


Input:
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.exc.MismatchedInputException;
import de.gtarc.opaca.api.RuntimePlatformApi;
import de.gtarc.opaca.platform.auth.JwtUtil;
import de.gtarc.opaca.platform.auth.TokenUserDetailsService;
import de.gtarc.opaca.platform.containerclient.ContainerClient;
import de.gtarc.opaca.platform.containerclient.DockerClient;
import de.gtarc.opaca.platform.containerclient.KubernetesClient;
import de.gtarc.opaca.platform.session.SessionData;
import de.gtarc.opaca.model.*;
import de.gtarc.opaca.util.ApiProxy;
import lombok.extern.java.Log;
import de.gtarc.opaca.util.EventHistory;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import jakarta.annotation.PostConstruct;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.http.ResponseEntity;
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;

Context:
opaca-platform/src/main/java/de/gtarc/opaca/platform/PlatformImpl.java
package de.gtarc.opaca.platform;

/**
 * This class provides the actual implementation of the API routes. Might also be split up
 * further, e.g. for agent-forwarding, container-management, and linking to other platforms.
 */
@Log
@Component

opaca-model/src/main/java/de/gtarc/opaca/util/EventHistory.java
public class EventHistory {
    private static final EventHistory INSTANCE = new EventHistory();
    private final List<Event> events = Collections.synchronizedList(new LinkedList<>());

    private EventHistory() {
    }

    public static EventHistory getInstance() {
        return INSTANCE;
    }

    public void addEvent(Event entry) {
        if (entry != null) {
            events.add(entry);
        }
    }

    public List<Event> getEvents() {
        return List.copyOf(events);
    }
}

opaca-model/src/main/java/de/gtarc/opaca/api/RuntimePlatformApi.java
public interface RuntimePlatformApi extends CommonApi {

    /**
     * Get full information on the Runtime Platform, including all running Agent Containers and
     * Agents, connected other platforms, etc.
     *
     * REST Route: GET /info
     *
     * @return Extensive information on the platform and its containers and agents.
     */
    RuntimePlatform getPlatformInfo() throws IOException;

    /**
     * Get history of "events" that occurred in this runtime platform
     *
     * REST: GET /history
     *
     * @return list of recent events, most-recent last
     */
    List<Event> getHistory() throws IOException;

    /*
     * AUTHENTICATION
     */

    /**
     * Retrieve Access Token for given user to be passed as header for secured routes.
     *
     * REST: GET /login
     *
     * @param loginParams Bundles the username and password in the request body
     * @return JWT access token
     */
    String login(Login loginParams) throws IOException;

    /*
     * AGENT CONTAINER ROUTES
     */

    // see CommonApi.java

    /*
     * CONTAINER MANAGEMENT
     */

    /**
     * Deploy a container to the runtime Platform. Check requirements, get actual docker image, and
     * deploy to Docker/Kubernetes, then return the ID of the running Agent Container.
     *
     * REST: POST /containers
     *
     * @param container The container to start
     * @return ID of the started container
     */
    String addContainer(PostAgentContainer container) throws IOException;

    /**
     * Get descriptions of all currently running Agent Containers.
     *
     * REST: GET /containers
     *
     * @return List of all running containers
     */
    List<AgentContainer> getContainers() throws IOException;

    /**
     * Get description of a single Agent Container
     *
     * REST: GET /containers/{id}
     *
     * @param containerId ID of the container
     * @return Description of the container
     */
    AgentContainer getContainer(String containerId) throws IOException;

    /**
     * Remove an Agent Container from the platform.
     *
     * REST: DELETE /containers/{id}
     *
     * @param containerId ID of the container
     * @return Removal successful?
     */
    boolean removeContainer(String containerId) throws IOException;

    /**
     * Notify Platform of changes in one of its own containers, triggering an update by calling the /info route.
     * Can be called by the container itself, or by some other entity or the user.
     *
     * REST: POST /containers/notify
     *
     * @param containerId The ID of the container to update.
     * @return true/false depending on whether the update was successful (false = container not reachable, removed)
     */
    boolean notifyUpdateContainer(String containerId) throws IOException;

    /*
     * CONNECTIONS MANAGEMENT
     */

    /**
     * Connect this platform to another platform, running on a different host.
     * The connection will be bi-directional.
     *
     * REST: POST /connections
     *
     * @param loginConnection Stores the username, password, and url to connect to
     * @return Connection successful?
     */
    boolean connectPlatform(LoginConnection loginConnection) throws IOException;

    /**
     * Get list uf base-URLs of connected other Runtime Platforms
     *
     * REST: GET /connections
     *
     * @return List of base-URLs of connected Platforms
     */
    List<String> getConnections() throws IOException;

    /**
     * Disconnect a previously connected Platform, in both directions.
     *
     * REST: DELETE /connections
     *
     * @param url The base-URL of the platform to disconnect.
     * @return Disconnect successful?
     */
    boolean disconnectPlatform(String url) throws IOException;

    /**
     * Notify Platform of changes in a connected Platform, triggering an update by calling the /info route.
     * Can be called by the platform itself, or by some other entity or the user.
     *
     * REST: POST /connections/notify
     *
     * @param platformUrl The URL of the platform to update.
     * @return true/false depending on whether the update was successful (false = platform not reachable, removed)
     */
    boolean notifyUpdatePlatform(String platformUrl) throws IOException;

}

opaca-platform/src/main/java/de/gtarc/opaca/platform/containerclient/ContainerClient.java
public interface ContainerClient {

    /**
     * Initialize the client using properties in the given configuration file. Different clients may
     * require different attributes.
     */
    void initialize(PlatformConfig config, SessionData SessionData);

    /**
     * Test connection to the Backend, e.g. Docker or Kubernetes. This is called right after initialize,
     * but made this a separate method to make it more explicit. This should raise an Exception (with
     * some details, if possible) that will then crash the Service right after the start.
     */
    void testConnectivity();

    /**
     * Start a container with the given container ID (for later reference) and image name. If all goes well,
     * return nothing, otherwise raise an appropriate exception.
     *
     * @return Port Mappings
     */
    AgentContainer.Connectivity startContainer(String containerId, String token, PostAgentContainer container) throws IOException, NoSuchElementException;

    /**
     * Stop the agent container with the given ID.
     */
    void stopContainer(String containerId) throws IOException;

    /**
     * Get the URL where the container can be reached for forwarding requests.
     */
    String getUrl(String containerId);

}

opaca-platform/src/main/java/de/gtarc/opaca/platform/auth/TokenUserDetailsService.java
@Service
public class TokenUserDetailsService implements UserDetailsService {

    @Autowired
    private SessionData sessionData;

    @Autowired
    private PlatformConfig config;

    private Map<String, String> userCredentials;

    @PostConstruct
	public void postConstruct() {
		userCredentials = sessionData.userCredentials;
        if (userCredentials.isEmpty()) {
            addUser(config.usernamePlatform, config.passwordPlatform);
        }
	}

    /** Returns the user as a standardized 'User' object */
    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        if (userCredentials.containsKey(username)) {
            return new User(username, userCredentials.get(username), List.of());
        } else {
            throw new UsernameNotFoundException("User not found: " + username);
        }
    }

    /**
     * Adding user to the credentials map. Those user credentials can be a human's credentials
     * as [username:password] or agent container credentials as [containerID:containerID].
     */
    public void addUser(String username, String password) {
        userCredentials.put(username, password);
    }

    public void removeUser(String username) {
        userCredentials.remove(username);
    }
}

opaca-platform/src/main/java/de/gtarc/opaca/platform/auth/JwtUtil.java
@Service
public class JwtUtil {

    @Autowired
    private PlatformConfig config;

    @Autowired
    private TokenUserDetailsService tokenUserDetailsService;

    public String generateTokenForUser(String username, String password) {
        UserDetails userDetails = tokenUserDetailsService.loadUserByUsername(username);
        if (userDetails.getPassword().equals(password)) {
            return createToken(username, Duration.ofHours(1));
        } else {
            throw new BadCredentialsException("Wrong password");
        }
    }

    public String generateTokenForAgentContainer(String containerId) {
        // TODO expiration date of 10 hours does not work for agent containers, should be able to run for weeks
        return createToken(containerId, Duration.ofHours(10));
    }

    private String createToken(String username, Duration duration) {
        return Jwts.builder().setSubject(username)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + duration.toMillis()))
                .signWith(SignatureAlgorithm.HS256, config.secret).compact();
    }

    public boolean validateToken(String token, UserDetails userDetails) {
        return (getUsernameFromToken(token).equals(userDetails.getUsername()) &&
                getExpirationDateFromToken(token).after(new Date()));
    }

    public String getUsernameFromToken(String token) {
        return Jwts.parser().setSigningKey(config.secret).parseClaimsJws(token).getBody().getSubject();
    }

    private Date getExpirationDateFromToken(String token) {
        return Jwts.parser().setSigningKey(config.secret).parseClaimsJws(token).getBody().getExpiration();
    }

}

opaca-platform/src/main/java/de/gtarc/opaca/platform/containerclient/DockerClient.java
@Log
public class DockerClient implements ContainerClient {

    private PlatformConfig config;

    /** Client for accessing (remote) Docker runtime */
    private com.github.dockerjava.api.DockerClient dockerClient;

    /** additional Docker-specific information on agent containers */
    private Map<String, DockerContainerInfo> dockerContainers;

    /** Available Docker Auth */
    private Map<String, AuthConfig> auth;

    /** Set of already used ports on target Docker host */
    private Set<Integer> usedPorts;

    @Data
    @AllArgsConstructor
    public static class DockerContainerInfo {
        String containerId;
        AgentContainer.Connectivity connectivity;
    }

    @Override
    public void initialize(PlatformConfig config, SessionData sessionData) {
        this.config = config;

        DockerClientConfig dockerConfig = DefaultDockerClientConfig.createDefaultConfigBuilder()
                .withDockerHost(getDockerHost())
                .build();

        DockerHttpClient dockerHttpClient = new ApacheDockerHttpClient.Builder()
                .dockerHost(dockerConfig.getDockerHost())
                .sslConfig(dockerConfig.getSSLConfig())
                .maxConnections(100)
                .connectionTimeout(Duration.ofSeconds(30))
                .responseTimeout(Duration.ofSeconds(45))
                .build();

        this.auth = loadDockerAuth();
        this.dockerClient = DockerClientImpl.getInstance(dockerConfig, dockerHttpClient);
        this.dockerContainers = sessionData.dockerContainers;
        this.usedPorts = sessionData.usedPorts;
    }

    @Override
    public void testConnectivity() {
        this.dockerClient.listContainersCmd().exec();
    }

    @Override
    public AgentContainer.Connectivity startContainer(String containerId, String token, PostAgentContainer container) throws IOException, NoSuchElementException {
        var image = container.getImage();
        var imageName = image.getImageName();
        var extraPorts = image.getExtraPorts();

        try {
            if (! isImagePresent(imageName)) {
                pullDockerImage(imageName);
            }

            // port mappings for API- and Extra-Ports
            var newPorts = new HashSet<Integer>();
            Map<Integer, Integer> portMap = Stream.concat(Stream.of(image.getApiPort()), extraPorts.keySet().stream())
                    .collect(Collectors.toMap(p -> p, p -> reserveNextFreePort(p, newPorts)));
            // translate to Docker PortBindings (incl. ExposedPort descriptions)
            List<PortBinding> portBindings = portMap.entrySet().stream()
                    .map(e -> PortBinding.parse(e.getValue() + ":" + e.getKey() + "/" + getProtocol(e.getKey(), image)))
                    .collect(Collectors.toList());

            log.info("Creating Container...");
            CreateContainerResponse res = dockerClient.createContainerCmd(imageName)
                    .withEnv(buildEnv(containerId, token, image.getParameters(), container.getArguments()))
                    .withHostConfig(HostConfig.newHostConfig().withPortBindings(portBindings))
                    .withExposedPorts(portBindings.stream().map(PortBinding::getExposedPort).collect(Collectors.toList()))
                    .exec();

            log.info(String.format("Result: %s", res));

            log.info("Starting Container...");
            dockerClient.startContainerCmd(res.getId()).exec();

            var connectivity = new AgentContainer.Connectivity(
                    getContainerBaseUrl(),
                    portMap.get(image.getApiPort()),
                    extraPorts.keySet().stream().collect(Collectors.toMap(portMap::get, extraPorts::get))
            );
            dockerContainers.put(containerId, new DockerContainerInfo(res.getId(), connectivity));
            usedPorts.addAll(newPorts);

            return connectivity;

        } catch (NotFoundException e) {
            // might theoretically happen if image is deleted between pull and run...
            log.warning("Image not found: " + imageName);
            throw new NoSuchElementException("Image not found: " + imageName);
        } catch (DockerException e) {
            throw new IOException("Failed to start Docker container.", e);
        }
    }

    private String[] buildEnv(String containerId, String token, List<ImageParameter> parameters, Map<String, String> arguments) {
        return config.buildContainerEnv(containerId, token, parameters, arguments).entrySet().stream()
                .map(e -> String.format("%s=%s", e.getKey(), e.getValue()))
                .toArray(String[]::new);
    }

    @Override
    public void stopContainer(String containerId) throws IOException {
        try {
            var containerInfo = dockerContainers.remove(containerId);
            usedPorts.remove(containerInfo.connectivity.getApiPortMapping());
            usedPorts.removeAll(containerInfo.connectivity.getExtraPortMappings().keySet());
            dockerClient.stopContainerCmd(containerInfo.containerId).exec();
        } catch (NotModifiedException e) {
            var msg = "Could not stop Container " + containerId + "; already stopped?";
            log.warning(msg);
            throw new NoSuchElementException(msg);
        }
        // TODO possibly that the container refuses being stopped? call "kill" instead? how to test this?
    }

    @Override
    public String getUrl(String containerId) {
        var conn = dockerContainers.get(containerId).connectivity;
        return conn.getPublicUrl() + ":" + conn.getApiPortMapping();
    }

    private String getProtocol(int port, AgentContainerImage image) {
        if (image.getExtraPorts().containsKey(port)) {
            String protocol = image.getExtraPorts().get(port).getProtocol();
            return "udp".equalsIgnoreCase(protocol) ? "udp" : "tcp";
        } else {
            return "tcp";
        }
    }

    private String getLocalDockerHost() {
        // Just differentiates between Windows and others for now
        return SystemUtils.IS_OS_WINDOWS
                ? "npipe:////./pipe/docker_engine"
                : "unix:///var/run/docker.sock";
    }

    private String getDockerHost() {
        return Strings.isNullOrEmpty(config.remoteDockerHost)
                ? getLocalDockerHost()
                : String.format("tcp://%s:%s", config.remoteDockerHost, config.remoteDockerPort);
    }

    private String getContainerBaseUrl() {
        return Strings.isNullOrEmpty(config.remoteDockerHost)
                ? config.getOwnBaseUrl().replaceAll(":\\d+$", "")
                : String.format("http://%s", config.remoteDockerHost);
    }

    /**
     * Try to pull the Docker image from registry where it can be found (according to image name).
     * Raise NoSuchElementException if image can not be pulled for whatever reason.
     */
    private void pullDockerImage(String imageName) {
        log.info("Pulling Image..." + imageName);
        try {
            var registry = imageName.split("/")[0];
            dockerClient.pullImageCmd(imageName)
                    .withAuthConfig(this.auth.get(registry))
                    .exec(new PullImageResultCallback())
                    .awaitCompletion();
        } catch (InterruptedException e) {
            log.warning(e.getMessage());
        } catch (InternalServerErrorException e) {
            log.severe("Pull Image failed: " + e.getMessage());
            throw new NoSuchElementException("Failed to Pull image: " + e.getMessage());
        }
    }

    /**
     * Starting from the given preferred port, get and reserve the next free port.
     */
    private int reserveNextFreePort(int port, Set<Integer> newPorts) {
        while (!isPortAvailable(port, newPorts)) ++port;
        newPorts.add(port);
        return port;
    }

    private boolean isPortAvailable(int port, Set<Integer> newPorts) {
        if (usedPorts.contains(port) || newPorts.contains(port)) return false;
        try (var s1 = new ServerSocket(port); var s2 = new DatagramSocket(port)) {
            return true;
        } catch (IOException e) {
            return false;
        }
    }

    private boolean isImagePresent(String imageName) {
        try {
            this.dockerClient.inspectImageCmd(imageName).exec();
            return true;
        } catch (NotFoundException e) {
            return false;
        }
    }

    private Map<String, AuthConfig> loadDockerAuth() {
        return config.loadDockerAuth().stream().collect(Collectors.toMap(
                PlatformConfig.ImageRegistryAuth::getRegistry,
                x -> new AuthConfig()
                        .withRegistryAddress(x.getRegistry())
                        .withUsername(x.getLogin())
                        .withPassword(x.getLogin())));
    }
}

opaca-model/src/main/java/de/gtarc/opaca/util/ApiProxy.java
public class ApiProxy implements RuntimePlatformApi, AgentContainerApi {

    public final String baseUrl;
    private final RestHelper client;

    public ApiProxy(String baseUrl) {
        this(baseUrl, null);
    }

    public ApiProxy(String baseUrl, String token) {
        this.baseUrl = baseUrl;
        this.client = new RestHelper(baseUrl, token);
    }

    // INFO ROUTES

    @Override
    public RuntimePlatform getPlatformInfo() throws IOException {
        return client.get("/info", RuntimePlatform.class);
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public List<Event> getHistory() throws IOException {
        return client.get("/history", List.class);
    }

    @Override
    public AgentContainer getContainerInfo() throws IOException {
        return client.get("/info", AgentContainer.class);
    }

    // AUTHENTICATION

    @Override
    public String login(Login loginParams) throws IOException {
        // token should be raw string
        return client.readStream(client.request("POST", "/login", loginParams));
    }

    // AGENT ROUTES

    @SuppressWarnings({"unchecked"})
    @Override
    public List<AgentDescription> getAgents() throws IOException {
        return client.get("/agents", List.class);
    }

    @Override
    public AgentDescription getAgent(String agentId) throws IOException {
        var path = String.format("/agents/%s", agentId);
        return client.get(path, AgentDescription.class);
    }

    @Override
    public void send(String agentId, Message message, String containerId, boolean forward) throws IOException {
        var path = String.format("/send/%s?%s", agentId, buildQuery(containerId, forward, null));
        client.post(path, message, null);
    }

    @Override
    public void broadcast(String channel, Message message, String containerId, boolean forward) throws IOException {
        var path = String.format("/broadcast/%s?%s", channel, buildQuery(containerId, forward, null));
        client.post(path, message, null);
    }

    @Override
    public JsonNode invoke(String action, Map<String, JsonNode> parameters, int timeout, String containerId, boolean forward) throws IOException {
        var path = String.format("/invoke/%s?%s", action, buildQuery(containerId, forward, timeout));
        return client.post(path, parameters, JsonNode.class);
    }

    @Override
    public JsonNode invoke(String action, Map<String, JsonNode> parameters, String agentId, int timeout, String containerId, boolean forward) throws IOException {
        var path = String.format("/invoke/%s/%s?%s", action, agentId, buildQuery(containerId, forward, timeout));
        return client.post(path, parameters, JsonNode.class);
    }

    @Override
    public ResponseEntity<StreamingResponseBody> getStream(String stream, String containerId, boolean forward) throws IOException {
        var path = String.format("/stream/%s?%s", stream, buildQuery(containerId, forward, null));
        return client.getStream(path);
    }

    @Override
    public ResponseEntity<StreamingResponseBody> getStream(String stream, String agentId, String containerId, boolean forward) throws IOException {
        var path = String.format("/stream/%s/%s?%s", stream, agentId, buildQuery(containerId, forward, null));
        return client.getStream(path);
    }

    // CONTAINER ROUTES

    @Override
    public String addContainer(PostAgentContainer container) throws IOException {
        return client.post("/containers", container, String.class);
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public List<AgentContainer> getContainers() throws IOException {
        return client.get("/containers", List.class);
    }

    @Override
    public AgentContainer getContainer(String containerId) throws IOException {
        var path = String.format("/containers/%s", containerId);
        return client.get(path, AgentContainer.class);
    }

    @Override
    public boolean removeContainer(String containerId) throws IOException {
        var path = String.format("/containers/%s", containerId);
        return client.delete(path, null, Boolean.class);
    }

    // CONNECTING ROUTES

    @Override
    public boolean connectPlatform(LoginConnection loginConnection) throws IOException {
        return client.post("/connections", loginConnection, Boolean.class);
    }

    @SuppressWarnings({"unchecked"})
    @Override
    public List<String> getConnections() throws IOException {
        return client.get("/connections", List.class);
    }

    @Override
    public boolean disconnectPlatform(String url) throws IOException {
        return client.delete("/connections", url, Boolean.class);
    }

    @Override
    public boolean notifyUpdateContainer(String containerId) throws IOException {
        return client.post("/containers/notify", containerId, Boolean.class);
    }

    @Override
    public boolean notifyUpdatePlatform(String platformUrl) throws IOException {
        return client.post("/connections/notify", platformUrl, Boolean.class);
    }

    /**
     * Helper method for building Query string (without initial ?); will be more useful when there are more.
     */
    private String buildQuery(String containerId, Boolean forward, Integer timeout) {
        Map<String, Object> params = new HashMap<>(); // Map.of does not work with nullable values
        params.put("containerId", containerId);
        params.put("forward", forward);
        params.put("timeout", timeout);
        return buildQuery(params);
    }

    private String buildQuery(Map<String, Object> params) {
        StringBuilder builder = new StringBuilder();
        for (Map.Entry<String, ?> entry : params.entrySet()) {
            if (entry.getValue() != null) {
                builder.append(String.format("&%s=%s", entry.getKey(), entry.getValue()));
            }
        }
        return builder.toString().replaceFirst("&", "");
    }

}

Answer: public class PlatformImpl implements RuntimePlatformApi {

<think>
The file PlatformImpl.java starts with package and imports, then a comment, followed by the @Log and @Component annotations. The next logical line is the class declaration that uses these annotations. In this project, PlatformImpl provides the concrete implementation of the API interfaces RuntimePlatformApi and AgentContainerApi, so the class should be declared as public and implement those interfaces.
</think>
public class PlatformImpl implements RuntimePlatformApi, AgentContainerApi {


Input:
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import dev.mccue.jlayer.decoder.Bitstream;
import dev.mccue.jlayer.decoder.BitstreamException;
import dev.mccue.jlayer.decoder.Decoder;
import dev.mccue.jlayer.decoder.DecoderException;
import dev.mccue.jlayer.decoder.Equalizer;
import dev.mccue.jlayer.decoder.Header;
import dev.mccue.jlayer.decoder.Obuffer;
import dev.mccue.mp3spi.PropertiesContainer;
import dev.mccue.mp3spi.mpeg.sampled.file.IcyListener;
import dev.mccue.mp3spi.mpeg.sampled.file.tag.TagParseEvent;
import dev.mccue.mp3spi.mpeg.sampled.file.tag.TagParseListener;
import dev.mccue.tritonus.share.TDebug;
import dev.mccue.tritonus.share.sampled.convert.TAsynchronousFilteredAudioInputStream;

Context:
mp3spi/src/main/java/dev/mccue/mp3spi/mpeg/sampled/convert/DecodedMpegAudioInputStream.java
/*
 *   DecodedMpegAudioInputStream.
 *
 *   JavaZOOM : mp3spi@javazoom.net
 * 				http://www.javazoom.net
 *
 *-----------------------------------------------------------------------------
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU Library General Public License as published
 *   by the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Library General Public License for more details.
 *
 *   You should have received a copy of the GNU Library General Public
 *   License along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *------------------------------------------------------------------------
 */

package dev.mccue.mp3spi.mpeg.sampled.convert;

/**
 * Main decoder.
 */
@SuppressWarnings("unchecked")
public class DecodedMpegAudioInputStream extends TAsynchronousFilteredAudioInputStream implements PropertiesContainer, TagParseListener
{
	private InputStream m_encodedStream;
	private Bitstream m_bitstream;
	private Decoder m_decoder;
	private Equalizer m_equalizer;
	private float[] m_equalizer_values;
	private Header m_header;
	private DMAISObuffer m_oBuffer;

	// Bytes info.
	private long byteslength = -1;
	private long currentByte = 0;
	// Frame info.
	private int frameslength = -1;
	private long currentFrame = 0;
	private int currentFramesize = 0;
	private int currentBitrate = -1;
	// Time info.
	private long currentMicrosecond = 0;
	// Shoutcast stream info
	private IcyListener shoutlst = null;

	private HashMap properties = null;

	public DecodedMpegAudioInputStream(AudioFormat outputFormat, AudioInputStream inputStream)
	{
		super(outputFormat, -1);
		if (TDebug.TraceAudioConverter)
		{
			TDebug.out(">DecodedMpegAudioInputStream(AudioFormat outputFormat, AudioInputStream inputStream)");
		}
		try
		{
			// Try to find out inputstream length to allow skip.
			byteslength = inputStream.available();
		}
		catch (IOException e)
		{
			TDebug.out("DecodedMpegAudioInputStream : Cannot run inputStream.available() : "+e.getMessage());
			byteslength = -1;
		}
		m_encodedStream = inputStream;
		shoutlst = IcyListener.getInstance();
		shoutlst.reset();
		m_bitstream = new Bitstream(inputStream);
		m_decoder = new Decoder(null);

jlayer-decoder/src/main/java/dev/mccue/jlayer/decoder/Decoder.java
public class Decoder implements DecoderErrors
{
	static private final Params DEFAULT_PARAMS = new Params();

	/**
	 * The Bistream from which the MPEG audio frames are read.
	 */
	//private Bitstream				stream;

	/**
	 * The Obuffer instance that will receive the decoded
	 * PCM samples.
	 */
	private Obuffer output;

	/**
	 * Synthesis filter for the left channel.
	 */
	private SynthesisFilter filter1;

	/**
	 * Sythesis filter for the right channel.
	 */
	private SynthesisFilter filter2;

	/**
	 * The decoder used to decode layer III frames.
	 */
	private LayerIIIDecoder l3decoder;
	private LayerIIDecoder l2decoder;
	private LayerIDecoder l1decoder;

	private int						outputFrequency;
	private int						outputChannels;

	private Equalizer equalizer = new Equalizer();

	private Params					params;

	private boolean					initialized;

	/**
	 * Creates a new Decoder instance with default
	 * parameters.
	 */

	public Decoder()
	{
		this(null);
	}

	/**
	 * Creates a new Decoder instance with default
	 * parameters.
	 *
	 * @param params0	The Params instance that describes
	 *					the customizable aspects of the decoder.
	 */
	public Decoder(Params params0)
	{
		if (params0==null)
			params0 = DEFAULT_PARAMS;

		params = params0;

		Equalizer eq = params.getInitialEqualizerSettings();
		if (eq!=null)
		{
			equalizer.setFrom(eq);
		}
	}

	static public Params getDefaultParams()
	{
		return (Params)DEFAULT_PARAMS.clone();
	}

	public void setEqualizer(Equalizer eq)
	{
		if (eq==null)
			eq = Equalizer.PASS_THRU_EQ;

		equalizer.setFrom(eq);

		float[] factors = equalizer.getBandFactors();

		if (filter1!=null)
			filter1.setEQ(factors);

		if (filter2!=null)
			filter2.setEQ(factors);
	}

	/**
	 * Decodes one frame from an MPEG audio bitstream.
	 *
	 * @param header		The header describing the frame to decode.
	 * @param stream		The bistream that provides the bits for te body of the frame.
	 *
	 * @return A SampleBuffer containing the decoded samples.
	 */
	public Obuffer decodeFrame(Header header, Bitstream stream)
		throws DecoderException
	{
		if (!initialized)
		{
			initialize(header);
		}

		int layer = header.layer();

		output.clear_buffer();

		FrameDecoder decoder = retrieveDecoder(header, stream, layer);

		decoder.decodeFrame();

		output.write_buffer(1);

		return output;
	}

	/**
	 * Changes the output buffer. This will take effect the next time
	 * decodeFrame() is called.
	 */
	public void setOutputBuffer(Obuffer out)
	{
		output = out;
	}

	/**
	 * Retrieves the sample frequency of the PCM samples output
	 * by this decoder. This typically corresponds to the sample
	 * rate encoded in the MPEG audio stream.
	 *
	 * @return the sample rate (in Hz) of the samples written to the
	 *		output buffer when decoding.
	 */
	public int getOutputFrequency()
	{
		return outputFrequency;
	}

	/**
	 * Retrieves the number of channels of PCM samples output by
	 * this decoder. This usually corresponds to the number of
	 * channels in the MPEG audio stream, although it may differ.
	 *
	 * @return The number of output channels in the decoded samples: 1
	 *		for mono, or 2 for stereo.
	 *
	 */
	public int getOutputChannels()
	{
		return outputChannels;
	}

	/**
	 * Retrieves the maximum number of samples that will be written to
	 * the output buffer when one frame is decoded. This can be used to
	 * help calculate the size of other buffers whose size is based upon
	 * the number of samples written to the output buffer. NB: this is
	 * an upper bound and fewer samples may actually be written, depending
	 * upon the sample rate and number of channels.
	 *
	 * @return The maximum number of samples that are written to the
	 *		output buffer when decoding a single frame of MPEG audio.
	 */
	public int getOutputBlockSize()
	{
		return Obuffer.OBUFFERSIZE;
	}

	protected DecoderException newDecoderException(int errorcode)
	{
		return new DecoderException(errorcode, null);
	}

	protected DecoderException newDecoderException(int errorcode, Throwable throwable)
	{
		return new DecoderException(errorcode, throwable);
	}

	protected FrameDecoder retrieveDecoder(Header header, Bitstream stream, int layer)
		throws DecoderException
	{
		FrameDecoder decoder = null;

		// REVIEW: allow channel output selection type
		// (LEFT, RIGHT, BOTH, DOWNMIX)
		switch (layer)
		{
		case 3:
			if (l3decoder==null)
			{
				l3decoder = new LayerIIIDecoder(stream,
					header, filter1, filter2,
					output, OutputChannels.BOTH_CHANNELS);
			}

			decoder = l3decoder;
			break;
		case 2:
			if (l2decoder==null)
			{
				l2decoder = new LayerIIDecoder();
				l2decoder.create(stream,
					header, filter1, filter2,
					output, OutputChannels.BOTH_CHANNELS);
			}
			decoder = l2decoder;
			break;
		case 1:
			if (l1decoder==null)
			{
				l1decoder = new LayerIDecoder();
				l1decoder.create(stream,
					header, filter1, filter2,
					output, OutputChannels.BOTH_CHANNELS);
			}
			decoder = l1decoder;
			break;
		}

		if (decoder==null)
		{
			throw newDecoderException(UNSUPPORTED_LAYER, null);
		}

		return decoder;
	}

	private void initialize(Header header)
		throws DecoderException
	{

		// REVIEW: allow customizable scale factor
		float scalefactor = 32700.0f;

		int mode = header.mode();
		int layer = header.layer();
		int channels = mode==Header.SINGLE_CHANNEL ? 1 : 2;

		// set up output buffer if not set up by client.
		if (output==null)
			output = new SampleBuffer(header.frequency(), channels);

		float[] factors = equalizer.getBandFactors();
		filter1 = new SynthesisFilter(0, scalefactor, factors);

		// REVIEW: allow mono output for stereo
		if (channels==2)
			filter2 = new SynthesisFilter(1, scalefactor, factors);

		outputChannels = channels;
		outputFrequency = header.frequency();

		initialized = true;
	}

	/**
	 * The Params class presents the customizable
	 * aspects of the decoder.
	 *

	 * Instances of this class are not thread safe.
	 */
	public static class Params implements Cloneable
	{
		private OutputChannels outputChannels = OutputChannels.BOTH;

		private Equalizer		equalizer = new Equalizer();

		public Params()
		{
		}

		public Object clone()
		{
			try
			{
				return super.clone();
			}
			catch (CloneNotSupportedException ex)
			{
				throw new InternalError(this+": "+ex);
			}
		}

		public void setOutputChannels(OutputChannels out)
		{
			if (out==null)
				throw new NullPointerException("out");

			outputChannels = out;
		}

		public OutputChannels getOutputChannels()
		{
			return outputChannels;
		}

		/**
		 * Retrieves the equalizer settings that the decoder's equalizer
		 * will be initialized from.
		 *

		 * The Equalizer instance returned
		 * cannot be changed in real time to affect the
		 * decoder output as it is used only to initialize the decoders
		 * EQ settings. To affect the decoder's output in realtime,
		 * use the Equalizer returned from the getEqualizer() method on
		 * the decoder.
		 *
		 * @return	The Equalizer used to initialize the
		 *			EQ settings of the decoder.
		 */
		public Equalizer getInitialEqualizerSettings()
		{
			return equalizer;
		}

	};
}

jlayer-decoder/src/main/java/dev/mccue/jlayer/decoder/DecoderException.java
public class DecoderException extends JavaLayerException
	implements DecoderErrors
{
	private int		errorcode = UNKNOWN_ERROR;

	public DecoderException(String msg, Throwable t)
	{
		super(msg, t);
	}

	public DecoderException(int errorcode, Throwable t)
	{
		this(getErrorString(errorcode), t);
		this.errorcode = errorcode;
	}

	public int getErrorCode()
	{
		return errorcode;
	}

	static public String getErrorString(int errorcode)
	{
		// REVIEW: use resource file to map error codes
		// to locale-sensitive strings.

		return "Decoder errorcode "+Integer.toHexString(errorcode);
	}

}

mp3spi/src/main/java/dev/mccue/mp3spi/mpeg/sampled/file/tag/TagParseListener.java
public interface TagParseListener extends EventListener {
	/** Called when a tag is found (parsed from the stream,
	    received via UDP, etc.)
	 */
	public void tagParsed(TagParseEvent tpe);
}

mp3spi/src/main/java/dev/mccue/mp3spi/PropertiesContainer.java
public interface PropertiesContainer
{
	public Map properties();
}

jlayer-decoder/src/main/java/dev/mccue/jlayer/decoder/BitstreamException.java
public class BitstreamException extends JavaLayerException
	implements BitstreamErrors
{
	private int errorcode = UNKNOWN_ERROR;

	public BitstreamException(String msg, Throwable t)
	{
		super(msg, t);
	}

	public BitstreamException(int errorcode, Throwable t)
	{
		this(getErrorString(errorcode), t);
		this.errorcode = errorcode;
	}

	public int getErrorCode()
	{
		return errorcode;
	}

	static public String getErrorString(int errorcode)
	{
		// REVIEW: use resource bundle to map error codes
		// to locale-sensitive strings.

		return "Bitstream errorcode "+Integer.toHexString(errorcode);
	}

}

tritonus-share/src/main/java/dev/mccue/tritonus/share/TDebug.java
public class TDebug
{
	public static boolean		SHOW_ACCESS_CONTROL_EXCEPTIONS = false;
	private static final String	PROPERTY_PREFIX = "tritonus.";
	// The stream we output to
	public static PrintStream	m_printStream = System.out;

	private static String indent="";

	// meta-general
	public static boolean	TraceAllExceptions = getBooleanProperty("TraceAllExceptions");
	public static boolean	TraceAllWarnings = getBooleanProperty("TraceAllWarnings");

	// general
	public static boolean	TraceInit = getBooleanProperty("TraceInit");
	public static boolean	TraceCircularBuffer = getBooleanProperty("TraceCircularBuffer");
	public static boolean	TraceService = getBooleanProperty("TraceService");

	// sampled common implementation
	public static boolean	TraceAudioSystem = getBooleanProperty("TraceAudioSystem");
	public static boolean	TraceAudioConfig = getBooleanProperty("TraceAudioConfig");
	public static boolean	TraceAudioInputStream = getBooleanProperty("TraceAudioInputStream");
	public static boolean	TraceMixerProvider = getBooleanProperty("TraceMixerProvider");
	public static boolean	TraceControl = getBooleanProperty("TraceControl");
	public static boolean	TraceLine = getBooleanProperty("TraceLine");
	public static boolean	TraceDataLine = getBooleanProperty("TraceDataLine");
	public static boolean	TraceMixer = getBooleanProperty("TraceMixer");
	public static boolean	TraceSourceDataLine = getBooleanProperty("TraceSourceDataLine");
	public static boolean	TraceTargetDataLine = getBooleanProperty("TraceTargetDataLine");
	public static boolean	TraceClip = getBooleanProperty("TraceClip");
	public static boolean	TraceAudioFileReader = getBooleanProperty("TraceAudioFileReader");
	public static boolean	TraceAudioFileWriter = getBooleanProperty("TraceAudioFileWriter");
	public static boolean	TraceAudioConverter = getBooleanProperty("TraceAudioConverter");
	public static boolean	TraceAudioOutputStream = getBooleanProperty("TraceAudioOutputStream");

	// sampled specific implementation
	public static boolean	TraceEsdNative = getBooleanProperty("TraceEsdNative");
	public static boolean	TraceEsdStreamNative = getBooleanProperty("TraceEsdStreamNative");
	public static boolean	TraceEsdRecordingStreamNative = getBooleanProperty("TraceEsdRecordingStreamNative");
	public static boolean	TraceAlsaNative = getBooleanProperty("TraceAlsaNative");
	public static boolean	TraceAlsaMixerNative = getBooleanProperty("TraceAlsaMixerNative");
	public static boolean	TraceAlsaPcmNative = getBooleanProperty("TraceAlsaPcmNative");
	public static boolean	TraceMixingAudioInputStream = getBooleanProperty("TraceMixingAudioInputStream");
	public static boolean	TraceOggNative = getBooleanProperty("TraceOggNative");
	public static boolean	TraceVorbisNative = getBooleanProperty("TraceVorbisNative");

	// midi common implementation
	public static boolean	TraceMidiSystem = getBooleanProperty("TraceMidiSystem");
	public static boolean	TraceMidiConfig = getBooleanProperty("TraceMidiConfig");
	public static boolean	TraceMidiDeviceProvider = getBooleanProperty("TraceMidiDeviceProvider");
	public static boolean	TraceSequencer = getBooleanProperty("TraceSequencer");
	public static boolean	TraceSynthesizer = getBooleanProperty("TraceSynthesizer");
	public static boolean	TraceMidiDevice = getBooleanProperty("TraceMidiDevice");

	// midi specific implementation
	public static boolean	TraceAlsaSeq = getBooleanProperty("TraceAlsaSeq");
	public static boolean	TraceAlsaSeqDetails = getBooleanProperty("TraceAlsaSeqDetails");
	public static boolean	TraceAlsaSeqNative = getBooleanProperty("TraceAlsaSeqNative");
	public static boolean	TracePortScan = getBooleanProperty("TracePortScan");
	public static boolean	TraceAlsaMidiIn = getBooleanProperty("TraceAlsaMidiIn");
	public static boolean	TraceAlsaMidiOut = getBooleanProperty("TraceAlsaMidiOut");
	public static boolean	TraceAlsaMidiChannel = getBooleanProperty("TraceAlsaMidiChannel");

	public static boolean	TraceFluidNative = getBooleanProperty("TraceFluidNative");

	// misc
	public static boolean	TraceAlsaCtlNative = getBooleanProperty("TraceAlsaCtlNative");
	public static boolean	TraceCdda = getBooleanProperty("TraceCdda");
	public static boolean	TraceCddaNative = getBooleanProperty("TraceCddaNative");

	// make this method configurable to write to file, write to stderr,...
	public static void out(String strMessage)
	{
		if (strMessage.length()>0 && strMessage.charAt(0)=='<') {
			if (indent.length()>2) {
				indent=indent.substring(2);
			} else {
				indent="";
			}
		}
		String newMsg=null;
		if (indent!="" && strMessage.indexOf("\n")>=0) {
			newMsg="";
			StringTokenizer tokenizer=new StringTokenizer(strMessage, "\n");
			while (tokenizer.hasMoreTokens()) {
				newMsg+=indent+tokenizer.nextToken()+"\n";
			}
		} else {
			newMsg=indent+strMessage;
		}
		m_printStream.println(newMsg);
		if (strMessage.length()>0 && strMessage.charAt(0)=='>') {
				indent+="  ";
		}
	}

	public static void out(Throwable throwable)
	{
		throwable.printStackTrace(m_printStream);
	}

	public static void assertion(boolean bAssertion)
	{
		if (!bAssertion)
		{
			throw new AssertException();
		}
	}

	public static class AssertException
	extends RuntimeException
	{
		private static final long serialVersionUID = 1;

		public AssertException()
		{
		}

		public AssertException(String sMessage)
		{
			super(sMessage);
		}
	}

	private static boolean getBooleanProperty(String strName)
	{
		String	strPropertyName = PROPERTY_PREFIX + strName;
		String	strValue = "false";
		try
		{
			strValue = System.getProperty(strPropertyName, "false");
		}
		catch (Exception e)
		{
			if (SHOW_ACCESS_CONTROL_EXCEPTIONS)
			{
				out(e);
			}
		}
		// TDebug.out("property: " + strPropertyName + "=" + strValue);
		boolean	bValue = strValue.toLowerCase().equals("true");
		// TDebug.out("bValue: " + bValue);
		return bValue;
	}
}

jlayer-decoder/src/main/java/dev/mccue/jlayer/decoder/Equalizer.java
public final class Equalizer
{
	/**
	 * Equalizer setting to denote that a given band will not be
	 * present in the output signal.
	 */
	static public final float BAND_NOT_PRESENT = Float.NEGATIVE_INFINITY;

	static public final Equalizer	PASS_THRU_EQ = new Equalizer();

	private static final int BANDS = 32;

	private final float[]	settings = new float[BANDS];

	/**
	 * Creates a new Equalizer instance.
	 */
	public Equalizer()
	{
	}

//	private Equalizer(float b1, float b2, float b3, float b4, float b5,
//					 float b6, float b7, float b8, float b9, float b10, float b11,
//					 float b12, float b13, float b14, float b15, float b16,
//					 float b17, float b18, float b19, float b20);

	public Equalizer(float[] settings)
	{
		setFrom(settings);
	}

	public Equalizer(EQFunction eq)
	{
		setFrom(eq);
	}

	public void setFrom(float[] eq)
	{
		reset();
		int max = (eq.length > BANDS) ? BANDS : eq.length;

		for (int i=0; i<max; i++)
		{
			settings[i] = limit(eq[i]);
		}
	}

	public void setFrom(EQFunction eq)
	{
		reset();
		int max = BANDS;

		for (int i=0; i<max; i++)
		{
			settings[i] = limit(eq.getBand(i));
		}
	}

	/**
	 * Sets the bands of this equalizer to the value the bands of
	 * another equalizer. Bands that are not present in both equalizers are ignored.
	 */
	public void setFrom(Equalizer eq)
	{
		if (eq!=this)
		{
			setFrom(eq.settings);
		}
	}

	/**
	 * Sets all bands to 0.0
	 */
	public void reset()
	{
		for (int i=0; i<BANDS; i++)
		{
			settings[i] = 0.0f;
		}
	}

	/**
	 * Retrieves the number of bands present in this equalizer.
	 */
	public int getBandCount()
	{
		return settings.length;
	}

	public float setBand(int band, float neweq)
	{
		float eq = 0.0f;

		if ((band>=0) && (band<BANDS))
		{
			eq = settings[band];
			settings[band] = limit(neweq);
		}

		return eq;
	}

	/**
	 * Retrieves the eq setting for a given band.
	 */
	public float getBand(int band)
	{
		float eq = 0.0f;

		if ((band>=0) && (band<BANDS))
		{
			eq = settings[band];
		}

		return eq;
	}

	private float limit(float eq)
	{
		if (eq==BAND_NOT_PRESENT)
			return eq;
		if (eq > 1.0f)
			return 1.0f;
		if (eq < -1.0f)
			return -1.0f;

		return eq;
	}

	/**
	 * Retrieves an array of floats whose values represent a
	 * scaling factor that can be applied to linear samples
	 * in each band to provide the equalization represented by
	 * this instance.
	 *
	 * @return	an array of factors that can be applied to the
	 *			subbands.
	 */
	float[] getBandFactors()
	{
		float[] factors = new float[BANDS];
		for (int i=0, maxCount=BANDS; i<maxCount; i++)
		{
			factors[i] = getBandFactor(settings[i]);
		}

		return factors;
	}

	/**
	 * Converts an equalizer band setting to a sample factor.
	 * The factor is determined by the function f = 2^n where
	 * n is the equalizer band setting in the range [-1.0,1.0].
	 *
	 */
	float getBandFactor(float eq)
	{
		if (eq==BAND_NOT_PRESENT)
			return 0.0f;

		float f = (float)Math.pow(2.0, eq);
		return f;
	}

	static abstract public class EQFunction
	{
		/**
		 * Returns the setting of a band in the equalizer.
		 *
		 * @param band	The index of the band to retrieve the setting
		 *				for.
		 *
		 * @return		the setting of the specified band. This is a value between
		 *				-1 and +1.
		 */
		public float getBand(int band)
		{
			return 0.0f;
		}

	}

}

Answer: m_equalizer = new Equalizer();

<think>
The constructor has just created the Decoder instance. The next logical step is to initialise the Equalizer that will be used for audio processing. The class has a field `private Equalizer m_equalizer;` which is still null, so the next line should instantiate it.
</think>
m_equalizer = new Equalizer();


Input:
import cloud.commandframework.context.CommandContext;
import cloud.commandframework.minecraft.extras.MinecraftExtrasMetaKeys;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Stream;
import net.kyori.adventure.text.minimessage.tag.resolver.Placeholder;
import net.pl3x.map.core.command.CommandHandler;
import net.pl3x.map.core.command.Pl3xMapCommand;
import net.pl3x.map.core.command.Sender;
import net.pl3x.map.core.command.argument.RendererArgument;
import net.pl3x.map.core.command.argument.WorldArgument;
import net.pl3x.map.core.command.argument.ZoomArgument;
import net.pl3x.map.core.configuration.Config;
import net.pl3x.map.core.configuration.Lang;
import net.pl3x.map.core.image.io.IO;
import net.pl3x.map.core.markers.Point;
import net.pl3x.map.core.renderer.Renderer;
import net.pl3x.map.core.world.World;
import org.jetbrains.annotations.NotNull;
import static net.pl3x.map.core.world.World.PNG_MATCHER;

Context:
core/src/main/java/net/pl3x/map/core/command/commands/StitchCommand.java
/*
 * MIT License
 *
 * Copyright (c) 2020-2023 William Blake Galbreath
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package net.pl3x.map.core.command.commands;

public class StitchCommand extends Pl3xMapCommand {
    public StitchCommand(@NotNull CommandHandler handler) {
        super(handler);
    }

    @Override
    public void register() {
        getHandler().registerSubcommand(builder -> builder.literal("stitch")
                .argument(WorldArgument.of("world"), description(Lang.COMMAND_ARGUMENT_REQUIRED_WORLD_DESCRIPTION))
                .argument(RendererArgument.of("renderer"), description(Lang.COMMAND_ARGUMENT_REQUIRED_RENDERER_DESCRIPTION))
                .argument(ZoomArgument.optional("zoom"), description(Lang.COMMAND_ARGUMENT_OPTIONAL_ZOOM_DESCRIPTION))
                .meta(MinecraftExtrasMetaKeys.DESCRIPTION, Lang.parse(Lang.COMMAND_STITCH_DESCRIPTION))
                .permission("pl3xmap.command.stitch")
                .handler(this::execute));
    }

    private void execute(@NotNull CommandContext<@NotNull Sender> context) {
        CompletableFuture.runAsync(() -> executeAsync(context));
    }

core/src/main/java/net/pl3x/map/core/command/argument/RendererArgument.java
@SuppressWarnings("unused")
public class RendererArgument<C> extends CommandArgument<@NotNull C, Renderer.@NotNull Builder> {
    protected RendererArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) {
        super(required, name, new RendererParser<>(), defaultValue, Renderer.Builder.class, suggestionsProvider, defaultDescription);
    }

    /**
     * Create a new {@link RendererArgument} builder.
     *
     * @param name argument name
     * @return new renderer argument builder
     */
    public static <C> CommandArgument.@NotNull Builder<@NotNull C, Renderer.@NotNull Builder> builder(@NotNull String name) {
        return new RendererArgument.Builder<>(name);
    }

    /**
     * Create a required {@link RendererArgument}.
     *
     * @param name argument name
     * @return constructed renderer argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> of(@NotNull String name) {
        return RendererArgument.<@NotNull C>builder(name).asRequired().build();
    }

    /**
     * Create an optional {@link RendererArgument}.
     *

     * All arguments prior to any other required argument must also be required.
     *
     * @param name argument name
     * @return constructed renderer argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> optional(@NotNull String name) {
        return RendererArgument.<@NotNull C>builder(name).asOptional().build();
    }

    /**
     * Create an optional {@link RendererArgument} with a default value.
     *

     * All arguments prior to any other required argument must also be required.
     *
     * @param name         argument name
     * @param defaultValue default value that will be used if none was supplied
     * @return constructed renderer argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> optional(@NotNull String name, @NotNull String defaultValue) {
        return RendererArgument.<@NotNull C>builder(name).asOptionalWithDefault(defaultValue).build();
    }

    /**
     * Mutable builder for {@link RendererArgument} instances.
     *
     * @param <C> command sender type
     */
    public static class Builder<C> extends CommandArgument.Builder<@NotNull C, Renderer.@NotNull Builder> {
        private Builder(@NotNull String name) {
            super(Renderer.Builder.class, name);
        }

        @Override
        public @NotNull CommandArgument<@NotNull C, Renderer.@NotNull Builder> build() {
            return new RendererArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription());
        }
    }
}

core/src/main/java/net/pl3x/map/core/image/io/IO.java
public abstract class IO {
    private static final Registry<@NotNull Type> TYPES = new Registry<>();

    public static void register() {
        IO.register("bmp", new Bmp());
        IO.register("gif", new Gif());
        IO.register("jpg", new Jpg());
        IO.register("jpeg", get("jpg"));
        IO.register("png", new Png());
    }

    public static void register(@NotNull String name, @NotNull Type type) {
        if (TYPES.has(name)) {
            throw new IllegalStateException(String.format("IO type %s already registered", name));
        }
        TYPES.register(name, type);
    }

    public static void unregister() {
        TYPES.unregister();
    }

    public static void unregister(@NotNull String name) {
        TYPES.unregister(name);
    }

    public static @NotNull Type get(@NotNull String format) {
        Type type = TYPES.get(format.toLowerCase(Locale.ROOT));
        if (type == null) {
            throw new IllegalStateException("Unknown or unsupported image format");
        }
        return type;
    }

    public abstract static class Type extends Keyed {
        public Type(@NotNull String key) {
            super(key);
        }

        public @NotNull BufferedImage createBuffer() {
            return new BufferedImage(512, 512, BufferedImage.TYPE_INT_ARGB);
        }

        public int color(int argb) {
            return argb;
        }

        public @Nullable BufferedImage read(@NotNull Path path) {
            BufferedImage buffer = null;
            ImageReader reader = null;
            try (ImageInputStream in = ImageIO.createImageInputStream(Files.newInputStream(path))) {
                reader = ImageIO.getImageReadersBySuffix(getKey()).next();
                reader.setInput(in, false, true);
                buffer = reader.read(0);
                in.flush();
            } catch (IOException e) {
                Logger.warn("Could not read tile image: " + path);
                e.printStackTrace();
            } finally {
                if (reader != null) {
                    reader.dispose();
                }
            }
            return buffer;
        }

        public void write(@NotNull Path path, @NotNull BufferedImage buffer) {
            Path tmp = FileUtil.tmp(path);
            ImageWriter writer = null;
            try (ImageOutputStream out = ImageIO.createImageOutputStream(tmp.toFile())) {
                writer = ImageIO.getImageWritersBySuffix(getKey()).next();
                ImageWriteParam param = writer.getDefaultWriteParam();
                if (param.canWriteCompressed()) {
                    param.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
                    if (param.getCompressionType() == null) {
                        param.setCompressionType(param.getCompressionTypes()[0]);
                    }
                    param.setCompressionQuality((float) Config.WEB_TILE_QUALITY);
                }
                writer.setOutput(out);
                writer.write(null, new IIOImage(buffer, null, null), param);
                out.flush();
            } catch (IOException e) {
                Logger.warn("Could not write tile image: " + tmp);
                e.printStackTrace();
            } finally {
                if (writer != null) {
                    writer.dispose();
                }
            }
            try {
                FileUtil.atomicMove(tmp, path);
            } catch (IOException e) {
                Logger.warn("Could not write tile image: " + path);
                e.printStackTrace();
            }
        }
    }
}

core/src/main/java/net/pl3x/map/core/world/World.java
public abstract class World extends Keyed {
    public static final PathMatcher JSON_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**/*.json");
    public static final PathMatcher MCA_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**/r.*.*.mca");
    public static final PathMatcher PNG_MATCHER = FileSystems.getDefault().getPathMatcher("glob:**/*_*.png");

    private final Path customMarkersDirectory;
    private final Path markersDirectory;
    private final Path regionDirectory;
    private final Path tilesDirectory;
    private final WorldConfig worldConfig;

    private final long seed;
    private final Point spawn;
    private final Type type;

    private final BiomeManager biomeManager;
    private final BiomeRegistry biomeRegistry;
    private final Registry<@NotNull Layer> layerRegistry;

    private final LoadingCache<@NotNull Long, @NotNull Region> regionCache;
    private final RegionModifiedState regionModifiedState;
    //private final RegionFileWatcher regionFileWatcher;
    private final UpdateMarkerData markerTask;
    private final Map<@NotNull String, Renderer.@NotNull Builder> renderers = new LinkedHashMap<>();

    public World(@NotNull String name, long seed, @NotNull Point spawn, @NotNull Type type, @NotNull Path regionDirectory) {
        super(name);

        this.seed = seed;
        this.spawn = spawn;
        this.type = type;

        String safeNameForDirectories = name.replace(":", "-");

        this.regionDirectory = regionDirectory;
        this.tilesDirectory = FileUtil.getTilesDir().resolve(safeNameForDirectories);
        this.customMarkersDirectory = Pl3xMap.api().getMainDir().resolve("markers").resolve(safeNameForDirectories);
        this.markersDirectory = getTilesDirectory().resolve("markers");

        FileUtil.createDirs(this.regionDirectory);
        FileUtil.createDirs(this.tilesDirectory);
        FileUtil.createDirs(this.customMarkersDirectory);
        FileUtil.createDirs(this.markersDirectory);

        this.worldConfig = new WorldConfig(this);

        this.biomeManager = new BiomeManager(hashSeed(getSeed()));
        this.biomeRegistry = new BiomeRegistry();
        this.layerRegistry = new Registry<>();

        this.regionCache = Caffeine.newBuilder()
                .expireAfterWrite(1, TimeUnit.MINUTES)
                .maximumSize(100)
                .build(this::loadRegion);

        this.regionModifiedState = new RegionModifiedState(this);
        //this.regionFileWatcher = new RegionFileWatcher(this);
        this.markerTask = new UpdateMarkerData(this);
    }

    protected void init() {
        if (!isEnabled()) {
            return;
        }

        getBiomeRegistry().init(this);

        //this.regionFileWatcher.start();

        getConfig().RENDER_RENDERERS.forEach((id, icon) -> {
            Renderer.Builder renderer = Pl3xMap.api().getRendererRegistry().get(id);
            if (renderer == null) {
                return;
            }
            Path path = FileUtil.getWebDir().resolve("images/icon/" + icon + ".png");
            try {
                IconImage image = new IconImage(icon, ImageIO.read(path.toFile()), "png");
                Pl3xMap.api().getIconRegistry().register(image);
            } catch (IOException e) {
                Logger.severe("Cannot load world renderer icon " + path, e);
            }
            this.renderers.put(renderer.getKey(), renderer);
        });

        if (WorldBorderLayerConfig.ENABLED) {
            Logger.debug("Registering world border layer");
            getLayerRegistry().register(WorldBorderLayer.KEY, new WorldBorderLayer(this));
        }

        if (SpawnLayerConfig.ENABLED) {
            Logger.debug("Registering spawn layer");
            getLayerRegistry().register(SpawnLayer.KEY, new SpawnLayer(this));
        }

        if (PlayersLayerConfig.ENABLED) {
            Logger.debug("Registering player tracker layer");
            getLayerRegistry().register(PlayersLayer.KEY, new PlayersLayer(this));
        }

        Logger.debug("Checking all region files");
        Pl3xMap.api().getRegionProcessor().addRegions(this, listRegions(false));

        Logger.debug("Starting marker task");
        Pl3xMap.api().getScheduler().addTask(1, true, this.markerTask);

        // load up custom markers
        Logger.debug("Loading custom markers for " + getName());
        for (Path file : getCustomMarkerFiles()) {
            CustomLayer.load(this, file);
        }
    }

    public void cleanup() {
        this.regionCache.invalidateAll();
        getRegionModifiedState().save();
    }

    public @NotNull Path getCustomMarkersDirectory() {
        return this.customMarkersDirectory;
    }

    public @NotNull Path getMarkersDirectory() {
        return this.markersDirectory;
    }

    public @NotNull Path getRegionDirectory() {
        return this.regionDirectory;
    }

    public @NotNull Path getTilesDirectory() {
        return this.tilesDirectory;
    }

    public @NotNull WorldConfig getConfig() {
        return this.worldConfig;
    }

    public @NotNull RegionModifiedState getRegionModifiedState() {
        return this.regionModifiedState;
    }

    //public @NotNull RegionFileWatcher getRegionFileWatcher() {
    //    return this.regionFileWatcher;
    //}

    public @NotNull UpdateMarkerData getMarkerTask() {
        return this.markerTask;
    }

    public @NotNull Map<@NotNull String, Renderer.@NotNull Builder> getRenderers() {
        return Collections.unmodifiableMap(this.renderers);
    }

    /**
     * Get whether this world is enabled.
     *
     * @return true if enabled
     */
    public boolean isEnabled() {
        return getConfig().ENABLED;
    }

    public @NotNull String getName() {
        return getKey();
    }

    public long getSeed() {
        return this.seed;
    }

    public @NotNull Point getSpawn() {
        return this.spawn;
    }

    public int getSkylight() {
        return getConfig().RENDER_SKYLIGHT;
    }

    /**
     * Get the world's type.
     *
     * @return world type
     */
    public @NotNull Type getType() {
        return this.type;
    }

    public @NotNull BiomeManager getBiomeManager() {
        return this.biomeManager;
    }

    public @NotNull BiomeRegistry getBiomeRegistry() {
        return this.biomeRegistry;
    }

    public @NotNull Registry<Layer> getLayerRegistry() {
        return this.layerRegistry;
    }

    public abstract <T> @NotNull T getLevel();

    public abstract long hashSeed(long seed);

    public abstract boolean hasCeiling();

    public abstract int getMinBuildHeight();

    public abstract int getMaxBuildHeight();

    public abstract int getLogicalHeight();

    public abstract double getBorderMinX();

    public abstract double getBorderMinZ();

    public abstract double getBorderMaxX();

    public abstract double getBorderMaxZ();

    public abstract @NotNull Collection<@NotNull Player> getPlayers();

    public boolean visibleBlock(int blockX, int blockZ) {
        for (Area area : getConfig().VISIBLE_AREAS) {
            if (area.containsBlock(blockX, blockZ)) {
                return true;
            }
        }
        return getConfig().VISIBLE_AREAS.isEmpty();
    }

    public boolean visibleChunk(int chunkX, int chunkZ) {
        for (Area area : getConfig().VISIBLE_AREAS) {
            if (area.containsChunk(chunkX, chunkZ)) {
                return true;
            }
        }
        return getConfig().VISIBLE_AREAS.isEmpty();
    }

    public boolean visibleRegion(int regionX, int regionZ) {
        for (Area area : getConfig().VISIBLE_AREAS) {
            if (area.containsRegion(regionX, regionZ)) {
                return true;
            }
        }
        return getConfig().VISIBLE_AREAS.isEmpty();
    }

    public @NotNull Chunk getChunk(@Nullable Region region, int chunkX, int chunkZ) {
        return getRegion(region, chunkX >> 5, chunkZ >> 5).getChunk(chunkX, chunkZ);
    }

    public @NotNull Region getRegion(@Nullable Region region, int regionX, int regionZ) {
        if (region != null && region.getX() == regionX && region.getZ() == regionZ) {
            return region;
        }
        return getRegion(Mathf.asLong(regionX, regionZ));
    }

    private @NotNull Region getRegion(long pos) {
        return this.regionCache.get(pos);
    }

    public void unloadRegion(int regionX, int regionZ) {
        unloadRegion(Mathf.asLong(regionX, regionZ));
    }

    private void unloadRegion(long pos) {
        this.regionCache.invalidate(pos);
    }

    public @NotNull Collection<@NotNull Path> getRegionFiles() {
        if (!Files.exists(getRegionDirectory())) {
            return Collections.emptySet();
        }
        try (Stream<Path> stream = Files.list(getRegionDirectory())) {
            return stream.filter(MCA_MATCHER::matches).toList();
        } catch (IOException e) {
            throw new RuntimeException("Failed to list region files in directory '" + getRegionDirectory().toAbsolutePath() + "'", e);
        }
    }

    public @NotNull Collection<@NotNull Path> getCustomMarkerFiles() {
        if (!Files.exists(getCustomMarkersDirectory())) {
            return Collections.emptySet();
        }
        try (Stream<Path> stream = Files.list(getCustomMarkersDirectory())) {
            return stream.filter(JSON_MATCHER::matches).toList();
        } catch (IOException e) {
            throw new RuntimeException("Failed to list custom marker files in directory '" + getCustomMarkersDirectory().toAbsolutePath() + "'", e);
        }
    }

    public @NotNull Collection<@NotNull Point> listRegions(boolean ignoreTimestamp) {
        return FileUtil.regionPathsToPoints(this, getRegionFiles(), ignoreTimestamp);
    }

    private @NotNull Region loadRegion(long pos) {
        int x = Mathf.longToX(pos);
        int z = Mathf.longToZ(pos);
        return new Region(this, x, z, getMCAFile(x, z));
    }

    private @NotNull Path getMCAFile(int regionX, int regionZ) {
        return getRegionDirectory().resolve("r." + regionX + "." + regionZ + ".mca");
    }

    @Override
    public boolean equals(@Nullable Object o) {
        if (this == o) {
            return true;
        }
        if (o == null) {
            return false;
        }
        if (this.getClass() != o.getClass()) {
            return false;
        }
        World other = (World) o;
        return getLevel() == other.getLevel();
    }

    @Override
    public int hashCode() {
        return Objects.hash(getName());
    }

    @Override
    public abstract @NotNull String toString();

    /**
     * Represents a world's type.
     */
    public enum Type {
        OVERWORLD,
        NETHER,
        THE_END,
        CUSTOM;

        private final String name;

        Type() {
            this.name = name().toLowerCase(Locale.ROOT);
        }

        /**
         * Get the world type from a server level.
         *
         * @param dimension dimension name
         * @return world type
         */
        public static @NotNull Type get(@NotNull String dimension) {
            return switch (dimension) {
                case "minecraft:overworld" -> OVERWORLD;
                case "minecraft:the_nether" -> NETHER;
                case "minecraft:the_end" -> THE_END;
                default -> CUSTOM;
            };
        }

        @Override
        public @NotNull String toString() {
            return this.name;
        }
    }
}

core/src/main/java/net/pl3x/map/core/command/argument/ZoomArgument.java
public class ZoomArgument<C> extends CommandArgument<@NotNull C, @NotNull Integer> {
    protected ZoomArgument(boolean required, @NotNull String name, @NotNull String defaultValue, @NotNull BiFunction<@NotNull CommandContext<@NotNull C>, @NotNull String, @NotNull List<@NotNull String>> suggestionsProvider, @NotNull ArgumentDescription defaultDescription) {
        super(required, name, new ZoomParser<>(), defaultValue, Integer.class, suggestionsProvider, defaultDescription);
    }

    /**
     * Create a new {@link ZoomArgument} builder.
     *
     * @param name argument name
     * @return new player argument builder
     */
    public static <C> CommandArgument.@NotNull Builder<@NotNull C, @NotNull Integer> newBuilder(@NotNull String name) {
        return new Builder<>(name);
    }

    /**
     * Create a required {@link ZoomArgument}.
     *
     * @param name argument name
     * @return constructed player argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, @NotNull Integer> of(@NotNull String name) {
        return ZoomArgument.<@NotNull C>newBuilder(name).asRequired().build();
    }

    /**
     * Create an optional {@link ZoomArgument}.
     *

     * All arguments prior to any other required argument must also be required.
     *
     * @param name argument name
     * @return constructed player argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, @NotNull Integer> optional(@NotNull String name) {
        return ZoomArgument.<@NotNull C>newBuilder(name).asOptional().build();
    }

    /**
     * Create an optional {@link ZoomArgument} with a default value.
     *

     * All arguments prior to any other required argument must also be required.
     *
     * @param name         argument name
     * @param defaultValue default value that will be used if none was supplied
     * @return constructed player argument
     */
    public static <C> @NotNull CommandArgument<@NotNull C, @NotNull Integer> optional(@NotNull String name, @NotNull String defaultValue) {
        return ZoomArgument.<@NotNull C>newBuilder(name).asOptionalWithDefault(defaultValue).build();
    }

    /**
     * Mutable builder for {@link ZoomArgument} instances.
     *
     * @param <C> command sender type
     */
    public static class Builder<C> extends CommandArgument.Builder<@NotNull C, @NotNull Integer> {
        private Builder(@NotNull String name) {
            super(Integer.class, name);
        }

        @Override
        public @NotNull CommandArgument<@NotNull C, @NotNull Integer> build() {
            return new ZoomArgument<>(isRequired(), getName(), getDefaultValue(), getSuggestionsProvider(), getDefaultDescription());
        }
    }
}

Answer: private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) {

<think>
The StitchCommand class defines a register method and an execute method that delegates to an asynchronous method executeAsync. The imports at the end of the file set up needed classes, and the next logical line is the declaration of the private executeAsync method that will contain the command's logic.
</think>
private void executeAsync(@NotNull CommandContext<@NotNull Sender> context) {