Further API tweaks

This commit is contained in:
Andrew Steinborn
2021-06-18 16:18:15 -04:00
parent 5579f76316
commit cbee52aa61
22 changed files with 95 additions and 106 deletions

View File

@@ -0,0 +1,41 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util;
import java.time.Duration;
/**
* Provides utility functions for working with durations.
*/
public final class DurationUtils {
private static final long ONE_TICK_IN_MILLISECONDS = 50;
private DurationUtils() {
throw new AssertionError("Instances of this class should not be created.");
}
/**
* Converts the given duration to Minecraft ticks.
*
* @param duration the duration to convert into Minecraft ticks
* @return the duration represented as the number of Minecraft ticks
*/
public static long toTicks(Duration duration) {
return duration.toMillis() / ONE_TICK_IN_MILLISECONDS;
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
public class FileSystemUtils {
/**
* Visits the resources at the given {@link Path} within the resource
* path of the given {@link Class}.
*
* @param target The target class of the resource path to scan
* @param consumer The consumer to visit the resolved path
* @param firstPathComponent First path component
* @param remainingPathComponents Remaining path components
*/
@SuppressFBWarnings({"RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE"})
public static boolean visitResources(Class<?> target, Consumer<Path> consumer,
String firstPathComponent, String... remainingPathComponents)
throws IOException {
final URL knownResource = FileSystemUtils.class.getClassLoader()
.getResource("default-velocity.toml");
if (knownResource == null) {
throw new IllegalStateException(
"default-velocity.toml does not exist, don't know where we are");
}
if (knownResource.getProtocol().equals("jar")) {
// Running from a JAR
String jarPathRaw = Iterables.get(Splitter.on('!').split(knownResource.toString()), 0);
URI path = URI.create(jarPathRaw + "!/");
try (FileSystem fileSystem = FileSystems.newFileSystem(path, Map.of("create", "true"))) {
Path toVisit = fileSystem.getPath(firstPathComponent, remainingPathComponents);
if (Files.exists(toVisit)) {
consumer.accept(toVisit);
return true;
}
return false;
}
} else {
// Running from the file system
URI uri;
List<String> componentList = new ArrayList<>();
componentList.add(firstPathComponent);
componentList.addAll(Arrays.asList(remainingPathComponents));
try {
URL url = target.getClassLoader().getResource(String.join("/", componentList));
if (url == null) {
return false;
}
uri = url.toURI();
} catch (URISyntaxException e) {
throw new IllegalStateException(e);
}
consumer.accept(Paths.get(uri));
return true;
}
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util.collect;
import com.google.common.base.Preconditions;
import com.google.common.collect.ForwardingSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
* An unsynchronized collection that puts an upper bound on the size of the collection.
*/
public final class CappedSet<T> extends ForwardingSet<T> {
private final Set<T> delegate;
private final int upperSize;
private CappedSet(Set<T> delegate, int upperSize) {
this.delegate = delegate;
this.upperSize = upperSize;
}
/**
* Creates a capped collection backed by a {@link HashSet}.
* @param maxSize the maximum size of the collection
* @param <T> the type of elements in the collection
* @return the new collection
*/
public static <T> Set<T> create(int maxSize) {
return new CappedSet<>(new HashSet<>(), maxSize);
}
@Override
protected Set<T> delegate() {
return delegate;
}
@Override
public boolean add(T element) {
if (this.delegate.size() >= upperSize) {
Preconditions.checkState(this.delegate.contains(element),
"collection is too large (%s >= %s)",
this.delegate.size(), this.upperSize);
return false;
}
return this.delegate.add(element);
}
@Override
public boolean addAll(Collection<? extends T> collection) {
return this.standardAddAll(collection);
}
}

View File

@@ -0,0 +1,92 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util.collect;
import java.util.EnumSet;
/**
* An immutable map of {@link Enum} entries to {@code int}s.
* @param <E> the enum type
*/
public final class Enum2IntMap<E extends Enum<E>> {
private final int[] mappings;
private Enum2IntMap(int[] mappings) {
this.mappings = mappings;
}
public int get(E key) {
return mappings[key.ordinal()];
}
public static class Builder<E extends Enum<E>> {
private final int[] mappings;
private final EnumSet<E> populated;
private int defaultValue = -1;
public Builder(Class<E> klazz) {
this.mappings = new int[klazz.getEnumConstants().length];
this.populated = EnumSet.noneOf(klazz);
}
/**
* Adds a mapping to the map.
* @param key the key to use
* @param value the value to associate with the key
* @return {@code this}, for chaining
*/
public Builder<E> put(E key, int value) {
this.mappings[key.ordinal()] = value;
this.populated.add(key);
return this;
}
public Builder<E> remove(E key) {
this.populated.remove(key);
return this;
}
public Builder<E> defaultValue(int defaultValue) {
this.defaultValue = defaultValue;
return this;
}
/**
* Fetches a mapping from the map.
* @param key the key to use
* @return the value in the map
*/
public int get(E key) {
if (this.populated.contains(key)) {
return this.mappings[key.ordinal()];
}
return this.defaultValue;
}
/**
* Builds the map.
* @return the built map
*/
public Enum2IntMap<E> build() {
for (E unpopulated : EnumSet.complementOf(this.populated)) {
this.mappings[unpopulated.ordinal()] = this.defaultValue;
}
return new Enum2IntMap<>(this.mappings.clone());
}
}
}

View File

@@ -0,0 +1,58 @@
/*
* Copyright (C) 2018 Velocity Contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.util.concurrent;
/**
* A class that guarantees that a given initialization shall only occur once. The implementation
* is (almost) a direct Java port of the Go {@code sync.Once} type (see the
* <a href="https://golang.org/pkg/sync/#Once">Go documentation</a>) and thus has similar
* semantics.
*/
public final class Once {
private static final int NOT_STARTED = 0;
private static final int COMPLETED = 1;
private volatile int completed = NOT_STARTED;
private final Object lock = new Object();
/**
* Calls {@code runnable.run()} exactly once if this instance is being called for the first time,
* otherwise the invocation shall wait until {@code runnable.run()} completes. The first runnable
* used when this function is called is run. Future calls to this method once the initial
* runnable completes are no-ops - a new instance should be used instead.
*
* @param runnable the runnable to run
*/
public void run(Runnable runnable) {
if (completed == NOT_STARTED) {
slowRun(runnable);
}
}
private void slowRun(Runnable runnable) {
synchronized (lock) {
if (completed == NOT_STARTED) {
try {
runnable.run();
} finally {
completed = COMPLETED;
}
}
}
}
}