History on disk rollback command

Useful for undoing specific edits.
This commit is contained in:
Jesse Boyd 2016-04-21 14:07:28 +10:00
parent 652a983907
commit bf7d066520
13 changed files with 676 additions and 25 deletions

View File

@ -24,6 +24,9 @@ commands:
wrg:
description: (FAWE) Select your current WorldEdit Region.
aliases: [/wrg,wer,/wer,worldeditregion,/worldeditregion,/region]
frb:
description: (FAWE) Rollback an edit
aliases: [fawerollback,fawerb,/uu,/rb,/frb,/fawerollback,/fawerb]
permissions:
fawe.bypass:
default: false

View File

@ -30,6 +30,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
@ -315,4 +316,14 @@ public class FaweBukkit extends JavaPlugin implements IFawe, Listener {
public String getPlatform() {
return "bukkit";
}
@Override
public UUID getUUID(String name) {
return Bukkit.getOfflinePlayer(name).getUniqueId();
}
@Override
public String getName(UUID uuid) {
return Bukkit.getOfflinePlayer(uuid).getName();
}
}

View File

@ -24,6 +24,9 @@ commands:
wrg:
description: (FAWE) Select your current WorldEdit Region.
aliases: [/wrg,wer,/wer,worldeditregion,/worldeditregion,/region]
frb:
description: (FAWE) Rollback an edit
aliases: [fawerollback,fawerb,/uu,/rb,/frb,/fawerollback,/fawerb]
permissions:
fawe.bypass:
default: false

View File

