/*
* This file is part of NucleusFramework for Bukkit, licensed under the MIT License (MIT).
*
* Copyright (c) JCThePants (www.jcwhatever.com)
*
* 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 com.jcwhatever.nucleus.utils.coords;
import com.jcwhatever.nucleus.utils.PreCon;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.util.Vector;
import javax.annotation.Nullable;
/**
* A synchronized extension to Bukkit's {@link org.bukkit.Location} class that is
* partially thread safe. It's not thread safe when getting Bukkit objects such
* as {@link org.bukkit.block.Block}, {@link org.bukkit.World} or
* {@link org.bukkit.Chunk}.
*
* <p>Useful for creating/loading a {@link org.bukkit.Location} from an asynchronous
* thread where it's not safe to retrieve the Bukkit {@link org.bukkit.World} object.
* The {@link SyncLocation} can hold the name of the world until it's safe to retrieve the
* Bukkit {@link org.bukkit.World} (safety is determined by the coder using the class).</p>
*
* <p>When using {@link Location#equals} on a {@link SyncLocation}, the result is always false.
* however when reversed, a {@link SyncLocation} will be able to compare itself to a
* {@link Location} instance.</p>
*/
public class SyncLocation extends Location {
private String _worldName;
protected final Object _sync = new Object();
/**
* Constructor.
*
* @param location The {@link org.bukkit.Location} instance to get info from.
*/
public SyncLocation(Location location) {
this(location.getWorld(),
location.getX(), location.getY(), location.getZ(),
location.getYaw(), location.getPitch());
}
/**
* Constructor.
*
* @param location The {@link SyncLocation} instance to get info from.
*/
public SyncLocation(SyncLocation location) {
this(location.getWorldName(),
location.getX(), location.getY(), location.getZ(),
location.getYaw(), location.getPitch());
}
/**
* Constructor.
*
* @param world The world the location is in.
* @param x The X coordinates.
* @param y The Y coordinates.
* @param z The Z coordinates.
* @param yaw The yaw angle.
* @param pitch The pitch angle.
*/
public SyncLocation(@Nullable World world, double x, double y, double z, float yaw, float pitch) {
super(world, x, y, z, yaw, pitch);
if (world != null)
_worldName = world.getName();
}
/**
* Constructor.
*
* @param worldName The name of the world the location is in.
* @param x The X coordinates.
* @param y The Y coordinates.
* @param z The Z coordinates.
* @param yaw The yaw angle.
* @param pitch The pitch angle.
*/
public SyncLocation(@Nullable String worldName, double x, double y, double z, float yaw, float pitch) {
super(null, x, y, z, yaw, pitch);
_worldName = worldName;
}
/**
* Constructor.
*
* @param worldName The name of the world the location is in.
* @param x The X coordinate block value.
* @param y The Y coordinate block value.
* @param z The Z coordinate block value.
*/
public SyncLocation(@Nullable String worldName, int x, int y, int z) {
super(null, x, y, z);
_worldName = worldName;
}
/**
* Constructor.
*
* @param world The world the location is in.
* @param x The X coordinate block value.
* @param y The Y coordinate block value.
* @param z The Z coordinate block value.
*/
public SyncLocation(@Nullable World world, int x, int y, int z) {
super(world, x, y, z);
if (world != null)
_worldName = world.getName();
}
/**
* Get the name of the world the location is in.
*/
public String getWorldName() {
synchronized (_sync) {
return _worldName;
}
}
@Override
public World getWorld() {
synchronized (_sync) {
if (super.getWorld() == null && _worldName != null) {
super.setWorld(Bukkit.getWorld(_worldName));
}
return super.getWorld();
}
}
@Override
public void setWorld(@Nullable World world) {
synchronized (_sync) {
_worldName = world != null ? world.getName() : null;
super.setWorld(world);
}
}
/**
* Set the name of the world the location is in.
*
* @param worldName The name of the world.
*/
public void setWorld(@Nullable String worldName) {
synchronized (_sync) {
_worldName = worldName;
super.setWorld(null);
}
}
/**
* Get the chunk the location is in.
*
* <p>Not thread safe.</p>
*
* @return Null if the location does not have a world set.
*/
@Override
@Nullable
public Chunk getChunk() {
if (getWorld() == null)
return null;
return super.getChunk();
}
/**
* Get the block at the location.
*
* <p>Not thread safe.</p>
*
* @return Null if a world is not set.
*/
@Override
@Nullable
public Block getBlock() {
if (getWorld() == null)
return null;
return super.getBlock();
}
@Override
public double getX() {
synchronized (_sync) {
return super.getX();
}
}
@Override
public void setX(double x) {
synchronized (_sync) {
super.setX(x);
}
}
@Override
public double getY() {
synchronized (_sync) {
return super.getY();
}
}
@Override
public void setY(double y) {
synchronized (_sync) {
super.setY(y);
}
}
@Override
public double getZ() {
synchronized (_sync) {
return super.getZ();
}
}
@Override
public void setZ(double z) {
synchronized (_sync) {
super.setZ(z);
}
}
@Override
public float getYaw() {
synchronized (_sync) {
return super.getYaw();
}
}
@Override
public void setYaw(float yaw) {
synchronized (_sync) {
super.setYaw(yaw);
}
}
@Override
public float getPitch() {
synchronized (_sync) {
return super.getPitch();
}
}
@Override
public void setPitch(float pitch) {
synchronized (_sync) {
super.setPitch(pitch);
}
}
@Override
public int getBlockX() {
synchronized (_sync) {
return super.getBlockX();
}
}
@Override
public int getBlockY() {
synchronized (_sync) {
return super.getBlockY();
}
}
@Override
public int getBlockZ() {
synchronized (_sync) {
return super.getBlockZ();
}
}
@Override
public SyncLocation setDirection(Vector vector) {
synchronized (_sync) {
return (SyncLocation)super.setDirection(vector);
}
}
@Override
public SyncLocation add(Location location) {
synchronized (_sync) {
return (SyncLocation)super.add(location);
}
}
@Override
public SyncLocation add(double x, double y, double z) {
synchronized (_sync) {
return (SyncLocation)super.add(x, y, z);
}
}
@Override
public SyncLocation add(Vector vector) {
synchronized (_sync) {
return (SyncLocation)super.add(vector);
}
}
@Override
public SyncLocation subtract(Location location) {
synchronized (_sync) {
return (SyncLocation)super.subtract(location);
}
}
@Override
public SyncLocation subtract(Vector vector) {
synchronized (_sync) {
return (SyncLocation)super.subtract(vector);
}
}
@Override
public SyncLocation subtract(double x, double y, double z) {
synchronized (_sync) {
return (SyncLocation)super.subtract(x, y, z);
}
}
@Override
public SyncLocation multiply(double factor) {
synchronized (_sync) {
return (SyncLocation)super.multiply(factor);
}
}
@Override
public SyncLocation zero() {
synchronized (_sync) {
return (SyncLocation)super.zero();
}
}
@Override
public double length() {
synchronized (_sync) {
return super.length();
}
}
@Override
public double lengthSquared() {
synchronized (_sync) {
return super.lengthSquared();
}
}
@Override
public double distance(Location location) {
return Math.sqrt(distanceSquared(location));
}
/**
* Get the distance to the specified coordinates.
*
* @param x The X coordinates.
* @param y The Y coordinates.
* @param z The Z coordinates.
*/
public double distance(double x, double y, double z) {
return Math.sqrt(distanceSquared(x, y, z));
}
@Override
public double distanceSquared(Location location) {
PreCon.notNull(location);
synchronized (_sync) {
if (location instanceof SyncLocation) {
SyncLocation immutable = (SyncLocation) location;
if (_worldName == null || immutable._worldName == null)
throw new IllegalArgumentException("Cannot measure distance without a specified world.");
if (!_worldName.equals(immutable._worldName))
throw new IllegalArgumentException("Cannot measure distance between differing worlds.");
} else {
if (_worldName == null || location.getWorld() == null)
throw new IllegalArgumentException("Cannot measure distance without a specified world.");
if (!_worldName.equals(location.getWorld().getName()))
throw new IllegalArgumentException("Cannot measure distance between differing worlds.");
}
return distanceSquared(location.getX(), location.getY(), location.getZ());
}
}
/**
* Get the distance squared to the specified coordinates.
*
* @param x The X coordinates.
* @param y The Y coordinates.
* @param z The Z coordinates.
*/
public double distanceSquared(double x, double y, double z) {
double dx = getX() - x;
double dy = getY() - y;
double dz = getZ() - z;
return (dx * dx) + (dy * dy) + (dz * dz);
}
/**
* Get a Bukkit {@link org.bukkit.Location}.
*
* <p>If invoked from an asynchronous thread, the returned {@link org.bukkit.Location}'s
* {@link org.bukkit.World} value is null since it's not safe to retrieve the world
* object from any thread other than the primary.</p>
*/
public Location getBukkitLocation() {
World world = super.getWorld();
if (world == null && Bukkit.isPrimaryThread() && getWorldName() != null)
world = Bukkit.getWorld(getWorldName());
return new Location(world, getX(), getY(), getZ(), getYaw(), getPitch());
}
@Override
public Vector toVector() {
synchronized (_sync) {
return super.toVector();
}
}
@Override
public SyncLocation clone() {
return new SyncLocation(this);
}
@Override
public int hashCode() {
synchronized (_sync) {
return super.hashCode();
}
}
@Override
public boolean equals(Object obj) {
if (obj == null)
return false;
if (obj == this)
return true;
synchronized (_sync) {
if (obj instanceof SyncLocation) {
SyncLocation other = (SyncLocation) obj;
return ((_worldName == null && other._worldName == null) ||
(_worldName != null && _worldName.equals(other._worldName))) &&
Double.compare(getX(), other.getX()) == 0 && Double.compare(getY(), other.getY()) == 0 &&
Double.compare(getZ(), other.getZ()) == 0 && Float.compare(getYaw(), other.getYaw()) == 0 &&
Float.compare(getPitch(), other.getPitch()) == 0;
}
else if (obj instanceof Location) {
Location other = (Location) obj;
return ((_worldName == null && other.getWorld() == null) ||
(_worldName != null && other.getWorld() != null && _worldName.equals(other.getWorld().getName()))) &&
Double.compare(getX(), other.getX()) == 0 && Double.compare(getY(), other.getY()) == 0 &&
Double.compare(getZ(), other.getZ()) == 0 && Float.compare(getYaw(), other.getYaw()) == 0 &&
Float.compare(getPitch(), other.getPitch()) == 0;
}
return false;
}
}
}