@ -2,6 +2,7 @@ package com.boydti.fawe;
import com.boydti.fawe.command.FixLighting;
import com.boydti.fawe.command.Reload;
import com.boydti.fawe.command.Rollback;
import com.boydti.fawe.command.Stream;
import com.boydti.fawe.command.Wea;
import com.boydti.fawe.command.WorldEditRegion;
@ -201,6 +202,7 @@ public class Fawe {
this.IMP.setupCommand("stream", new Stream());
this.IMP.setupCommand("wrg", new WorldEditRegion());
this.IMP.setupCommand("fawe", new Reload());
this.IMP.setupCommand("frb", new Rollback());
}
public void setupConfigs() {

View File

@ -10,6 +10,7 @@ import com.sk89q.worldedit.EditSession;
import java.io.File;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;
public interface IFawe {
public void debug(final String s);
@ -39,4 +40,8 @@ public interface IFawe {
public Set<FawePlayer> getPlayers();
public String getPlatform();
public UUID getUUID(String name);
public String getName(UUID uuid);
}

View File

@ -0,0 +1,160 @@
package com.boydti.fawe.command;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.MathMan;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.EditSession;
import com.sk89q.worldedit.world.World;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
public class Rollback extends FaweCommand {
public Rollback() {
super("fawe.rollback");
}
@Override
public boolean execute(final FawePlayer player, final String... args) {
if (args.length < 1) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return false;
}
World world = player.getWorld();
switch (args[0]) {
default: {
BBC.COMMAND_SYNTAX.send(player, "/frb info u:<uuid> r:<radius> t:<time>");
return false;
}
case "i":
case "info": {
if (args.length < 2) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return false;
}
player.deleteMeta("rollback");
final FaweLocation origin = player.getLocation();
rollback(player, Arrays.copyOfRange(args, 1, args.length), new RunnableVal<List<DiskStorageHistory>>() {
@Override
public void run(List<DiskStorageHistory> edits) {
long total = 0;
player.sendMessage("&d=== Edits ===");
for (DiskStorageHistory edit : edits) {
int[] headerAndFooter = edit.readHeaderAndFooter(new RegionWrapper(origin.x, origin.x, origin.z, origin.z));
RegionWrapper region = new RegionWrapper(headerAndFooter[0], headerAndFooter[2], headerAndFooter[1], headerAndFooter[3]);
int dx = region.distanceX(origin.x);
int dz = region.distanceZ(origin.z);
String name = Fawe.imp().getName(edit.getUUID());
long seconds = (System.currentTimeMillis() - edit.getBDFile().lastModified()) / 1000;
total += edit.getBDFile().length();
player.sendMessage(name + " : " + dx + "," + dz + " : " + MainUtil.secToTime(seconds));
}
player.sendMessage("&d=============");
player.sendMessage("&dSize: " + (((double) (total / 1024)) / 1000) + "MB");
player.sendMessage("&dTo rollback: /frb undo");
player.sendMessage("&d=============");
player.setMeta("rollback", edits);
}
});
break;
}
case "undo":
case "revert": {
final List<DiskStorageHistory> edits = (List<DiskStorageHistory>) player.getMeta("rollback");
player.deleteMeta("rollback");
if (edits == null) {
BBC.COMMAND_SYNTAX.send(player, "/frb info u:<uuid> r:<radius> t:<time>");
return false;
}
final Runnable task = new Runnable() {
@Override
public void run() {
if (edits.size() == 0) {
player.sendMessage("&d" + BBC.PREFIX.s() + " Rollback complete!");
return;
}
DiskStorageHistory edit = edits.remove(0);
player.sendMessage("&d" + edit.getBDFile());
EditSession session = edit.toEditSession(null);
session.undo(session);
edit.deleteFiles();
SetQueue.IMP.addTask(this);
}
};
TaskManager.IMP.async(task);
}
}
return true;
}
public void rollback(final FawePlayer player, final String[] args, final RunnableVal<List<DiskStorageHistory>> result) {
TaskManager.IMP.async(new Runnable() {
@Override
public void run() {
UUID user = null;
int radius = Integer.MAX_VALUE;
long time = Long.MAX_VALUE;
for (int i = 0; i < args.length; i++) {
String[] split = args[i].split(":");
if (split.length != 2) {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return;
}
switch (split[0].toLowerCase()) {
case "username":
case "user":
case "u": {
try {
if (split[1].length() > 16) {
user = UUID.fromString(split[1]);
} else {
user = Fawe.imp().getUUID(split[1]);
}
} catch (IllegalArgumentException e) {}
if (user == null) {
player.sendMessage("&dInvalid user: " + split[1]);
return;
}
break;
}
case "r":
case "radius": {
if (!MathMan.isInteger(split[1])) {
player.sendMessage("&dInvalid radius: " + split[1]);
return;
}
radius = Integer.parseInt(split[1]);
break;
}
case "t":
case "time": {
time = MainUtil.timeToSec(split[1]) * 1000;
break;
}
default: {
BBC.COMMAND_SYNTAX.send(player, "/frb <info|undo> u:<uuid> r:<radius> t:<time>");
return;
}
}
}
FaweLocation origin = player.getLocation();
List<DiskStorageHistory> edits = MainUtil.getBDFiles(origin, user, radius, time);
if (edits.size() == 0) {
player.sendMessage("No edits found!");
return;
}
result.run(edits);
}
});
}
}

View File

@ -1,16 +0,0 @@
package com.boydti.fawe.command;
import com.boydti.fawe.object.FaweCommand;
import com.boydti.fawe.object.FawePlayer;
public class Undolist extends FaweCommand {
public Undolist() {
super("fawe.undolist");
}
@Override
public boolean execute(final FawePlayer player, final String... args) {
return false;
}
}

View File

@ -1,5 +1,8 @@
package com.boydti.fawe.object;
import com.sk89q.worldedit.WorldEdit;
import com.sk89q.worldedit.world.World;
/**
*/
@ -32,6 +35,15 @@ public class FaweLocation {
return ((this.x == other.x) && (this.y == other.y) && (this.z == other.z) && (this.world.equals(other.world)));
}
public World getWorld() {
for (World world : WorldEdit.getInstance().getServer().getWorlds()) {
if (world.getName().equals(this.world)) {
return world;
}
}
return null;
}
@Override
public int hashCode() {
return this.x << (8 + this.z) << (4 + this.y);

View File

@ -15,8 +15,10 @@ import com.sk89q.worldedit.regions.RegionSelector;
import com.sk89q.worldedit.regions.selector.CuboidRegionSelector;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@ -30,7 +32,7 @@ public abstract class FawePlayer<T> {
*/
private volatile ConcurrentHashMap<String, Object> meta;
public static <T> FawePlayer<T> wrap(final Object obj) {
public static <V> FawePlayer<V> wrap(final Object obj) {
return Fawe.imp().wrap(obj);
}
@ -71,7 +73,7 @@ public abstract class FawePlayer<T> {
return;
}
UUID uuid = getUUID();
ArrayDeque<Integer> editIds = new ArrayDeque<>();
List<Integer> editIds = new ArrayList<>();
File folder = new File(Fawe.imp().getDirectory(), "history" + File.separator + world.getName() + File.separator + uuid);
if (folder.isDirectory()) {
for (File file : folder.listFiles()) {
@ -81,6 +83,7 @@ public abstract class FawePlayer<T> {
}
}
}
Collections.sort(editIds);
if (editIds.size() > 0) {
Fawe.debug(BBC.PREFIX.s() + " Indexing " + editIds.size() + " history objects for " + getName());
for (int index : editIds) {
@ -169,20 +172,20 @@ public abstract class FawePlayer<T> {
/**
* Get the metadata for a key.
* @param <T>
* @param <V>
* @param key
* @return
*/
public <T> T getMeta(String key) {
public <V> V getMeta(String key) {
if (this.meta != null) {
return (T) this.meta.get(key);
return (V) this.meta.get(key);
}
return null;
}
public <T> T getMeta(String key, T def) {
public <V> V getMeta(String key, V def) {
if (this.meta != null) {
T value = (T) this.meta.get(key);
V value = (V) this.meta.get(key);
return value == null ? def : value;
}
return def;

View File

@ -26,6 +26,45 @@ public class RegionWrapper {
return ((x >= this.minX) && (x <= this.maxX) && (z >= this.minZ) && (z <= this.maxZ));
}
public int distanceX(int x) {
if (x >= minX) {
if (x <= maxX) {
return 0;
}
return maxX - x;
}
return minX - x;
}
public int distanceZ(int z) {
if (z >= minZ) {
if (z <= maxZ) {
return 0;
}
return maxZ - z;
}
return minZ - z;
}
public int distance(int x, int z) {
if (isIn(x, z)) {
return 0;
}
int dx1 = Math.abs(x - minX);
int dx2 = Math.abs(x - maxX);
int dz1 = Math.abs(z - minZ);
int dz2 = Math.abs(z - maxZ);
if (x >= minX && x <= maxX) {
return Math.min(dz1, dz2);
} else if (z >= minZ && z <= maxZ) {
return Math.min(dx1, dx2);
} else {
int dx = Math.min(dx1, dx2);
int dz = Math.min(dz1, dz2);
return (int) Math.sqrt(dx * dx + dz * dz);
}
}
@Override
public String toString() {
return this.minX + "," + this.minZ + "->" + this.maxX + "," + this.maxZ;

View File

@ -4,6 +4,8 @@ import com.boydti.fawe.Fawe;
import com.boydti.fawe.FaweCache;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.config.Settings;
import com.boydti.fawe.object.IntegerPair;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.util.MainUtil;
import com.boydti.fawe.util.ReflectionUtils;
import com.sk89q.jnbt.CompoundTag;
@ -51,7 +53,8 @@ import net.jpountz.lz4.LZ4OutputStream;
* - Slow
*/
public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
private UUID uuid;
private File bdFile;
private File nbtfFile;
private File nbttFile;
@ -95,6 +98,14 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
private AtomicInteger size = new AtomicInteger();
private World world;
public void deleteFiles() {
bdFile.delete();
nbtfFile.delete();
nbttFile.delete();
entfFile.delete();
enttFile.delete();
}
public DiskStorageHistory(World world, UUID uuid) {
size = new AtomicInteger();
String base = "history" + File.separator + world.getName() + File.separator + uuid;
@ -119,6 +130,7 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
}
public void init(World world, UUID uuid, int i) {
this.uuid = uuid;
this.world = world;
String base = "history" + File.separator + world.getName() + File.separator + uuid;
base += File.separator + i;
@ -129,6 +141,14 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
bdFile = new File(Fawe.imp().getDirectory(), base + ".bd");
}
public UUID getUUID() {
return uuid;
}
public File getBDFile() {
return bdFile;
}
public EditSession toEditSession(Player player) {
EditSessionFactory factory = WorldEdit.getInstance().getEditSessionFactory();
EditSession edit = factory.getEditSession(world, -1, null, player);
@ -335,6 +355,82 @@ public class DiskStorageHistory implements ChangeSet, FaweChangeSet {
return osENTT;
}
int fx;
int fz;
public int[] readHeaderAndFooter(RegionWrapper requiredRegion) {
if (fx == 0 && fz == 0 && bdFile.exists()) {
if ((ox != 0 || oz != 0) && !requiredRegion.isIn(ox, oz)) {
return new int[] {ox, oz, ox, oz};
}
try {
FileInputStream fis = new FileInputStream(bdFile);
LZ4Factory factory = LZ4Factory.fastestInstance();
LZ4Compressor compressor = factory.fastCompressor();
final InputStream gis;
if (Settings.COMPRESSION_LEVEL > 0) {
gis = new LZ4InputStream(new LZ4InputStream(fis));
} else {
gis = new LZ4InputStream(fis);
}
ox = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0));
oz = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0));
if (!requiredRegion.isIn(ox, oz)) {
fis.close();
gis.close();
return new int[] {ox, oz, ox, oz};
}
byte[] even = new byte[9];
byte[] odd = new byte[9];
byte[] result = null;
int i = 0;
while (true) {
if ((i++ & 1) == 0) {
if (gis.read(even) == -1) {
result = odd;
break;
}
} else {
if (gis.read(odd) == -1) {
result = even;
break;
}
}
}
fx = ((byte) result[0] & 0xFF) + ((byte) result[1] << 8) + ox;
fz = ((byte) result[2] & 0xFF) + ((byte) result[3] << 8) + oz;
fis.close();
gis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new int[] {ox, oz, fx, fz};
}
public IntegerPair readHeader() {
if (ox == 0 && oz == 0 && bdFile.exists()) {
try {
FileInputStream fis = new FileInputStream(bdFile);
LZ4Factory factory = LZ4Factory.fastestInstance();
LZ4Compressor compressor = factory.fastCompressor();
final InputStream gis;
if (Settings.COMPRESSION_LEVEL > 0) {
gis = new LZ4InputStream(new LZ4InputStream(fis));
} else {
gis = new LZ4InputStream(fis);
}
ox = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0));
oz = ((gis.read() << 24) + (gis.read() << 16) + (gis.read() << 8) + (gis.read() << 0));
fis.close();
gis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return new IntegerPair(ox, oz);
}
@SuppressWarnings("resource")
public Iterator<Change> getIterator(final boolean dir) {
flush();

View File

@ -2,15 +2,24 @@ package com.boydti.fawe.util;
import com.boydti.fawe.Fawe;
import com.boydti.fawe.config.BBC;
import com.boydti.fawe.object.FaweLocation;
import com.boydti.fawe.object.FawePlayer;
import com.boydti.fawe.object.RegionWrapper;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.changeset.DiskStorageHistory;
import com.sk89q.jnbt.CompoundTag;
import com.sk89q.jnbt.EndTag;
import com.sk89q.jnbt.ListTag;
import com.sk89q.jnbt.Tag;
import com.sk89q.worldedit.world.World;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.UUID;
public class MainUtil {
/*
@ -50,6 +59,136 @@ public class MainUtil {
}
}
public static String secToTime(long time) {
StringBuilder toreturn = new StringBuilder();
if (time>=33868800) {
int years = (int) (time/33868800);
time-=years*33868800;
toreturn.append(years+"y ");
}
if (time>=604800) {
int weeks = (int) (time/604800);
time-=weeks*604800;
toreturn.append(weeks+"w ");
}
if (time>=86400) {
int days = (int) (time/86400);
time-=days*86400;
toreturn.append(days+"d ");
}
if (time>=3600) {
int hours = (int) (time/3600);
time-=hours*3600;
toreturn.append(hours+"h ");
}
if (time>=60) {
int minutes = (int) (time/60);
time-=minutes*60;
toreturn.append(minutes+"m ");
}
if (toreturn.equals("")||time>0){
toreturn.append((time)+"s ");
}
return toreturn.toString().trim();
}
public static long timeToSec(String string) {
if (MathMan.isInteger(string)) {
return Long.parseLong(string);
}
string = string.toLowerCase().trim().toLowerCase();
if (string.equalsIgnoreCase("false")) {
return 0;
}
String[] split = string.split(" ");
long time = 0;
for (String value : split) {
int nums = Integer.parseInt(value.replaceAll("[^\\d]", ""));
String letters = value.replaceAll("[^a-z]", "");
switch (letters) {
case "week":
case "weeks":
case "wks":
case "w":
time += 604800 * nums;
case "days":
case "day":
case "d":
time += 86400 * nums;
case "hour":
case "hr":
case "hrs":
case "hours":
case "h":
time += 3600 * nums;
case "minutes":
case "minute":
case "mins":
case "min":
case "m":
time += 60 * nums;
case "seconds":
case "second":
case "secs":
case "sec":
case "s":
time += nums;
}
}
return time;
}
public static List<DiskStorageHistory> getBDFiles(FaweLocation origin, UUID user, int radius, long timediff) {
File history = new File(Fawe.imp().getDirectory(), "history" + File.separator + origin.world);
if (!history.exists()) {
return new ArrayList<>();
}
long now = System.currentTimeMillis();
ArrayList<File> files = new ArrayList<>();
for (File userFile : history.listFiles()) {
if (!userFile.isDirectory()) {
continue;
}
UUID userUUID;
try {
userUUID = UUID.fromString(userFile.getName());
} catch (IllegalArgumentException e) {
continue;
}
if (user != null && !userUUID.equals(user)) {
continue;
}
ArrayList<Integer> ids = new ArrayList<>();
for (File file : userFile.listFiles()) {
if (file.getName().endsWith(".bd")) {
if (timediff > Integer.MAX_VALUE || now - file.lastModified() <= timediff) {
files.add(file);
}
}
}
}
World world = origin.getWorld();
Collections.sort(files, new Comparator<File>() {
@Override
public int compare(File a, File b) {
long value = a.lastModified() - b.lastModified();
return value == 0 ? 0 : value < 0 ? 1 : -1;
}
});
ArrayList<DiskStorageHistory> result = new ArrayList<>();
for (File file : files) {
UUID uuid = UUID.fromString(file.getParentFile().getName());
DiskStorageHistory dsh = new DiskStorageHistory(world, uuid, Integer.parseInt(file.getName().split("\\.")[0]));
int[] headerAndFooter = dsh.readHeaderAndFooter(new RegionWrapper(origin.x - 512, origin.x + 512, origin.z - 512, origin.z + 512));
RegionWrapper region = new RegionWrapper(headerAndFooter[0], headerAndFooter[2], headerAndFooter[1], headerAndFooter[3]);
if (region.distance(origin.x, origin.z) <= radius) {
result.add(dsh);
}
}
return result;
}
public static void deleteOlder(File directory, final long timeDiff) {
final long now = System.currentTimeMillis();
iterateFiles(directory, new RunnableVal<File>() {

View File

@ -0,0 +1,194 @@
package com.boydti.fawe.util;
public class MathMan {
private static final int ATAN2_BITS = 7;
private static final int ATAN2_BITS2 = ATAN2_BITS << 1;
private static final int ATAN2_MASK = ~(-1 << ATAN2_BITS2);
private static final int ATAN2_COUNT = ATAN2_MASK + 1;
private static final int ATAN2_DIM = (int) Math.sqrt(ATAN2_COUNT);
private static final float INV_ATAN2_DIM_MINUS_1 = 1.0f / (ATAN2_DIM - 1);
private static final float[] atan2 = new float[ATAN2_COUNT];
static {
for (int i = 0; i < ATAN2_DIM; i++) {
for (int j = 0; j < ATAN2_DIM; j++) {
float x0 = (float) i / ATAN2_DIM;
float y0 = (float) j / ATAN2_DIM;
atan2[(j * ATAN2_DIM) + i] = (float) Math.atan2(y0, x0);
}
}
}
public static double getMean(int[] array) {
double count = 0;
for (int i : array) {
count += i;
}
return count / array.length;
}
public static double getMean(double[] array) {
double count = 0;
for (double i : array) {
count += i;
}
return count / array.length;
}
public static int pair(short x, short y) {
return (x << 16) | (y & 0xFFFF);
}
public static short unpairX(int hash) {
return (short) (hash >> 16);
}
public static short unpairY(int hash) {
return (short) (hash & 0xFFFF);
}
/**
* Returns [x, y, z]
* @param yaw
* @param pitch
* @return
*/
public static float[] getDirection(float yaw, float pitch) {
double pitch_sin = Math.sin(pitch);
return new float[]{(float) (pitch_sin * Math.cos(yaw)), (float) (pitch_sin * Math.sin(yaw)), (float) Math.cos(pitch)};
}
public static int roundInt(double value) {
return (int) (value < 0 ? (value == (int) value) ? value : value - 1 : value);
}
/**
* Returns [ pitch, yaw ]
* @param x
* @param y
* @param z
* @return
*/
public static float[] getPitchAndYaw(float x, float y, float z) {
float distance = sqrtApprox((z * z) + (x * x));
return new float[]{atan2(y, distance), atan2(x, z)};
}
public static final float atan2(float y, float x) {
float add, mul;
if (x < 0.0f) {
if (y < 0.0f) {
x = -x;
y = -y;
mul = 1.0f;
} else {
x = -x;
mul = -1.0f;
}
add = -3.141592653f;
} else {
if (y < 0.0f) {
y = -y;
mul = -1.0f;
} else {
mul = 1.0f;
}
add = 0.0f;
}
float invDiv = 1.0f / (((x < y) ? y : x) * INV_ATAN2_DIM_MINUS_1);
int xi = (int) (x * invDiv);
int yi = (int) (y * invDiv);
return (atan2[(yi * ATAN2_DIM) + xi] + add) * mul;
}
public static float sqrtApprox(float f) {
return f * Float.intBitsToFloat(0x5f375a86 - (Float.floatToIntBits(f) >> 1));
}
public static double sqrtApprox(double d) {
return Double.longBitsToDouble(((Double.doubleToLongBits(d) - (1l << 52)) >> 1) + (1l << 61));
}
public static float invSqrt(float x) {
float xhalf = 0.5f * x;
int i = Float.floatToIntBits(x);
i = 0x5f3759df - (i >> 1);
x = Float.intBitsToFloat(i);
x = x * (1.5f - (xhalf * x * x));
return x;
}
public static int getPositiveId(int i) {
if (i < 0) {
return (-i * 2) - 1;
}
return i * 2;
}
public static boolean isInteger(String str) {
if (str == null) {
return false;
}
int length = str.length();
if (length == 0) {
return false;
}
int i = 0;
if (str.charAt(0) == '-') {
if (length == 1) {
return false;
}
i = 1;
}
for (; i < length; i++) {
char c = str.charAt(i);
if ((c <= '/') || (c >= ':')) {
return false;
}
}
return true;
}
public static double getSD(double[] array, double av) {
double sd = 0;
for (double element : array) {
sd += Math.pow(Math.abs(element - av), 2);
}
return Math.sqrt(sd / array.length);
}
public static double getSD(int[] array, double av) {
double sd = 0;
for (int element : array) {
sd += Math.pow(Math.abs(element - av), 2);
}
return Math.sqrt(sd / array.length);
}
public static int mod(int x, int y) {
if (isPowerOfTwo(y)) {
return x & (y - 1);
}
return x % y;
}
public static int unsignedmod(int x, int y) {
if (isPowerOfTwo(y)) {
return x & (y - 1);
}
return x % y;
}
public static boolean isPowerOfTwo(int x) {
return (x & (x - 1)) == 0;
}
}