1
0
mirror of https://github.com/SquidDev-CC/CC-Tweaked synced 2024-12-14 12:10:30 +00:00

Switch the core library to be non-null by default

See comments in c8c128d335 for further
details. This requires /relatively/ few changes - mostly cases we were
missing @Nullable annotations.
This commit is contained in:
Jonathan Coates 2022-11-06 11:55:26 +00:00
parent c8c128d335
commit c82f37d3bf
No known key found for this signature in database
GPG Key ID: B9E431FF07C98D06
88 changed files with 465 additions and 626 deletions

View File

@ -6,6 +6,7 @@ import java.nio.charset.StandardCharsets
plugins { plugins {
`java-library` `java-library`
idea
jacoco jacoco
checkstyle checkstyle
id("com.diffplug.spotless") id("com.diffplug.spotless")
@ -139,3 +140,12 @@ spotless {
ktlint().editorConfigOverride(ktlintConfig) ktlint().editorConfigOverride(ktlintConfig)
} }
} }
idea.module {
excludeDirs.addAll(project.files("run", "out", "logs").files)
// Force Gradle to write to inherit the output directory from the parent, instead of writing to out/xxx/classes.
// This is required for Loom, and we patch Forge's run configurations to work there.
// TODO: Submit a patch to Forge to support ProjectRootManager.
inheritOutputDirs = true
}

View File

@ -18,6 +18,7 @@ dependencies {
compileOnly(project(":mc-stubs")) compileOnly(project(":mc-stubs"))
compileOnlyApi(libs.jsr305) compileOnlyApi(libs.jsr305)
compileOnlyApi(libs.checkerFramework) compileOnlyApi(libs.checkerFramework)
compileOnlyApi(libs.jetbrainsAnnotations)
"docApi"(project(":")) "docApi"(project(":"))
} }

View File

@ -5,6 +5,8 @@
*/ */
package dan200.computercraft.api.lua; package dan200.computercraft.api.lua;
import org.jetbrains.annotations.Contract;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Map; import java.util.Map;
@ -387,7 +389,9 @@ public interface IArguments {
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a string. * @throws LuaException If the value is not a string.
*/ */
default String optString(int index, String def) throws LuaException { @Nullable
@Contract("_, !null -> !null")
default String optString(int index, @Nullable String def) throws LuaException {
return optString(index).orElse(def); return optString(index).orElse(def);
} }
@ -399,7 +403,9 @@ public interface IArguments {
* @return The argument's value, or {@code def} if none was provided. * @return The argument's value, or {@code def} if none was provided.
* @throws LuaException If the value is not a table. * @throws LuaException If the value is not a table.
*/ */
default Map<?, ?> optTable(int index, Map<Object, Object> def) throws LuaException { @Nullable
@Contract("_, !null -> !null")
default Map<?, ?> optTable(int index, @Nullable Map<Object, Object> def) throws LuaException {
return optTable(index).orElse(def); return optTable(index).orElse(def);
} }
} }

View File

@ -86,6 +86,7 @@ public interface IComputerAccess {
* @see #unmount(String) * @see #unmount(String)
* @see IMount * @see IMount
*/ */
@Nullable
String mountWritable(String desiredLocation, IWritableMount mount, String driveName); String mountWritable(String desiredLocation, IWritableMount mount, String driveName);
/** /**

View File

@ -5,6 +5,7 @@ plugins {
id("cc-tweaked.kotlin-convention") id("cc-tweaked.kotlin-convention")
id("cc-tweaked.java-convention") id("cc-tweaked.java-convention")
id("cc-tweaked.publishing") id("cc-tweaked.publishing")
id("cc-tweaked.errorprone")
id("cc-tweaked") id("cc-tweaked")
} }

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.ILuaAPIFactory; import dan200.computercraft.api.lua.ILuaAPIFactory;
import javax.annotation.Nonnull;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
@ -20,7 +19,7 @@ public final class ApiFactories {
private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>(); private static final Collection<ILuaAPIFactory> factories = new LinkedHashSet<>();
private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories); private static final Collection<ILuaAPIFactory> factoriesView = Collections.unmodifiableCollection(factories);
public static synchronized void register(@Nonnull ILuaAPIFactory factory) { public static synchronized void register(ILuaAPIFactory factory) {
Objects.requireNonNull(factory, "provider cannot be null"); Objects.requireNonNull(factory, "provider cannot be null");
factories.add(factory); factories.add(factory);
} }

View File

@ -13,7 +13,7 @@ import dan200.computercraft.core.filesystem.FileSystemException;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.util.HashSet; import java.util.HashSet;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
@ -40,8 +40,9 @@ public abstract class ComputerAccess implements IComputerAccess {
mounts.clear(); mounts.clear();
} }
@Nullable
@Override @Override
public synchronized String mount(@Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName) { public synchronized String mount(String desiredLoc, IMount mount, String driveName) {
Objects.requireNonNull(desiredLoc, "desiredLocation cannot be null"); Objects.requireNonNull(desiredLoc, "desiredLocation cannot be null");
Objects.requireNonNull(mount, "mount cannot be null"); Objects.requireNonNull(mount, "mount cannot be null");
Objects.requireNonNull(driveName, "driveName cannot be null"); Objects.requireNonNull(driveName, "driveName cannot be null");
@ -49,14 +50,14 @@ public abstract class ComputerAccess implements IComputerAccess {
// Mount the location // Mount the location
String location; String location;
var fileSystem = environment.getFileSystem(); var fileSystem = environment.getFileSystem();
if (fileSystem == null) throw new IllegalStateException("File system has not been created");
synchronized (fileSystem) { synchronized (fileSystem) {
location = findFreeLocation(desiredLoc); location = findFreeLocation(desiredLoc);
if (location != null) { if (location != null) {
try { try {
fileSystem.mount(driveName, location, mount); fileSystem.mount(driveName, location, mount);
} catch (FileSystemException ignored) { } catch (FileSystemException e) {
throw new IllegalArgumentException(e.getMessage());
} }
} }
} }
@ -65,8 +66,9 @@ public abstract class ComputerAccess implements IComputerAccess {
return location; return location;
} }
@Nullable
@Override @Override
public synchronized String mountWritable(@Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName) { public synchronized String mountWritable(String desiredLoc, IWritableMount mount, String driveName) {
Objects.requireNonNull(desiredLoc, "desiredLocation cannot be null"); Objects.requireNonNull(desiredLoc, "desiredLocation cannot be null");
Objects.requireNonNull(mount, "mount cannot be null"); Objects.requireNonNull(mount, "mount cannot be null");
Objects.requireNonNull(driveName, "driveName cannot be null"); Objects.requireNonNull(driveName, "driveName cannot be null");
@ -74,14 +76,14 @@ public abstract class ComputerAccess implements IComputerAccess {
// Mount the location // Mount the location
String location; String location;
var fileSystem = environment.getFileSystem(); var fileSystem = environment.getFileSystem();
if (fileSystem == null) throw new IllegalStateException("File system has not been created");
synchronized (fileSystem) { synchronized (fileSystem) {
location = findFreeLocation(desiredLoc); location = findFreeLocation(desiredLoc);
if (location != null) { if (location != null) {
try { try {
fileSystem.mountWritable(driveName, location, mount); fileSystem.mountWritable(driveName, location, mount);
} catch (FileSystemException ignored) { } catch (FileSystemException e) {
throw new IllegalArgumentException(e.getMessage());
} }
} }
} }
@ -91,7 +93,7 @@ public abstract class ComputerAccess implements IComputerAccess {
} }
@Override @Override
public void unmount(String location) { public void unmount(@Nullable String location) {
if (location == null) return; if (location == null) return;
if (!mounts.contains(location)) throw new IllegalStateException("You didn't mount this location"); if (!mounts.contains(location)) throw new IllegalStateException("You didn't mount this location");
@ -105,17 +107,17 @@ public abstract class ComputerAccess implements IComputerAccess {
} }
@Override @Override
public void queueEvent(@Nonnull String event, Object... arguments) { public void queueEvent(String event, @Nullable Object... arguments) {
Objects.requireNonNull(event, "event cannot be null"); Objects.requireNonNull(event, "event cannot be null");
environment.queueEvent(event, arguments); environment.queueEvent(event, arguments);
} }
@Nonnull
@Override @Override
public IWorkMonitor getMainThreadMonitor() { public IWorkMonitor getMainThreadMonitor() {
return environment.getMainThreadMonitor(); return environment.getMainThreadMonitor();
} }
@Nullable
private String findFreeLocation(String desiredLoc) { private String findFreeLocation(String desiredLoc) {
try { try {
var fileSystem = environment.getFileSystem(); var fileSystem = environment.getFileSystem();

View File

@ -17,6 +17,7 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.filesystem.FileSystemException; import dan200.computercraft.core.filesystem.FileSystemException;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import javax.annotation.Nullable;
import java.nio.file.attribute.FileTime; import java.nio.file.attribute.FileTime;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@ -58,7 +59,7 @@ import java.util.function.Function;
*/ */
public class FSAPI implements ILuaAPI { public class FSAPI implements ILuaAPI {
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
private FileSystem fileSystem = null; private @Nullable FileSystem fileSystem = null;
public FSAPI(IAPIEnvironment env) { public FSAPI(IAPIEnvironment env) {
environment = env; environment = env;
@ -79,6 +80,12 @@ public class FSAPI implements ILuaAPI {
fileSystem = null; fileSystem = null;
} }
private FileSystem getFileSystem() {
var filesystem = fileSystem;
if (filesystem == null) throw new IllegalStateException("File system is not mounted");
return filesystem;
}
/** /**
* Returns a list of files in a directory. * Returns a list of files in a directory.
* *
@ -97,7 +104,7 @@ public class FSAPI implements ILuaAPI {
public final String[] list(String path) throws LuaException { public final String[] list(String path) throws LuaException {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
try { try {
return fileSystem.list(path); return getFileSystem().list(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -178,7 +185,7 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final long getSize(String path) throws LuaException { public final long getSize(String path) throws LuaException {
try { try {
return fileSystem.getSize(path); return getFileSystem().getSize(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -193,7 +200,7 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final boolean exists(String path) { public final boolean exists(String path) {
try { try {
return fileSystem.exists(path); return getFileSystem().exists(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
return false; return false;
} }
@ -208,7 +215,7 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final boolean isDir(String path) { public final boolean isDir(String path) {
try { try {
return fileSystem.isDir(path); return getFileSystem().isDir(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
return false; return false;
} }
@ -223,7 +230,7 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final boolean isReadOnly(String path) { public final boolean isReadOnly(String path) {
try { try {
return fileSystem.isReadOnly(path); return getFileSystem().isReadOnly(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
return false; return false;
} }
@ -239,7 +246,7 @@ public class FSAPI implements ILuaAPI {
public final void makeDir(String path) throws LuaException { public final void makeDir(String path) throws LuaException {
try { try {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
fileSystem.makeDir(path); getFileSystem().makeDir(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -258,7 +265,7 @@ public class FSAPI implements ILuaAPI {
public final void move(String path, String dest) throws LuaException { public final void move(String path, String dest) throws LuaException {
try { try {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
fileSystem.move(path, dest); getFileSystem().move(path, dest);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -277,7 +284,7 @@ public class FSAPI implements ILuaAPI {
public final void copy(String path, String dest) throws LuaException { public final void copy(String path, String dest) throws LuaException {
try { try {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
fileSystem.copy(path, dest); getFileSystem().copy(path, dest);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -296,7 +303,7 @@ public class FSAPI implements ILuaAPI {
public final void delete(String path) throws LuaException { public final void delete(String path) throws LuaException {
try { try {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
fileSystem.delete(path); getFileSystem().delete(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -363,32 +370,32 @@ public class FSAPI implements ILuaAPI {
switch (mode) { switch (mode) {
case "r" -> { case "r" -> {
// Open the file for reading, then create a wrapper around the reader // Open the file for reading, then create a wrapper around the reader
var reader = fileSystem.openForRead(path, EncodedReadableHandle::openUtf8); var reader = getFileSystem().openForRead(path, EncodedReadableHandle::openUtf8);
return new Object[]{ new EncodedReadableHandle(reader.get(), reader) }; return new Object[]{ new EncodedReadableHandle(reader.get(), reader) };
} }
case "w" -> { case "w" -> {
// Open the file for writing, then create a wrapper around the writer // Open the file for writing, then create a wrapper around the writer
var writer = fileSystem.openForWrite(path, false, EncodedWritableHandle::openUtf8); var writer = getFileSystem().openForWrite(path, false, EncodedWritableHandle::openUtf8);
return new Object[]{ new EncodedWritableHandle(writer.get(), writer) }; return new Object[]{ new EncodedWritableHandle(writer.get(), writer) };
} }
case "a" -> { case "a" -> {
// Open the file for appending, then create a wrapper around the writer // Open the file for appending, then create a wrapper around the writer
var writer = fileSystem.openForWrite(path, true, EncodedWritableHandle::openUtf8); var writer = getFileSystem().openForWrite(path, true, EncodedWritableHandle::openUtf8);
return new Object[]{ new EncodedWritableHandle(writer.get(), writer) }; return new Object[]{ new EncodedWritableHandle(writer.get(), writer) };
} }
case "rb" -> { case "rb" -> {
// Open the file for binary reading, then create a wrapper around the reader // Open the file for binary reading, then create a wrapper around the reader
var reader = fileSystem.openForRead(path, Function.identity()); var reader = getFileSystem().openForRead(path, Function.identity());
return new Object[]{ BinaryReadableHandle.of(reader.get(), reader) }; return new Object[]{ BinaryReadableHandle.of(reader.get(), reader) };
} }
case "wb" -> { case "wb" -> {
// Open the file for binary writing, then create a wrapper around the writer // Open the file for binary writing, then create a wrapper around the writer
var writer = fileSystem.openForWrite(path, false, Function.identity()); var writer = getFileSystem().openForWrite(path, false, Function.identity());
return new Object[]{ BinaryWritableHandle.of(writer.get(), writer) }; return new Object[]{ BinaryWritableHandle.of(writer.get(), writer) };
} }
case "ab" -> { case "ab" -> {
// Open the file for binary appending, then create a wrapper around the reader // Open the file for binary appending, then create a wrapper around the reader
var writer = fileSystem.openForWrite(path, true, Function.identity()); var writer = getFileSystem().openForWrite(path, true, Function.identity());
return new Object[]{ BinaryWritableHandle.of(writer.get(), writer) }; return new Object[]{ BinaryWritableHandle.of(writer.get(), writer) };
} }
default -> throw new LuaException("Unsupported mode"); default -> throw new LuaException("Unsupported mode");
@ -404,7 +411,7 @@ public class FSAPI implements ILuaAPI {
* @param path The path to get the drive of. * @param path The path to get the drive of.
* @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. * @return The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files.
* @throws LuaException If the path doesn't exist. * @throws LuaException If the path doesn't exist.
* @cc.treturn string The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files. * @cc.treturn string|nil The name of the drive that the file is on; e.g. {@code hdd} for local files, or {@code rom} for ROM files.
* @cc.usage Print the drives of a couple of mounts: * @cc.usage Print the drives of a couple of mounts:
* *
* <pre>{@code * <pre>{@code
@ -412,10 +419,11 @@ public class FSAPI implements ILuaAPI {
* print("/rom/: " .. fs.getDrive("rom")) * print("/rom/: " .. fs.getDrive("rom"))
* }</pre> * }</pre>
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] getDrive(String path) throws LuaException { public final Object[] getDrive(String path) throws LuaException {
try { try {
return fileSystem.exists(path) ? new Object[]{ fileSystem.getMountLabel(path) } : null; return getFileSystem().exists(path) ? new Object[]{ getFileSystem().getMountLabel(path) } : null;
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -435,7 +443,7 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final Object getFreeSpace(String path) throws LuaException { public final Object getFreeSpace(String path) throws LuaException {
try { try {
var freeSpace = fileSystem.getFreeSpace(path); var freeSpace = getFileSystem().getFreeSpace(path);
return freeSpace >= 0 ? freeSpace : "unlimited"; return freeSpace >= 0 ? freeSpace : "unlimited";
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
@ -459,7 +467,7 @@ public class FSAPI implements ILuaAPI {
public final String[] find(String path) throws LuaException { public final String[] find(String path) throws LuaException {
try { try {
environment.observe(Metrics.FS_OPS); environment.observe(Metrics.FS_OPS);
return fileSystem.find(path); return getFileSystem().find(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
@ -476,10 +484,11 @@ public class FSAPI implements ILuaAPI {
* @cc.since 1.87.0 * @cc.since 1.87.0
* @see #getFreeSpace To get the free space available on this drive. * @see #getFreeSpace To get the free space available on this drive.
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object getCapacity(String path) throws LuaException { public final Object getCapacity(String path) throws LuaException {
try { try {
var capacity = fileSystem.getCapacity(path); var capacity = getFileSystem().getCapacity(path);
return capacity.isPresent() ? capacity.getAsLong() : null; return capacity.isPresent() ? capacity.getAsLong() : null;
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
@ -508,21 +517,21 @@ public class FSAPI implements ILuaAPI {
@LuaFunction @LuaFunction
public final Map<String, Object> attributes(String path) throws LuaException { public final Map<String, Object> attributes(String path) throws LuaException {
try { try {
var attributes = fileSystem.getAttributes(path); var attributes = getFileSystem().getAttributes(path);
Map<String, Object> result = new HashMap<>(); Map<String, Object> result = new HashMap<>();
result.put("modification", getFileTime(attributes.lastModifiedTime())); result.put("modification", getFileTime(attributes.lastModifiedTime()));
result.put("modified", getFileTime(attributes.lastModifiedTime())); result.put("modified", getFileTime(attributes.lastModifiedTime()));
result.put("created", getFileTime(attributes.creationTime())); result.put("created", getFileTime(attributes.creationTime()));
result.put("size", attributes.isDirectory() ? 0 : attributes.size()); result.put("size", attributes.isDirectory() ? 0 : attributes.size());
result.put("isDir", attributes.isDirectory()); result.put("isDir", attributes.isDirectory());
result.put("isReadOnly", fileSystem.isReadOnly(path)); result.put("isReadOnly", getFileSystem().isReadOnly(path));
return result; return result;
} catch (FileSystemException e) { } catch (FileSystemException e) {
throw new LuaException(e.getMessage()); throw new LuaException(e.getMessage());
} }
} }
private static long getFileTime(FileTime time) { private static long getFileTime(@Nullable FileTime time) {
return time == null ? 0 : time.toMillis(); return time == null ? 0 : time.toMillis();
} }
} }

View File

@ -18,7 +18,6 @@ import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpHeaders;
import io.netty.handler.codec.http.HttpMethod; import io.netty.handler.codec.http.HttpMethod;
import javax.annotation.Nonnull;
import java.util.Collections; import java.util.Collections;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
@ -35,7 +34,7 @@ import static dan200.computercraft.core.apis.TableHelper.*;
public class HTTPAPI implements ILuaAPI { public class HTTPAPI implements ILuaAPI {
private final IAPIEnvironment apiEnvironment; private final IAPIEnvironment apiEnvironment;
private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>(ResourceGroup.DEFAULT); private final ResourceGroup<CheckUrl> checkUrls = new ResourceGroup<>(() -> ResourceGroup.DEFAULT_LIMIT);
private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>(() -> CoreConfig.httpMaxRequests); private final ResourceGroup<HttpRequest> requests = new ResourceQueue<>(() -> CoreConfig.httpMaxRequests);
private final ResourceGroup<Websocket> websockets = new ResourceGroup<>(() -> CoreConfig.httpMaxWebsockets); private final ResourceGroup<Websocket> websockets = new ResourceGroup<>(() -> CoreConfig.httpMaxWebsockets);
@ -155,8 +154,7 @@ public class HTTPAPI implements ILuaAPI {
} }
} }
@Nonnull private HttpHeaders getHeaders(Map<?, ?> headerTable) throws LuaException {
private HttpHeaders getHeaders(@Nonnull Map<?, ?> headerTable) throws LuaException {
HttpHeaders headers = new DefaultHttpHeaders(); HttpHeaders headers = new DefaultHttpHeaders();
for (Map.Entry<?, ?> entry : headerTable.entrySet()) { for (Map.Entry<?, ?> entry : headerTable.entrySet()) {
var value = entry.getValue(); var value = entry.getValue();

View File

@ -14,7 +14,6 @@ import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.metrics.Metric; import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public interface IAPIEnvironment { public interface IAPIEnvironment {
@ -27,16 +26,12 @@ public interface IAPIEnvironment {
int getComputerID(); int getComputerID();
@Nonnull
ComputerEnvironment getComputerEnvironment(); ComputerEnvironment getComputerEnvironment();
@Nonnull
GlobalEnvironment getGlobalEnvironment(); GlobalEnvironment getGlobalEnvironment();
@Nonnull
IWorkMonitor getMainThreadMonitor(); IWorkMonitor getMainThreadMonitor();
@Nonnull
Terminal getTerminal(); Terminal getTerminal();
FileSystem getFileSystem(); FileSystem getFileSystem();
@ -45,7 +40,7 @@ public interface IAPIEnvironment {
void reboot(); void reboot();
void queueEvent(String event, Object... args); void queueEvent(String event, @Nullable Object... args);
void setOutput(ComputerSide side, int output); void setOutput(ComputerSide side, int output);
@ -73,7 +68,7 @@ public interface IAPIEnvironment {
void cancelTimer(int id); void cancelTimer(int id);
void observe(@Nonnull Metric.Event event, long change); void observe(Metric.Event event, long change);
void observe(@Nonnull Metric.Counter counter); void observe(Metric.Counter counter);
} }

View File

@ -7,6 +7,7 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import javax.annotation.Nullable;
import java.time.Instant; import java.time.Instant;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.ZoneId; import java.time.ZoneId;
@ -117,6 +118,7 @@ final class LuaDateTime {
return def; return def;
} }
@Nullable
private static Boolean getBoolField(Map<?, ?> table, String field) throws LuaException { private static Boolean getBoolField(Map<?, ?> table, String field) throws LuaException {
var value = table.get(field); var value = table.get(field);
if (value instanceof Boolean || value == null) return (Boolean) value; if (value instanceof Boolean || value == null) return (Boolean) value;

View File

@ -13,7 +13,7 @@ import dan200.computercraft.core.util.StringUtil;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.time.Instant; import java.time.Instant;
import java.time.ZoneId; import java.time.ZoneId;
import java.time.ZoneOffset; import java.time.ZoneOffset;
@ -40,7 +40,7 @@ public class OSAPI implements ILuaAPI {
private record Alarm(double time, int day) implements Comparable<Alarm> { private record Alarm(double time, int day) implements Comparable<Alarm> {
@Override @Override
public int compareTo(@Nonnull Alarm o) { public int compareTo(Alarm o) {
var t = day * 24.0 + time; var t = day * 24.0 + time;
var ot = day * 24.0 + time; var ot = day * 24.0 + time;
return Double.compare(t, ot); return Double.compare(t, ot);
@ -241,9 +241,10 @@ public class OSAPI implements ILuaAPI {
* Returns the label of the computer, or {@code nil} if none is set. * Returns the label of the computer, or {@code nil} if none is set.
* *
* @return The label of the computer. * @return The label of the computer.
* @cc.treturn string The label of the computer. * @cc.treturn string|nil The label of the computer.
* @cc.since 1.3 * @cc.since 1.3
*/ */
@Nullable
@LuaFunction({ "getComputerLabel", "computerLabel" }) @LuaFunction({ "getComputerLabel", "computerLabel" })
public final Object[] getComputerLabel() { public final Object[] getComputerLabel() {
var label = apiEnvironment.getLabel(); var label = apiEnvironment.getLabel();
@ -258,7 +259,7 @@ public class OSAPI implements ILuaAPI {
*/ */
@LuaFunction @LuaFunction
public final void setComputerLabel(Optional<String> label) { public final void setComputerLabel(Optional<String> label) {
apiEnvironment.setLabel(StringUtil.normaliseLabel(label.orElse(null))); apiEnvironment.setLabel(label.map(StringUtil::normaliseLabel).orElse(null));
} }
/** /**

View File

@ -18,7 +18,6 @@ import dan200.computercraft.core.computer.ComputerSide;
import dan200.computercraft.core.metrics.Metrics; import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.util.LuaUtil; import dan200.computercraft.core.util.LuaUtil;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
@ -99,20 +98,23 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
// IComputerAccess implementation // IComputerAccess implementation
@Nullable
@Override @Override
public synchronized String mount(@Nonnull String desiredLoc, @Nonnull IMount mount, @Nonnull String driveName) { public synchronized String mount(String desiredLoc, IMount mount, String driveName) {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
return super.mount(desiredLoc, mount, driveName); return super.mount(desiredLoc, mount, driveName);
} }
@Nullable
@Override @Override
public synchronized String mountWritable(@Nonnull String desiredLoc, @Nonnull IWritableMount mount, @Nonnull String driveName) { public synchronized String mountWritable(String desiredLoc, IWritableMount mount, String driveName) {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
return super.mountWritable(desiredLoc, mount, driveName); return super.mountWritable(desiredLoc, mount, driveName);
} }
@Override @Override
public synchronized void unmount(String location) { public synchronized void unmount(@Nullable String location) {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
super.unmount(location); super.unmount(location);
} }
@ -124,19 +126,17 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
@Override @Override
public void queueEvent(@Nonnull String event, Object... arguments) { public void queueEvent(String event, @Nullable Object... arguments) {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
super.queueEvent(event, arguments); super.queueEvent(event, arguments);
} }
@Nonnull
@Override @Override
public String getAttachmentName() { public String getAttachmentName() {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
return side; return side;
} }
@Nonnull
@Override @Override
public Map<String, IPeripheral> getAvailablePeripherals() { public Map<String, IPeripheral> getAvailablePeripherals() {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
@ -153,7 +153,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
@Nullable @Nullable
@Override @Override
public IPeripheral getAvailablePeripheral(@Nonnull String name) { public IPeripheral getAvailablePeripheral(String name) {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
for (var wrapper : peripherals) { for (var wrapper : peripherals) {
@ -164,7 +164,6 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
return null; return null;
} }
@Nonnull
@Override @Override
public IWorkMonitor getMainThreadMonitor() { public IWorkMonitor getMainThreadMonitor() {
if (!attached) throw new NotAttachedException(); if (!attached) throw new NotAttachedException();
@ -185,7 +184,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
// IPeripheralChangeListener // IPeripheralChangeListener
@Override @Override
public void onPeripheralChanged(ComputerSide side, IPeripheral newPeripheral) { public void onPeripheralChanged(ComputerSide side, @Nullable IPeripheral newPeripheral) {
synchronized (peripherals) { synchronized (peripherals) {
var index = side.ordinal(); var index = side.ordinal();
if (peripherals[index] != null) { if (peripherals[index] != null) {
@ -253,6 +252,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
return false; return false;
} }
@Nullable
@LuaFunction @LuaFunction
public final Object[] getType(String sideName) { public final Object[] getType(String sideName) {
var side = ComputerSide.valueOfInsensitive(sideName); var side = ComputerSide.valueOfInsensitive(sideName);
@ -264,6 +264,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
} }
} }
@Nullable
@LuaFunction @LuaFunction
public final Object[] hasType(String sideName, String type) { public final Object[] hasType(String sideName, String type) {
var side = ComputerSide.valueOfInsensitive(sideName); var side = ComputerSide.valueOfInsensitive(sideName);
@ -278,6 +279,7 @@ public class PeripheralAPI implements ILuaAPI, IAPIEnvironment.IPeripheralChange
return null; return null;
} }
@Nullable
@LuaFunction @LuaFunction
public final Object[] getMethods(String sideName) { public final Object[] getMethods(String sideName) {
var side = ComputerSide.valueOfInsensitive(sideName); var side = ComputerSide.valueOfInsensitive(sideName);

View File

@ -10,6 +10,8 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import java.util.List;
/** /**
* Get and set redstone signals adjacent to this computer. * Get and set redstone signals adjacent to this computer.
* <p> * <p>
@ -72,7 +74,7 @@ public class RedstoneAPI implements ILuaAPI {
* @cc.since 1.2 * @cc.since 1.2
*/ */
@LuaFunction @LuaFunction
public final String[] getSides() { public final List<String> getSides() {
return ComputerSide.NAMES; return ComputerSide.NAMES;
} }

View File

@ -8,7 +8,6 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.api.lua.LuaValues;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Map; import java.util.Map;
@ -22,17 +21,15 @@ public final class TableHelper {
throw new IllegalStateException("Cannot instantiate singleton " + getClass().getName()); throw new IllegalStateException("Cannot instantiate singleton " + getClass().getName());
} }
@Nonnull public static LuaException badKey(String key, String expected, @Nullable Object actual) {
public static LuaException badKey(@Nonnull String key, @Nonnull String expected, @Nullable Object actual) {
return badKey(key, expected, LuaValues.getType(actual)); return badKey(key, expected, LuaValues.getType(actual));
} }
@Nonnull public static LuaException badKey(String key, String expected, String actual) {
public static LuaException badKey(@Nonnull String key, @Nonnull String expected, @Nonnull String actual) {
return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")"); return new LuaException("bad field '" + key + "' (" + expected + " expected, got " + actual + ")");
} }
public static double getNumberField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException { public static double getNumberField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value instanceof Number) { if (value instanceof Number) {
return ((Number) value).doubleValue(); return ((Number) value).doubleValue();
@ -41,7 +38,7 @@ public final class TableHelper {
} }
} }
public static int getIntField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException { public static int getIntField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value instanceof Number) { if (value instanceof Number) {
return (int) ((Number) value).longValue(); return (int) ((Number) value).longValue();
@ -50,11 +47,11 @@ public final class TableHelper {
} }
} }
public static double getRealField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException { public static double getRealField(Map<?, ?> table, String key) throws LuaException {
return checkReal(key, getNumberField(table, key)); return checkReal(key, getNumberField(table, key));
} }
public static boolean getBooleanField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException { public static boolean getBooleanField(Map<?, ?> table, String key) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value instanceof Boolean) { if (value instanceof Boolean) {
return (Boolean) value; return (Boolean) value;
@ -63,8 +60,7 @@ public final class TableHelper {
} }
} }
@Nonnull public static String getStringField(Map<?, ?> table, String key) throws LuaException {
public static String getStringField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value instanceof String) { if (value instanceof String) {
return (String) value; return (String) value;
@ -74,8 +70,7 @@ public final class TableHelper {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Nonnull public static Map<Object, Object> getTableField(Map<?, ?> table, String key) throws LuaException {
public static Map<Object, Object> getTableField(@Nonnull Map<?, ?> table, @Nonnull String key) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value instanceof Map) { if (value instanceof Map) {
return (Map<Object, Object>) value; return (Map<Object, Object>) value;
@ -84,7 +79,7 @@ public final class TableHelper {
} }
} }
public static double optNumberField(@Nonnull Map<?, ?> table, @Nonnull String key, double def) throws LuaException { public static double optNumberField(Map<?, ?> table, String key, double def) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value == null) { if (value == null) {
return def; return def;
@ -95,7 +90,7 @@ public final class TableHelper {
} }
} }
public static int optIntField(@Nonnull Map<?, ?> table, @Nonnull String key, int def) throws LuaException { public static int optIntField(Map<?, ?> table, String key, int def) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value == null) { if (value == null) {
return def; return def;
@ -106,11 +101,11 @@ public final class TableHelper {
} }
} }
public static double optRealField(@Nonnull Map<?, ?> table, @Nonnull String key, double def) throws LuaException { public static double optRealField(Map<?, ?> table, String key, double def) throws LuaException {
return checkReal(key, optNumberField(table, key, def)); return checkReal(key, optNumberField(table, key, def));
} }
public static boolean optBooleanField(@Nonnull Map<?, ?> table, @Nonnull String key, boolean def) throws LuaException { public static boolean optBooleanField(Map<?, ?> table, String key, boolean def) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value == null) { if (value == null) {
return def; return def;
@ -121,7 +116,8 @@ public final class TableHelper {
} }
} }
public static String optStringField(@Nonnull Map<?, ?> table, @Nonnull String key, String def) throws LuaException { @Nullable
public static String optStringField(Map<?, ?> table, String key, @Nullable String def) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value == null) { if (value == null) {
return def; return def;
@ -133,7 +129,7 @@ public final class TableHelper {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static Map<Object, Object> optTableField(@Nonnull Map<?, ?> table, @Nonnull String key, Map<Object, Object> def) throws LuaException { public static Map<Object, Object> optTableField(Map<?, ?> table, String key, Map<Object, Object> def) throws LuaException {
var value = table.get(key); var value = table.get(key);
if (value == null) { if (value == null) {
return def; return def;
@ -144,7 +140,7 @@ public final class TableHelper {
} }
} }
private static double checkReal(@Nonnull String key, double value) throws LuaException { private static double checkReal(String key, double value) throws LuaException {
if (!Double.isFinite(value)) throw badKey(key, "number", getNumericType(value)); if (!Double.isFinite(value)) throw badKey(key, "number", getNumericType(value));
return value; return value;
} }

View File

@ -12,7 +12,6 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import javax.annotation.Nonnull;
/** /**
* Interact with a computer's terminal or monitors, writing text and drawing * Interact with a computer's terminal or monitors, writing text and drawing
@ -51,7 +50,6 @@ public class TermAPI extends TermMethods implements ILuaAPI {
return new Object[]{ c.getR(), c.getG(), c.getB() }; return new Object[]{ c.getR(), c.getG(), c.getB() };
} }
@Nonnull
@Override @Override
public Terminal getTerminal() { public Terminal getTerminal() {
return terminal; return terminal;

View File

@ -12,7 +12,6 @@ import dan200.computercraft.core.terminal.Palette;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.core.util.StringUtil; import dan200.computercraft.core.util.StringUtil;
import javax.annotation.Nonnull;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
/** /**
@ -30,7 +29,6 @@ public abstract class TermMethods {
return bit; return bit;
} }
@Nonnull
public abstract Terminal getTerminal() throws LuaException; public abstract Terminal getTerminal() throws LuaException;
/** /**

View File

@ -8,7 +8,9 @@ package dan200.computercraft.core.apis.handles;
import dan200.computercraft.api.lua.LuaException; import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable; import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.core.util.Nullability;
import javax.annotation.Nullable;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -28,10 +30,10 @@ public class BinaryReadableHandle extends HandleGeneric {
private static final int BUFFER_SIZE = 8192; private static final int BUFFER_SIZE = 8192;
private final ReadableByteChannel reader; private final ReadableByteChannel reader;
final SeekableByteChannel seekable; final @Nullable SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate(1); private final ByteBuffer single = ByteBuffer.allocate(1);
BinaryReadableHandle(ReadableByteChannel reader, SeekableByteChannel seekable, TrackingCloseable closeable) { BinaryReadableHandle(ReadableByteChannel reader, @Nullable SeekableByteChannel seekable, TrackingCloseable closeable) {
super(closeable); super(closeable);
this.reader = reader; this.reader = reader;
this.seekable = seekable; this.seekable = seekable;
@ -59,6 +61,7 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn [3] string The bytes read as a string. This is returned when the {@code count} is given. * @cc.treturn [3] string The bytes read as a string. This is returned when the {@code count} is given.
* @cc.changed 1.80pr1 Now accepts an integer argument to read multiple bytes, returning a string instead of a number. * @cc.changed 1.80pr1 Now accepts an integer argument to read multiple bytes, returning a string instead of a number.
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] read(Optional<Integer> countArg) throws LuaException { public final Object[] read(Optional<Integer> countArg) throws LuaException {
checkOpen(); checkOpen();
@ -130,6 +133,7 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end. * @cc.treturn string|nil The remaining contents of the file, or {@code nil} if we are at the end.
* @cc.since 1.80pr1 * @cc.since 1.80pr1
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] readAll() throws LuaException { public final Object[] readAll() throws LuaException {
checkOpen(); checkOpen();
@ -164,6 +168,7 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.since 1.80pr1.9 * @cc.since 1.80pr1.9
* @cc.changed 1.81.0 `\r` is now stripped. * @cc.changed 1.81.0 `\r` is now stripped.
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException { public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
checkOpen(); checkOpen();
@ -230,10 +235,11 @@ public class BinaryReadableHandle extends HandleGeneric {
* @cc.treturn string The reason seeking failed. * @cc.treturn string The reason seeking failed.
* @cc.since 1.80pr1.9 * @cc.since 1.80pr1.9
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException { public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
checkOpen(); checkOpen();
return handleSeek(seekable, whence, offset); return handleSeek(Nullability.assertNonNull(seekable), whence, offset);
} }
} }
} }

View File

@ -10,7 +10,9 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.api.lua.LuaValues;
import dan200.computercraft.core.filesystem.TrackingCloseable; import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.core.util.Nullability;
import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@ -26,10 +28,10 @@ import java.util.Optional;
*/ */
public class BinaryWritableHandle extends HandleGeneric { public class BinaryWritableHandle extends HandleGeneric {
private final WritableByteChannel writer; private final WritableByteChannel writer;
final SeekableByteChannel seekable; final @Nullable SeekableByteChannel seekable;
private final ByteBuffer single = ByteBuffer.allocate(1); private final ByteBuffer single = ByteBuffer.allocate(1);
protected BinaryWritableHandle(WritableByteChannel writer, SeekableByteChannel seekable, TrackingCloseable closeable) { protected BinaryWritableHandle(WritableByteChannel writer, @Nullable SeekableByteChannel seekable, TrackingCloseable closeable) {
super(closeable); super(closeable);
this.writer = writer; this.writer = writer;
this.seekable = seekable; this.seekable = seekable;
@ -86,7 +88,8 @@ public class BinaryWritableHandle extends HandleGeneric {
try { try {
// Technically this is not needed // Technically this is not needed
if (writer instanceof FileChannel channel) channel.force(false); if (writer instanceof FileChannel channel) channel.force(false);
} catch (IOException ignored) { } catch (IOException e) {
throw new LuaException(e.getMessage());
} }
} }
@ -114,10 +117,11 @@ public class BinaryWritableHandle extends HandleGeneric {
* @cc.treturn string The reason seeking failed. * @cc.treturn string The reason seeking failed.
* @cc.since 1.80pr1.9 * @cc.since 1.80pr1.9
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException { public final Object[] seek(Optional<String> whence, Optional<Long> offset) throws LuaException {
checkOpen(); checkOpen();
return handleSeek(seekable, whence, offset); return handleSeek(Nullability.assertNonNull(seekable), whence, offset);
} }
} }
} }

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable; import dan200.computercraft.core.filesystem.TrackingCloseable;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels; import java.nio.channels.Channels;
@ -30,12 +30,12 @@ public class EncodedReadableHandle extends HandleGeneric {
private final BufferedReader reader; private final BufferedReader reader;
public EncodedReadableHandle(@Nonnull BufferedReader reader, @Nonnull TrackingCloseable closable) { public EncodedReadableHandle(BufferedReader reader, TrackingCloseable closable) {
super(closable); super(closable);
this.reader = reader; this.reader = reader;
} }
public EncodedReadableHandle(@Nonnull BufferedReader reader) { public EncodedReadableHandle(BufferedReader reader) {
this(reader, new TrackingCloseable.Impl(reader)); this(reader, new TrackingCloseable.Impl(reader));
} }
@ -48,6 +48,7 @@ public class EncodedReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The read line or {@code nil} if at the end of the file. * @cc.treturn string|nil The read line or {@code nil} if at the end of the file.
* @cc.changed 1.81.0 Added option to return trailing newline. * @cc.changed 1.81.0 Added option to return trailing newline.
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException { public final Object[] readLine(Optional<Boolean> withTrailingArg) throws LuaException {
checkOpen(); checkOpen();
@ -73,6 +74,7 @@ public class EncodedReadableHandle extends HandleGeneric {
* @throws LuaException If the file has been closed. * @throws LuaException If the file has been closed.
* @cc.treturn nil|string The remaining contents of the file, or {@code nil} if we are at the end. * @cc.treturn nil|string The remaining contents of the file, or {@code nil} if we are at the end.
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] readAll() throws LuaException { public final Object[] readAll() throws LuaException {
checkOpen(); checkOpen();
@ -102,6 +104,7 @@ public class EncodedReadableHandle extends HandleGeneric {
* @cc.treturn string|nil The read characters, or {@code nil} if at the of the file. * @cc.treturn string|nil The read characters, or {@code nil} if at the of the file.
* @cc.since 1.80pr1.4 * @cc.since 1.80pr1.4
*/ */
@Nullable
@LuaFunction @LuaFunction
public final Object[] read(Optional<Integer> countA) throws LuaException { public final Object[] read(Optional<Integer> countA) throws LuaException {
checkOpen(); checkOpen();

View File

@ -11,7 +11,6 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable; import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.core.util.StringUtil; import dan200.computercraft.core.util.StringUtil;
import javax.annotation.Nonnull;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels; import java.nio.channels.Channels;
@ -28,7 +27,7 @@ import java.nio.charset.StandardCharsets;
public class EncodedWritableHandle extends HandleGeneric { public class EncodedWritableHandle extends HandleGeneric {
private final BufferedWriter writer; private final BufferedWriter writer;
public EncodedWritableHandle(@Nonnull BufferedWriter writer, @Nonnull TrackingCloseable closable) { public EncodedWritableHandle(BufferedWriter writer, TrackingCloseable closable) {
super(closable); super(closable);
this.writer = writer; this.writer = writer;
} }
@ -80,7 +79,8 @@ public class EncodedWritableHandle extends HandleGeneric {
checkOpen(); checkOpen();
try { try {
writer.flush(); writer.flush();
} catch (IOException ignored) { } catch (IOException e) {
throw new LuaException(e.getMessage());
} }
} }

View File

@ -10,16 +10,16 @@ import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.core.filesystem.TrackingCloseable; import dan200.computercraft.core.filesystem.TrackingCloseable;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channel; import java.nio.channels.Channel;
import java.nio.channels.SeekableByteChannel; import java.nio.channels.SeekableByteChannel;
import java.util.Optional; import java.util.Optional;
public abstract class HandleGeneric { public abstract class HandleGeneric {
private TrackingCloseable closeable; private @Nullable TrackingCloseable closeable;
protected HandleGeneric(@Nonnull TrackingCloseable closeable) { protected HandleGeneric(TrackingCloseable closeable) {
this.closeable = closeable; this.closeable = closeable;
} }
@ -57,6 +57,7 @@ public abstract class HandleGeneric {
* @throws LuaException If the arguments were invalid * @throws LuaException If the arguments were invalid
* @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a> * @see <a href="https://www.lua.org/manual/5.1/manual.html#pdf-file:seek">{@code file:seek} in the Lua manual.</a>
*/ */
@Nullable
protected static Object[] handleSeek(SeekableByteChannel channel, Optional<String> whence, Optional<Long> offset) throws LuaException { protected static Object[] handleSeek(SeekableByteChannel channel, Optional<String> whence, Optional<Long> offset) throws LuaException {
long actualOffset = offset.orElse(0L); long actualOffset = offset.orElse(0L);
try { try {
@ -75,6 +76,7 @@ public abstract class HandleGeneric {
} }
} }
@Nullable
protected static SeekableByteChannel asSeekable(Channel channel) { protected static SeekableByteChannel asSeekable(Channel channel) {
if (!(channel instanceof SeekableByteChannel seekable)) return null; if (!(channel instanceof SeekableByteChannel seekable)) return null;

View File

@ -7,6 +7,7 @@ package dan200.computercraft.core.apis.http;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import javax.annotation.Nullable;
import java.net.URI; import java.net.URI;
import java.util.concurrent.Future; import java.util.concurrent.Future;
@ -18,7 +19,7 @@ import java.util.concurrent.Future;
public class CheckUrl extends Resource<CheckUrl> { public class CheckUrl extends Resource<CheckUrl> {
private static final String EVENT = "http_check"; private static final String EVENT = "http_check";
private Future<?> future; private @Nullable Future<?> future;
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
private final String address; private final String address;
@ -47,7 +48,7 @@ public class CheckUrl extends Resource<CheckUrl> {
if (tryClose()) environment.queueEvent(EVENT, address, true); if (tryClose()) environment.queueEvent(EVENT, address, true);
} catch (HTTPRequestException e) { } catch (HTTPRequestException e) {
if (tryClose()) environment.queueEvent(EVENT, address, false, e.getMessage()); if (tryClose()) environment.queueEvent(EVENT, address, false, NetworkUtils.toFriendlyError(e));
} }
} }

View File

@ -25,7 +25,7 @@ import io.netty.handler.traffic.GlobalTrafficShapingHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.TrustManagerFactory;
@ -65,10 +65,11 @@ public final class NetworkUtils {
} }
private static final Object sslLock = new Object(); private static final Object sslLock = new Object();
private static TrustManagerFactory trustManager; private static @Nullable TrustManagerFactory trustManager;
private static SslContext sslContext; private static @Nullable SslContext sslContext;
private static boolean triedSslContext = false; private static boolean triedSslContext = false;
@Nullable
private static TrustManagerFactory getTrustManager() { private static TrustManagerFactory getTrustManager() {
if (trustManager != null) return trustManager; if (trustManager != null) return trustManager;
synchronized (sslLock) { synchronized (sslLock) {
@ -86,6 +87,7 @@ public final class NetworkUtils {
} }
} }
@Nullable
public static SslContext getSslContext() throws HTTPRequestException { public static SslContext getSslContext() throws HTTPRequestException {
if (sslContext != null || triedSslContext) return sslContext; if (sslContext != null || triedSslContext) return sslContext;
synchronized (sslLock) { synchronized (sslLock) {
@ -171,10 +173,10 @@ public final class NetworkUtils {
return bytes; return bytes;
} }
@Nonnull public static String toFriendlyError(Throwable cause) {
public static String toFriendlyError(@Nonnull Throwable cause) {
if (cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException) { if (cause instanceof WebSocketHandshakeException || cause instanceof HTTPRequestException) {
return cause.getMessage(); var message = cause.getMessage();
return message == null ? "Could not connect" : message;
} else if (cause instanceof TooLongFrameException) { } else if (cause instanceof TooLongFrameException) {
return "Message is too large"; return "Message is too large";
} else if (cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException) { } else if (cause instanceof ReadTimeoutException || cause instanceof ConnectTimeoutException) {

View File

@ -8,6 +8,7 @@ package dan200.computercraft.core.apis.http;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFuture;
import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.lang.ref.Reference; import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
@ -94,12 +95,14 @@ public abstract class Resource<T extends Resource<T>> implements Closeable {
return limiter.queue(thisT, () -> task.accept(thisT)); return limiter.queue(thisT, () -> task.accept(thisT));
} }
protected static <T extends Closeable> T closeCloseable(T closeable) { @Nullable
protected static <T extends Closeable> T closeCloseable(@Nullable T closeable) {
IoUtil.closeQuietly(closeable); IoUtil.closeQuietly(closeable);
return null; return null;
} }
protected static ChannelFuture closeChannel(ChannelFuture future) { @Nullable
protected static ChannelFuture closeChannel(@Nullable ChannelFuture future) {
if (future != null) { if (future != null) {
future.cancel(false); future.cancel(false);
var channel = future.channel(); var channel = future.channel();
@ -109,7 +112,8 @@ public abstract class Resource<T extends Resource<T>> implements Closeable {
return null; return null;
} }
protected static <T extends Future<?>> T closeFuture(T future) { @Nullable
protected static <T extends Future<?>> T closeFuture(@Nullable T future) {
if (future != null) future.cancel(true); if (future != null) future.cancel(true);
return null; return null;
} }

View File

@ -18,9 +18,6 @@ import java.util.function.Supplier;
*/ */
public class ResourceGroup<T extends Resource<T>> { public class ResourceGroup<T extends Resource<T>> {
public static final int DEFAULT_LIMIT = 512; public static final int DEFAULT_LIMIT = 512;
public static final IntSupplier DEFAULT = () -> DEFAULT_LIMIT;
private static final IntSupplier ZERO = () -> 0;
final IntSupplier limit; final IntSupplier limit;
@ -32,10 +29,6 @@ public class ResourceGroup<T extends Resource<T>> {
this.limit = limit; this.limit = limit;
} }
public ResourceGroup() {
limit = ZERO;
}
public void startup() { public void startup() {
active = true; active = true;
} }

View File

@ -21,9 +21,6 @@ public class ResourceQueue<T extends Resource<T>> extends ResourceGroup<T> {
super(limit); super(limit);
} }
public ResourceQueue() {
}
@Override @Override
public synchronized void shutdown() { public synchronized void shutdown() {
super.shutdown(); super.shutdown();

View File

@ -5,7 +5,6 @@
*/ */
package dan200.computercraft.core.apis.http.options; package dan200.computercraft.core.apis.http.options;
import javax.annotation.Nonnull;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.OptionalLong; import java.util.OptionalLong;
@ -17,7 +16,6 @@ public enum Action {
this, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty() this, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty()
); );
@Nonnull
public PartialOptions toPartial() { public PartialOptions toPartial() {
return partial; return partial;
} }

View File

@ -9,6 +9,7 @@ import com.google.common.net.InetAddresses;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -51,6 +52,7 @@ interface AddressPredicate {
return true; return true;
} }
@Nullable
public static HostRange parse(String addressStr, String prefixSizeStr) { public static HostRange parse(String addressStr, String prefixSizeStr) {
int prefixSize; int prefixSize;
try { try {
@ -79,11 +81,11 @@ interface AddressPredicate {
var size = prefixSize; var size = prefixSize;
for (var i = 0; i < minBytes.length; i++) { for (var i = 0; i < minBytes.length; i++) {
if (size <= 0) { if (size <= 0) {
minBytes[i] &= 0; minBytes[i] = (byte) 0;
maxBytes[i] |= 0xFF; maxBytes[i] = (byte) 0xFF;
} else if (size < 8) { } else if (size < 8) {
minBytes[i] &= 0xFF << (8 - size); minBytes[i] = (byte) (minBytes[i] & 0xFF << (8 - size));
maxBytes[i] |= ~(0xFF << (8 - size)); maxBytes[i] = (byte) (maxBytes[i] | ~(0xFF << (8 - size)));
} }
size -= 8; size -= 8;

View File

@ -10,7 +10,6 @@ import dan200.computercraft.core.apis.http.options.AddressPredicate.DomainPatter
import dan200.computercraft.core.apis.http.options.AddressPredicate.HostRange; import dan200.computercraft.core.apis.http.options.AddressPredicate.HostRange;
import dan200.computercraft.core.apis.http.options.AddressPredicate.PrivatePattern; import dan200.computercraft.core.apis.http.options.AddressPredicate.PrivatePattern;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.net.Inet4Address; import java.net.Inet4Address;
import java.net.Inet6Address; import java.net.Inet6Address;
@ -32,14 +31,14 @@ public final class AddressRule {
private final OptionalInt port; private final OptionalInt port;
private final PartialOptions partial; private final PartialOptions partial;
private AddressRule(@Nonnull AddressPredicate predicate, OptionalInt port, @Nonnull PartialOptions partial) { private AddressRule(AddressPredicate predicate, OptionalInt port, PartialOptions partial) {
this.predicate = predicate; this.predicate = predicate;
this.partial = partial; this.partial = partial;
this.port = port; this.port = port;
} }
@Nullable @Nullable
public static AddressRule parse(String filter, OptionalInt port, @Nonnull PartialOptions partial) { public static AddressRule parse(String filter, OptionalInt port, PartialOptions partial) {
var cidr = filter.indexOf('/'); var cidr = filter.indexOf('/');
if (cidr >= 0) { if (cidr >= 0) {
var addressStr = filter.substring(0, cidr); var addressStr = filter.substring(0, cidr);
@ -63,7 +62,7 @@ public final class AddressRule {
* @param ipv4Address An ipv4 version of the address, if the original was an ipv6 address. * @param ipv4Address An ipv4 version of the address, if the original was an ipv6 address.
* @return Whether it matches any of these patterns. * @return Whether it matches any of these patterns.
*/ */
private boolean matches(String domain, int port, InetAddress address, Inet4Address ipv4Address) { private boolean matches(String domain, int port, InetAddress address, @Nullable Inet4Address ipv4Address) {
if (this.port.isPresent() && this.port.getAsInt() != port) return false; if (this.port.isPresent() && this.port.getAsInt() != port) return false;
return predicate.matches(domain) return predicate.matches(domain)
|| predicate.matches(address) || predicate.matches(address)

View File

@ -5,20 +5,18 @@
*/ */
package dan200.computercraft.core.apis.http.options; package dan200.computercraft.core.apis.http.options;
import javax.annotation.Nonnull;
/** /**
* Options about a specific domain. * Options about a specific domain.
*/ */
public final class Options { public final class Options {
@Nonnull
public final Action action; public final Action action;
public final long maxUpload; public final long maxUpload;
public final long maxDownload; public final long maxDownload;
public final int timeout; public final int timeout;
public final int websocketMessage; public final int websocketMessage;
Options(@Nonnull Action action, long maxUpload, long maxDownload, int timeout, int websocketMessage) { Options(Action action, long maxUpload, long maxDownload, int timeout, int websocketMessage) {
this.action = action; this.action = action;
this.maxUpload = maxUpload; this.maxUpload = maxUpload;
this.maxDownload = maxDownload; this.maxDownload = maxDownload;

View File

@ -5,10 +5,13 @@
*/ */
package dan200.computercraft.core.apis.http.options; package dan200.computercraft.core.apis.http.options;
import com.google.errorprone.annotations.Immutable;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.OptionalInt; import java.util.OptionalInt;
import java.util.OptionalLong; import java.util.OptionalLong;
@Immutable
public final class PartialOptions { public final class PartialOptions {
public static final PartialOptions DEFAULT = new PartialOptions( public static final PartialOptions DEFAULT = new PartialOptions(
null, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty() null, OptionalLong.empty(), OptionalLong.empty(), OptionalInt.empty(), OptionalInt.empty()
@ -20,6 +23,7 @@ public final class PartialOptions {
private final OptionalInt timeout; private final OptionalInt timeout;
private final OptionalInt websocketMessage; private final OptionalInt websocketMessage;
@SuppressWarnings("Immutable") // Lazily initialised, so this mutation is invisible in the public API
private @Nullable Options options; private @Nullable Options options;
public PartialOptions(@Nullable Action action, OptionalLong maxUpload, OptionalLong maxDownload, OptionalInt timeout, OptionalInt websocketMessage) { public PartialOptions(@Nullable Action action, OptionalLong maxUpload, OptionalLong maxDownload, OptionalInt timeout, OptionalInt websocketMessage) {

View File

@ -24,6 +24,7 @@ import io.netty.handler.timeout.ReadTimeoutHandler;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
@ -42,9 +43,9 @@ public class HttpRequest extends Resource<HttpRequest> {
private static final int MAX_REDIRECTS = 16; private static final int MAX_REDIRECTS = 16;
private Future<?> executorFuture; private @Nullable Future<?> executorFuture;
private ChannelFuture connectFuture; private @Nullable ChannelFuture connectFuture;
private HttpRequestHandler currentRequest; private @Nullable HttpRequestHandler currentRequest;
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
@ -55,7 +56,10 @@ public class HttpRequest extends Resource<HttpRequest> {
final AtomicInteger redirects; final AtomicInteger redirects;
public HttpRequest(ResourceGroup<HttpRequest> limiter, IAPIEnvironment environment, String address, String postText, HttpHeaders headers, boolean binary, boolean followRedirects) { public HttpRequest(
ResourceGroup<HttpRequest> limiter, IAPIEnvironment environment, String address, @Nullable String postText,
HttpHeaders headers, boolean binary, boolean followRedirects
) {
super(limiter); super(limiter);
this.environment = environment; this.environment = environment;
this.address = address; this.address = address;
@ -171,7 +175,7 @@ public class HttpRequest extends Resource<HttpRequest> {
// Do an additional check for cancellation // Do an additional check for cancellation
checkClosed(); checkClosed();
} catch (HTTPRequestException e) { } catch (HTTPRequestException e) {
failure(e.getMessage()); failure(NetworkUtils.toFriendlyError(e));
} catch (Exception e) { } catch (Exception e) {
failure(NetworkUtils.toFriendlyError(e)); failure(NetworkUtils.toFriendlyError(e));
LOG.error(Logging.HTTP_ERROR, "Error in HTTP request", e); LOG.error(Logging.HTTP_ERROR, "Error in HTTP request", e);

View File

@ -20,6 +20,7 @@ import io.netty.handler.codec.http.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -27,6 +28,7 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize; import static dan200.computercraft.core.apis.http.request.HttpRequest.getHeaderSize;
@ -47,10 +49,10 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
private final HttpMethod method; private final HttpMethod method;
private final Options options; private final Options options;
private Charset responseCharset; private @Nullable Charset responseCharset;
private final HttpHeaders responseHeaders = new DefaultHttpHeaders(); private final HttpHeaders responseHeaders = new DefaultHttpHeaders();
private HttpResponseStatus responseStatus; private @Nullable HttpResponseStatus responseStatus;
private CompositeByteBuf responseBody; private @Nullable CompositeByteBuf responseBody;
HttpRequestHandler(HttpRequest request, URI uri, HttpMethod method, Options options) { HttpRequestHandler(HttpRequest request, URI uri, HttpMethod method, Options options) {
this.request = request; this.request = request;
@ -112,7 +114,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
HttpRequest.checkUri(redirect); HttpRequest.checkUri(redirect);
} catch (HTTPRequestException e) { } catch (HTTPRequestException e) {
// If we cannot visit this uri, then fail. // If we cannot visit this uri, then fail.
request.failure(e.getMessage()); request.failure(NetworkUtils.toFriendlyError(e));
return; return;
} }
@ -167,6 +169,9 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
} }
private void sendResponse() { private void sendResponse() {
Objects.requireNonNull(responseStatus, "Status has not been set");
Objects.requireNonNull(responseCharset, "Charset has not been set");
// Read the ByteBuf into a channel. // Read the ByteBuf into a channel.
var body = responseBody; var body = responseBody;
var bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes(body); var bytes = body == null ? EMPTY_BYTES : NetworkUtils.toBytes(body);
@ -203,6 +208,7 @@ public final class HttpRequestHandler extends SimpleChannelInboundHandler<HttpOb
* @param headers The headers of the HTTP response. * @param headers The headers of the HTTP response.
* @return The URI to redirect to, or {@code null} if no redirect should occur. * @return The URI to redirect to, or {@code null} if no redirect should occur.
*/ */
@Nullable
private URI getRedirect(HttpResponseStatus status, HttpHeaders headers) { private URI getRedirect(HttpResponseStatus status, HttpHeaders headers) {
var code = status.code(); var code = status.code();
if (code < 300 || code > 307 || code == 304 || code == 306) return null; if (code < 300 || code > 307 || code == 304 || code == 306) return null;

View File

@ -13,7 +13,6 @@ import dan200.computercraft.core.apis.handles.EncodedReadableHandle;
import dan200.computercraft.core.apis.handles.HandleGeneric; import dan200.computercraft.core.apis.handles.HandleGeneric;
import dan200.computercraft.core.asm.ObjectSource; import dan200.computercraft.core.asm.ObjectSource;
import javax.annotation.Nonnull;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@ -31,7 +30,7 @@ public class HttpResponseHandle implements ObjectSource {
private final String responseStatus; private final String responseStatus;
private final Map<String, String> responseHeaders; private final Map<String, String> responseHeaders;
public HttpResponseHandle(@Nonnull HandleGeneric reader, int responseCode, String responseStatus, @Nonnull Map<String, String> responseHeaders) { public HttpResponseHandle(HandleGeneric reader, int responseCode, String responseStatus, Map<String, String> responseHeaders) {
this.reader = reader; this.reader = reader;
this.responseCode = responseCode; this.responseCode = responseCode;
this.responseStatus = responseStatus; this.responseStatus = responseStatus;

View File

@ -29,6 +29,7 @@ import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
@ -51,9 +52,9 @@ public class Websocket extends Resource<Websocket> {
static final String CLOSE_EVENT = "websocket_closed"; static final String CLOSE_EVENT = "websocket_closed";
static final String MESSAGE_EVENT = "websocket_message"; static final String MESSAGE_EVENT = "websocket_message";
private Future<?> executorFuture; private @Nullable Future<?> executorFuture;
private ChannelFuture connectFuture; private @Nullable ChannelFuture connectFuture;
private WeakReference<WebsocketHandle> websocketHandle; private @Nullable WeakReference<WebsocketHandle> websocketHandle;
private final IAPIEnvironment environment; private final IAPIEnvironment environment;
private final URI uri; private final URI uri;
@ -73,12 +74,14 @@ public class Websocket extends Resource<Websocket> {
try { try {
uri = new URI(address); uri = new URI(address);
} catch (URISyntaxException ignored) { } catch (URISyntaxException ignored) {
// Fall through to the case below
} }
if (uri == null || uri.getHost() == null) { if (uri == null || uri.getHost() == null) {
try { try {
uri = new URI("ws://" + address); uri = new URI("ws://" + address);
} catch (URISyntaxException ignored) { } catch (URISyntaxException ignored) {
// Fall through to the case below
} }
} }
@ -152,7 +155,7 @@ public class Websocket extends Resource<Websocket> {
// Do an additional check for cancellation // Do an additional check for cancellation
checkClosed(); checkClosed();
} catch (HTTPRequestException e) { } catch (HTTPRequestException e) {
failure(e.getMessage()); failure(NetworkUtils.toFriendlyError(e));
} catch (Exception e) { } catch (Exception e) {
failure(NetworkUtils.toFriendlyError(e)); failure(NetworkUtils.toFriendlyError(e));
LOG.error(Logging.HTTP_ERROR, "Error in websocket", e); LOG.error(Logging.HTTP_ERROR, "Error in websocket", e);

View File

@ -15,7 +15,7 @@ import io.netty.channel.Channel;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame; import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.io.Closeable; import java.io.Closeable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Optional; import java.util.Optional;
@ -36,7 +36,7 @@ public class WebsocketHandle implements Closeable {
private final Options options; private final Options options;
private boolean closed = false; private boolean closed = false;
private Channel channel; private @Nullable Channel channel;
public WebsocketHandle(Websocket websocket, Options options, Channel channel) { public WebsocketHandle(Websocket websocket, Options options, Channel channel) {
this.websocket = websocket; this.websocket = websocket;
@ -127,7 +127,6 @@ public class WebsocketHandle implements Closeable {
this.timeoutId = timeoutId; this.timeoutId = timeoutId;
} }
@Nonnull
@Override @Override
public MethodResult resume(Object[] event) { public MethodResult resume(Object[] event) {
if (event.length >= 3 && Objects.equal(event[0], MESSAGE_EVENT) && Objects.equal(event[1], websocket.address())) { if (event.length >= 3 && Objects.equal(event[0], MESSAGE_EVENT) && Objects.equal(event[1], websocket.address())) {

View File

@ -21,7 +21,6 @@ import org.objectweb.asm.Type;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
@ -77,8 +76,7 @@ public final class Generator<T> {
this.methodDesc = methodDesc.toString(); this.methodDesc = methodDesc.toString();
} }
@Nonnull public List<NamedMethod<T>> getMethods(Class<?> klass) {
public List<NamedMethod<T>> getMethods(@Nonnull Class<?> klass) {
try { try {
return classCache.get(klass); return classCache.get(klass);
} catch (ExecutionException e) { } catch (ExecutionException e) {
@ -87,7 +85,6 @@ public final class Generator<T> {
} }
} }
@Nonnull
private List<NamedMethod<T>> build(Class<?> klass) { private List<NamedMethod<T>> build(Class<?> klass) {
ArrayList<NamedMethod<T>> methods = null; ArrayList<NamedMethod<T>> methods = null;
for (var method : klass.getMethods()) { for (var method : klass.getMethods()) {
@ -121,7 +118,7 @@ public final class Generator<T> {
return Collections.unmodifiableList(methods); return Collections.unmodifiableList(methods);
} }
private void addMethod(List<NamedMethod<T>> methods, Method method, LuaFunction annotation, PeripheralType genericType, T instance) { private void addMethod(List<NamedMethod<T>> methods, Method method, LuaFunction annotation, @Nullable PeripheralType genericType, T instance) {
var names = annotation.value(); var names = annotation.value();
var isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread(); var isSimple = method.getReturnType() != MethodResult.class && !annotation.mainThread();
if (names.length == 0) { if (names.length == 0) {
@ -133,7 +130,6 @@ public final class Generator<T> {
} }
} }
@Nonnull
private Optional<T> build(Method method) { private Optional<T> build(Method method) {
var name = method.getDeclaringClass().getName() + "." + method.getName(); var name = method.getDeclaringClass().getName() + "." + method.getName();
var modifiers = method.getModifiers(); var modifiers = method.getModifiers();
@ -259,6 +255,7 @@ public final class Generator<T> {
return cw.toByteArray(); return cw.toByteArray();
} }
@Nullable
private Boolean loadArg(MethodVisitor mw, Class<?> target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex) { private Boolean loadArg(MethodVisitor mw, Class<?> target, Method method, boolean unsafe, java.lang.reflect.Type genericArg, int argIndex) {
if (genericArg == target) { if (genericArg == target) {
mw.visitVarInsn(ALOAD, 1); mw.visitVarInsn(ALOAD, 1);

View File

@ -12,7 +12,7 @@ import dan200.computercraft.api.peripheral.PeripheralType;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
@ -30,12 +30,12 @@ public class GenericMethod {
final Method method; final Method method;
final LuaFunction annotation; final LuaFunction annotation;
final Class<?> target; final Class<?> target;
final PeripheralType peripheralType; final @Nullable PeripheralType peripheralType;
private static final List<GenericSource> sources = new ArrayList<>(); private static final List<GenericSource> sources = new ArrayList<>();
private static List<GenericMethod> cache; private static @Nullable List<GenericMethod> cache;
GenericMethod(Method method, LuaFunction annotation, Class<?> target, PeripheralType peripheralType) { GenericMethod(Method method, LuaFunction annotation, Class<?> target, @Nullable PeripheralType peripheralType) {
this.method = method; this.method = method;
this.annotation = annotation; this.annotation = annotation;
this.target = target; this.target = target;
@ -52,7 +52,7 @@ public class GenericMethod {
return cache = sources.stream().flatMap(GenericMethod::getMethods).toList(); return cache = sources.stream().flatMap(GenericMethod::getMethods).toList();
} }
public static synchronized void register(@Nonnull GenericSource source) { public static synchronized void register(GenericSource source) {
Objects.requireNonNull(source, "Source cannot be null"); Objects.requireNonNull(source, "Source cannot be null");
if (cache != null) { if (cache != null) {

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import javax.annotation.Nonnull;
import java.util.Collections; import java.util.Collections;
public interface LuaMethod { public interface LuaMethod {
@ -21,6 +20,5 @@ public interface LuaMethod {
String[] EMPTY_METHODS = new String[0]; String[] EMPTY_METHODS = new String[0];
@Nonnull MethodResult apply(Object target, ILuaContext context, IArguments args) throws LuaException;
MethodResult apply(@Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IArguments args) throws LuaException;
} }

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.api.peripheral.PeripheralType; import dan200.computercraft.api.peripheral.PeripheralType;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public final class NamedMethod<T> { public final class NamedMethod<T> {
@ -15,21 +14,19 @@ public final class NamedMethod<T> {
private final T method; private final T method;
private final boolean nonYielding; private final boolean nonYielding;
private final PeripheralType genericType; private final @Nullable PeripheralType genericType;
NamedMethod(String name, T method, boolean nonYielding, PeripheralType genericType) { NamedMethod(String name, T method, boolean nonYielding, @Nullable PeripheralType genericType) {
this.name = name; this.name = name;
this.method = method; this.method = method;
this.nonYielding = nonYielding; this.nonYielding = nonYielding;
this.genericType = genericType; this.genericType = genericType;
} }
@Nonnull
public String getName() { public String getName() {
return name; return name;
} }
@Nonnull
public T getMethod() { public T getMethod() {
return method; return method;
} }

View File

@ -12,7 +12,6 @@ import dan200.computercraft.api.lua.MethodResult;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IDynamicPeripheral; import dan200.computercraft.api.peripheral.IDynamicPeripheral;
import javax.annotation.Nonnull;
import java.util.Arrays; import java.util.Arrays;
public interface PeripheralMethod { public interface PeripheralMethod {
@ -24,6 +23,5 @@ public interface PeripheralMethod {
method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args) method -> (instance, context, computer, args) -> ((IDynamicPeripheral) instance).callMethod(computer, context, method, args)
); );
@Nonnull MethodResult apply(Object target, ILuaContext context, IComputerAccess computer, IArguments args) throws LuaException;
MethodResult apply(@Nonnull Object target, @Nonnull ILuaContext context, @Nonnull IComputerAccess computer, @Nonnull IArguments args) throws LuaException;
} }

View File

@ -7,10 +7,13 @@ package dan200.computercraft.core.asm;
import dan200.computercraft.api.lua.MethodResult; import dan200.computercraft.api.lua.MethodResult;
import javax.annotation.Nullable;
final class ResultHelpers { final class ResultHelpers {
private ResultHelpers() { private ResultHelpers() {
} }
@Nullable
static Object[] checkNormalResult(MethodResult result) { static Object[] checkNormalResult(MethodResult result) {
if (result.getCallback() != null) { if (result.getCallback() != null) {
// Due to how tasks are implemented, we can't currently return a MethodResult. This is an // Due to how tasks are implemented, we can't currently return a MethodResult. This is an

View File

@ -16,6 +16,7 @@ import dan200.computercraft.core.computer.mainthread.MainThreadScheduler;
import dan200.computercraft.core.filesystem.FileSystem; import dan200.computercraft.core.filesystem.FileSystem;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import javax.annotation.Nullable;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicLong;
@ -37,7 +38,7 @@ public class Computer {
// Various properties of the computer // Various properties of the computer
private final int id; private final int id;
private String label = null; private @Nullable String label = null;
// Read-only fields about the computer // Read-only fields about the computer
private final GlobalEnvironment globalEnvironment; private final GlobalEnvironment globalEnvironment;
@ -112,7 +113,7 @@ public class Computer {
executor.queueStop(false, true); executor.queueStop(false, true);
} }
public void queueEvent(String event, Object[] args) { public void queueEvent(String event, @Nullable Object[] args) {
executor.queueEvent(event, args); executor.queueEvent(event, args);
} }
@ -134,11 +135,12 @@ public class Computer {
return id; return id;
} }
@Nullable
public String getLabel() { public String getLabel() {
return label; return label;
} }
public void setLabel(String label) { public void setLabel(@Nullable String label) {
if (!Objects.equal(label, this.label)) { if (!Objects.equal(label, this.label)) {
this.label = label; this.label = label;
externalOutputChanged.set(true); externalOutputChanged.set(true);

View File

@ -19,10 +19,10 @@ import dan200.computercraft.core.metrics.Metrics;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import dan200.computercraft.core.util.Nullability;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.InputStream; import java.io.InputStream;
import java.util.ArrayDeque; import java.util.ArrayDeque;
@ -63,9 +63,9 @@ final class ComputerExecutor {
private final ComputerThread scheduler; private final ComputerThread scheduler;
final TimeoutState timeout; final TimeoutState timeout;
private FileSystem fileSystem; private @Nullable FileSystem fileSystem;
private ILuaMachine machine; private @Nullable ILuaMachine machine;
/** /**
* Whether the computer is currently on. This is set to false when a shutdown starts, or when turning on completes * Whether the computer is currently on. This is set to false when a shutdown starts, or when turning on completes
@ -126,7 +126,7 @@ final class ComputerExecutor {
* Note, if command is not {@code null}, then some command is scheduled to be executed. Otherwise it is not * Note, if command is not {@code null}, then some command is scheduled to be executed. Otherwise it is not
* currently in the queue (or is currently being executed). * currently in the queue (or is currently being executed).
*/ */
private volatile StateCommand command; private volatile @Nullable StateCommand command;
/** /**
* The queue of events which should be executed when this computer is on. * The queue of events which should be executed when this computer is on.
@ -150,7 +150,7 @@ final class ComputerExecutor {
*/ */
private boolean closed; private boolean closed;
private IWritableMount rootMount; private @Nullable IWritableMount rootMount;
/** /**
* The thread the executor is running on. This is non-null when performing work. We use this to ensure we're only * The thread the executor is running on. This is non-null when performing work. We use this to ensure we're only
@ -193,6 +193,8 @@ final class ComputerExecutor {
} }
FileSystem getFileSystem() { FileSystem getFileSystem() {
var fileSystem = this.fileSystem;
if (fileSystem == null) throw new IllegalStateException("FileSystem has not been created yet");
return fileSystem; return fileSystem;
} }
@ -276,7 +278,7 @@ final class ComputerExecutor {
* @param event The event's name * @param event The event's name
* @param args The event's arguments * @param args The event's arguments
*/ */
void queueEvent(@Nonnull String event, @Nullable Object[] args) { void queueEvent(String event, @Nullable Object[] args) {
// Events should be skipped if we're not on. // Events should be skipped if we're not on.
if (!isOn) return; if (!isOn) return;
@ -320,19 +322,28 @@ final class ComputerExecutor {
} }
} }
@Nullable
private IMount getRomMount() { private IMount getRomMount() {
return computer.getGlobalEnvironment().createResourceMount("computercraft", "lua/rom"); return computer.getGlobalEnvironment().createResourceMount("computercraft", "lua/rom");
} }
@Nullable
private IWritableMount getRootMount() { private IWritableMount getRootMount() {
if (rootMount == null) rootMount = computerEnvironment.createRootMount(); if (rootMount == null) rootMount = computerEnvironment.createRootMount();
return rootMount; return rootMount;
} }
@Nullable
private FileSystem createFileSystem() { private FileSystem createFileSystem() {
FileSystem filesystem = null; FileSystem filesystem = null;
try { try {
filesystem = new FileSystem("hdd", getRootMount()); var mount = getRootMount();
if (mount == null) {
displayFailure("Cannot mount computer mount", null);
return null;
}
filesystem = new FileSystem("hdd", mount);
var romMount = getRomMount(); var romMount = getRomMount();
if (romMount == null) { if (romMount == null) {
@ -351,12 +362,14 @@ final class ComputerExecutor {
} }
} }
@Nullable
private ILuaMachine createLuaMachine() { private ILuaMachine createLuaMachine() {
// Load the bios resource // Load the bios resource
InputStream biosStream = null; InputStream biosStream = null;
try { try {
biosStream = computer.getGlobalEnvironment().createResourceFile("computercraft", "lua/bios.lua"); biosStream = computer.getGlobalEnvironment().createResourceFile("computercraft", "lua/bios.lua");
} catch (Exception ignored) { } catch (Exception e) {
LOG.error("Failed to load BIOS", e);
} }
if (biosStream == null) { if (biosStream == null) {
@ -562,7 +575,7 @@ final class ComputerExecutor {
if (machine != null) machine.printExecutionState(out); if (machine != null) machine.printExecutionState(out);
} }
private void displayFailure(String message, String extra) { private void displayFailure(String message, @Nullable String extra) {
var terminal = computer.getTerminal(); var terminal = computer.getTerminal();
terminal.reset(); terminal.reset();
@ -583,8 +596,8 @@ final class ComputerExecutor {
terminal.write("ComputerCraft may be installed incorrectly"); terminal.write("ComputerCraft may be installed incorrectly");
} }
private void resumeMachine(String event, Object[] args) throws InterruptedException { private void resumeMachine(@Nullable String event, @Nullable Object[] args) throws InterruptedException {
var result = machine.handleEvent(event, args); var result = Nullability.assertNonNull(machine).handleEvent(event, args);
interruptedEvent = result.isPause(); interruptedEvent = result.isPause();
if (!result.isError()) return; if (!result.isError()) return;
@ -600,6 +613,6 @@ final class ComputerExecutor {
ERROR, ERROR,
} }
private record Event(String name, Object[] args) { private record Event(String name, @Nullable Object[] args) {
} }
} }

View File

@ -5,8 +5,8 @@
*/ */
package dan200.computercraft.core.computer; package dan200.computercraft.core.computer;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.List;
/** /**
* A side on a computer. This is relative to the direction the computer is facing. * A side on a computer. This is relative to the direction the computer is facing.
@ -19,7 +19,7 @@ public enum ComputerSide {
RIGHT("right"), RIGHT("right"),
LEFT("left"); LEFT("left");
public static final String[] NAMES = new String[]{ "bottom", "top", "back", "front", "right", "left" }; public static final List<String> NAMES = List.of("bottom", "top", "back", "front", "right", "left");
public static final int COUNT = 6; public static final int COUNT = 6;
@ -31,13 +31,12 @@ public enum ComputerSide {
this.name = name; this.name = name;
} }
@Nonnull
public static ComputerSide valueOf(int side) { public static ComputerSide valueOf(int side) {
return VALUES[side]; return VALUES[side];
} }
@Nullable @Nullable
public static ComputerSide valueOfInsensitive(@Nonnull String name) { public static ComputerSide valueOfInsensitive(String name) {
for (var side : VALUES) { for (var side : VALUES) {
if (side.name.equalsIgnoreCase(name)) return side; if (side.name.equalsIgnoreCase(name)) return side;
} }

View File

@ -13,7 +13,6 @@ import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.apis.ComputerAccess; import dan200.computercraft.core.apis.ComputerAccess;
import dan200.computercraft.core.apis.IAPIEnvironment; import dan200.computercraft.core.apis.IAPIEnvironment;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
import java.util.Map; import java.util.Map;
@ -33,7 +32,6 @@ public class ComputerSystem extends ComputerAccess implements IComputerSystem {
this.environment = environment; this.environment = environment;
} }
@Nonnull
@Override @Override
public String getAttachmentName() { public String getAttachmentName() {
return "computer"; return "computer";
@ -52,7 +50,6 @@ public class ComputerSystem extends ComputerAccess implements IComputerSystem {
return environment.getLabel(); return environment.getLabel();
} }
@Nonnull
@Override @Override
public Map<String, IPeripheral> getAvailablePeripherals() { public Map<String, IPeripheral> getAvailablePeripherals() {
// TODO: Should this return peripherals on the current computer? // TODO: Should this return peripherals on the current computer?
@ -61,7 +58,7 @@ public class ComputerSystem extends ComputerAccess implements IComputerSystem {
@Nullable @Nullable
@Override @Override
public IPeripheral getAvailablePeripheral(@Nonnull String name) { public IPeripheral getAvailablePeripheral(String name) {
return null; return null;
} }
} }

View File

@ -13,7 +13,6 @@ import dan200.computercraft.core.util.ThreadUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Objects; import java.util.Objects;
import java.util.TreeSet; import java.util.TreeSet;
@ -53,6 +52,7 @@ import java.util.concurrent.locks.ReentrantLock;
* @see TimeoutState For how hard timeouts are handled. * @see TimeoutState For how hard timeouts are handled.
* @see ComputerExecutor For how computers actually do execution. * @see ComputerExecutor For how computers actually do execution.
*/ */
@SuppressWarnings("GuardedBy") // FIXME: Hard to know what the correct thing to do is.
public final class ComputerThread { public final class ComputerThread {
private static final Logger LOG = LoggerFactory.getLogger(ComputerThread.class); private static final Logger LOG = LoggerFactory.getLogger(ComputerThread.class);
private static final ThreadFactory monitorFactory = ThreadUtils.factory("Computer-Monitor"); private static final ThreadFactory monitorFactory = ThreadUtils.factory("Computer-Monitor");
@ -537,7 +537,7 @@ public final class ComputerThread {
/** /**
* The thread this runner runs on. * The thread this runner runs on.
*/ */
final @Nonnull Thread owner; final Thread owner;
/** /**
* Whether this runner is currently executing. This may be set to false when this worker terminates, or when * Whether this runner is currently executing. This may be set to false when this worker terminates, or when

View File

@ -16,7 +16,7 @@ import dan200.computercraft.core.terminal.Terminal;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap; import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator; import java.util.Iterator;
@ -56,7 +56,7 @@ public final class Environment implements IAPIEnvironment {
private final int[] bundledInput = new int[ComputerSide.COUNT]; private final int[] bundledInput = new int[ComputerSide.COUNT];
private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT]; private final IPeripheral[] peripherals = new IPeripheral[ComputerSide.COUNT];
private IPeripheralChangeListener peripheralListener = null; private @Nullable IPeripheralChangeListener peripheralListener = null;
private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>(); private final Int2ObjectMap<Timer> timers = new Int2ObjectOpenHashMap<>();
private int nextTimerToken = 0; private int nextTimerToken = 0;
@ -72,25 +72,21 @@ public final class Environment implements IAPIEnvironment {
return computer.getID(); return computer.getID();
} }
@Nonnull
@Override @Override
public ComputerEnvironment getComputerEnvironment() { public ComputerEnvironment getComputerEnvironment() {
return environment; return environment;
} }
@Nonnull
@Override @Override
public GlobalEnvironment getGlobalEnvironment() { public GlobalEnvironment getGlobalEnvironment() {
return computer.getGlobalEnvironment(); return computer.getGlobalEnvironment();
} }
@Nonnull
@Override @Override
public IWorkMonitor getMainThreadMonitor() { public IWorkMonitor getMainThreadMonitor() {
return computer.getMainThreadMonitor(); return computer.getMainThreadMonitor();
} }
@Nonnull
@Override @Override
public Terminal getTerminal() { public Terminal getTerminal() {
return computer.getTerminal(); return computer.getTerminal();
@ -112,7 +108,7 @@ public final class Environment implements IAPIEnvironment {
} }
@Override @Override
public void queueEvent(String event, Object... args) { public void queueEvent(String event, @Nullable Object... args) {
computer.queueEvent(event, args); computer.queueEvent(event, args);
} }
@ -262,6 +258,7 @@ public final class Environment implements IAPIEnvironment {
} }
} }
@Nullable
@Override @Override
public IPeripheral getPeripheral(ComputerSide side) { public IPeripheral getPeripheral(ComputerSide side) {
synchronized (peripherals) { synchronized (peripherals) {
@ -269,7 +266,7 @@ public final class Environment implements IAPIEnvironment {
} }
} }
public void setPeripheral(ComputerSide side, IPeripheral peripheral) { public void setPeripheral(ComputerSide side, @Nullable IPeripheral peripheral) {
synchronized (peripherals) { synchronized (peripherals) {
var index = side.ordinal(); var index = side.ordinal();
var existing = peripherals[index]; var existing = peripherals[index];
@ -283,19 +280,20 @@ public final class Environment implements IAPIEnvironment {
} }
@Override @Override
public void setPeripheralChangeListener(IPeripheralChangeListener listener) { public void setPeripheralChangeListener(@Nullable IPeripheralChangeListener listener) {
synchronized (peripherals) { synchronized (peripherals) {
peripheralListener = listener; peripheralListener = listener;
} }
} }
@Nullable
@Override @Override
public String getLabel() { public String getLabel() {
return computer.getLabel(); return computer.getLabel();
} }
@Override @Override
public void setLabel(String label) { public void setLabel(@Nullable String label) {
computer.setLabel(label); computer.setLabel(label);
} }
@ -315,12 +313,12 @@ public final class Environment implements IAPIEnvironment {
} }
@Override @Override
public void observe(@Nonnull Metric.Event event, long change) { public void observe(Metric.Event event, long change) {
metrics.observe(event, change); metrics.observe(event, change);
} }
@Override @Override
public void observe(@Nonnull Metric.Counter counter) { public void observe(Metric.Counter counter) {
metrics.observe(counter); metrics.observe(counter);
} }

View File

@ -12,7 +12,6 @@ import dan200.computercraft.core.Logging;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
class LuaContext implements ILuaContext { class LuaContext implements ILuaContext {
private static final Logger LOG = LoggerFactory.getLogger(LuaContext.class); private static final Logger LOG = LoggerFactory.getLogger(LuaContext.class);
@ -23,7 +22,7 @@ class LuaContext implements ILuaContext {
} }
@Override @Override
public long issueMainThreadTask(@Nonnull final ILuaTask task) throws LuaException { public long issueMainThreadTask(final ILuaTask task) throws LuaException {
// Issue command // Issue command
final var taskID = computer.getUniqueTaskId(); final var taskID = computer.getUniqueTaskId();
final Runnable iTask = () -> { final Runnable iTask = () -> {

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.computer.mainthread;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import javax.annotation.Nonnull;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
/** /**
@ -38,7 +37,7 @@ public class NoWorkMainThreadScheduler implements MainThreadScheduler {
} }
@Override @Override
public void trackWork(long time, @Nonnull TimeUnit unit) { public void trackWork(long time, TimeUnit unit) {
} }
} }
} }

View File

@ -1,117 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class ComboMount implements IMount {
private final IMount[] parts;
public ComboMount(IMount[] parts) {
this.parts = parts;
}
// IMount implementation
@Override
public boolean exists(@Nonnull String path) throws IOException {
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.exists(path)) {
return true;
}
}
return false;
}
@Override
public boolean isDirectory(@Nonnull String path) throws IOException {
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.isDirectory(path)) {
return true;
}
}
return false;
}
@Override
public void list(@Nonnull String path, @Nonnull List<String> contents) throws IOException {
// Combine the lists from all the mounts
List<String> foundFiles = null;
var foundDirs = 0;
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.exists(path) && part.isDirectory(path)) {
if (foundFiles == null) {
foundFiles = new ArrayList<>();
}
part.list(path, foundFiles);
foundDirs++;
}
}
if (foundDirs == 1) {
// We found one directory, so we know it already doesn't contain duplicates
contents.addAll(foundFiles);
} else if (foundDirs > 1) {
// We found multiple directories, so filter for duplicates
Set<String> seen = new HashSet<>();
for (var file : foundFiles) {
if (seen.add(file)) {
contents.add(file);
}
}
} else {
throw new FileOperationException(path, "Not a directory");
}
}
@Override
public long getSize(@Nonnull String path) throws IOException {
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.exists(path)) {
return part.getSize(path);
}
}
throw new FileOperationException(path, "No such file");
}
@Nonnull
@Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException {
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.exists(path) && !part.isDirectory(path)) {
return part.openForRead(path);
}
}
throw new FileOperationException(path, "No such file");
}
@Nonnull
@Override
public BasicFileAttributes getAttributes(@Nonnull String path) throws IOException {
for (var i = parts.length - 1; i >= 0; --i) {
var part = parts[i];
if (part.exists(path) && !part.isDirectory(path)) {
return part.getAttributes(path);
}
}
throw new FileOperationException(path, "No such file");
}
}

View File

@ -1,41 +0,0 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException;
import java.nio.channels.ReadableByteChannel;
import java.util.List;
public class EmptyMount implements IMount {
@Override
public boolean exists(@Nonnull String path) {
return path.isEmpty();
}
@Override
public boolean isDirectory(@Nonnull String path) {
return path.isEmpty();
}
@Override
public void list(@Nonnull String path, @Nonnull List<String> contents) {
}
@Override
public long getSize(@Nonnull String path) {
return 0;
}
@Nonnull
@Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException {
throw new FileOperationException(path, "No such file");
}
}

View File

@ -11,7 +11,6 @@ import dan200.computercraft.api.filesystem.IWritableMount;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -25,7 +24,7 @@ import java.util.Set;
public class FileMount implements IWritableMount { public class FileMount implements IWritableMount {
private static final Logger LOG = LoggerFactory.getLogger(FileMount.class); private static final Logger LOG = LoggerFactory.getLogger(FileMount.class);
private static final int MINIMUM_FILE_SIZE = 500; private static final long MINIMUM_FILE_SIZE = 500;
private static final Set<OpenOption> READ_OPTIONS = Collections.singleton(StandardOpenOption.READ); private static final Set<OpenOption> READ_OPTIONS = Collections.singleton(StandardOpenOption.READ);
private static final Set<OpenOption> WRITE_OPTIONS = Sets.newHashSet(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); private static final Set<OpenOption> WRITE_OPTIONS = Sets.newHashSet(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING);
private static final Set<OpenOption> APPEND_OPTIONS = Sets.newHashSet(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND); private static final Set<OpenOption> APPEND_OPTIONS = Sets.newHashSet(StandardOpenOption.WRITE, StandardOpenOption.CREATE, StandardOpenOption.APPEND);
@ -41,7 +40,7 @@ public class FileMount implements IWritableMount {
} }
@Override @Override
public int write(@Nonnull ByteBuffer b) throws IOException { public int write(ByteBuffer b) throws IOException {
count(b.remaining()); count(b.remaining());
return inner.write(b); return inner.write(b);
} }
@ -129,7 +128,7 @@ public class FileMount implements IWritableMount {
// IMount implementation // IMount implementation
@Override @Override
public boolean exists(@Nonnull String path) { public boolean exists(String path) {
if (!created()) return path.isEmpty(); if (!created()) return path.isEmpty();
var file = getRealPath(path); var file = getRealPath(path);
@ -137,7 +136,7 @@ public class FileMount implements IWritableMount {
} }
@Override @Override
public boolean isDirectory(@Nonnull String path) { public boolean isDirectory(String path) {
if (!created()) return path.isEmpty(); if (!created()) return path.isEmpty();
var file = getRealPath(path); var file = getRealPath(path);
@ -145,7 +144,7 @@ public class FileMount implements IWritableMount {
} }
@Override @Override
public void list(@Nonnull String path, @Nonnull List<String> contents) throws IOException { public void list(String path, List<String> contents) throws IOException {
if (!created()) { if (!created()) {
if (!path.isEmpty()) throw new FileOperationException(path, "Not a directory"); if (!path.isEmpty()) throw new FileOperationException(path, "Not a directory");
return; return;
@ -161,7 +160,7 @@ public class FileMount implements IWritableMount {
} }
@Override @Override
public long getSize(@Nonnull String path) throws IOException { public long getSize(String path) throws IOException {
if (!created()) { if (!created()) {
if (path.isEmpty()) return 0; if (path.isEmpty()) return 0;
} else { } else {
@ -172,9 +171,8 @@ public class FileMount implements IWritableMount {
throw new FileOperationException(path, "No such file"); throw new FileOperationException(path, "No such file");
} }
@Nonnull
@Override @Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException { public ReadableByteChannel openForRead(String path) throws IOException {
if (created()) { if (created()) {
var file = getRealPath(path); var file = getRealPath(path);
if (file.exists() && !file.isDirectory()) return FileChannel.open(file.toPath(), READ_OPTIONS); if (file.exists() && !file.isDirectory()) return FileChannel.open(file.toPath(), READ_OPTIONS);
@ -183,9 +181,8 @@ public class FileMount implements IWritableMount {
throw new FileOperationException(path, "No such file"); throw new FileOperationException(path, "No such file");
} }
@Nonnull
@Override @Override
public BasicFileAttributes getAttributes(@Nonnull String path) throws IOException { public BasicFileAttributes getAttributes(String path) throws IOException {
if (created()) { if (created()) {
var file = getRealPath(path); var file = getRealPath(path);
if (file.exists()) return Files.readAttributes(file.toPath(), BasicFileAttributes.class); if (file.exists()) return Files.readAttributes(file.toPath(), BasicFileAttributes.class);
@ -197,7 +194,7 @@ public class FileMount implements IWritableMount {
// IWritableMount implementation // IWritableMount implementation
@Override @Override
public void makeDirectory(@Nonnull String path) throws IOException { public void makeDirectory(String path) throws IOException {
create(); create();
var file = getRealPath(path); var file = getRealPath(path);
if (file.exists()) { if (file.exists()) {
@ -224,7 +221,7 @@ public class FileMount implements IWritableMount {
} }
@Override @Override
public void delete(@Nonnull String path) throws IOException { public void delete(String path) throws IOException {
if (path.isEmpty()) throw new FileOperationException(path, "Access denied"); if (path.isEmpty()) throw new FileOperationException(path, "Access denied");
if (created()) { if (created()) {
@ -252,9 +249,8 @@ public class FileMount implements IWritableMount {
} }
} }
@Nonnull
@Override @Override
public WritableByteChannel openForWrite(@Nonnull String path) throws IOException { public WritableByteChannel openForWrite(String path) throws IOException {
create(); create();
var file = getRealPath(path); var file = getRealPath(path);
if (file.exists() && file.isDirectory()) throw new FileOperationException(path, "Cannot write to directory"); if (file.exists() && file.isDirectory()) throw new FileOperationException(path, "Cannot write to directory");
@ -269,9 +265,8 @@ public class FileMount implements IWritableMount {
return new SeekableCountingChannel(Files.newByteChannel(file.toPath(), WRITE_OPTIONS), MINIMUM_FILE_SIZE); return new SeekableCountingChannel(Files.newByteChannel(file.toPath(), WRITE_OPTIONS), MINIMUM_FILE_SIZE);
} }
@Nonnull
@Override @Override
public WritableByteChannel openForAppend(@Nonnull String path) throws IOException { public WritableByteChannel openForAppend(String path) throws IOException {
if (!created()) { if (!created()) {
throw new FileOperationException(path, "No such file"); throw new FileOperationException(path, "No such file");
} }
@ -292,7 +287,6 @@ public class FileMount implements IWritableMount {
return Math.max(capacity - usedSpace, 0); return Math.max(capacity - usedSpace, 0);
} }
@Nonnull
@Override @Override
public OptionalLong getCapacity() { public OptionalLong getCapacity() {
return OptionalLong.of(capacity - MINIMUM_FILE_SIZE); return OptionalLong.of(capacity - MINIMUM_FILE_SIZE);

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import com.google.common.base.Splitter;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import dan200.computercraft.api.filesystem.IFileSystem; import dan200.computercraft.api.filesystem.IFileSystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
@ -12,7 +13,6 @@ import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.CoreConfig; import dan200.computercraft.core.CoreConfig;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import javax.annotation.Nonnull;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.Reference; import java.lang.ref.Reference;
@ -60,16 +60,15 @@ public class FileSystem {
} }
public synchronized void mount(String label, String location, IMount mount) throws FileSystemException { public synchronized void mount(String label, String location, IMount mount) throws FileSystemException {
if (mount == null) throw new NullPointerException(); Objects.requireNonNull(mount, "mount cannot be null");
location = sanitizePath(location); location = sanitizePath(location);
if (location.contains("..")) throw new FileSystemException("Cannot mount below the root"); if (location.contains("..")) throw new FileSystemException("Cannot mount below the root");
mount(new MountWrapper(label, location, mount)); mount(new MountWrapper(label, location, mount));
} }
public synchronized void mountWritable(String label, String location, IWritableMount mount) throws FileSystemException { public synchronized void mountWritable(String label, String location, IWritableMount mount) throws FileSystemException {
if (mount == null) { Objects.requireNonNull(mount, "mount cannot be null");
throw new NullPointerException();
}
location = sanitizePath(location); location = sanitizePath(location);
if (location.contains("..")) { if (location.contains("..")) {
throw new FileSystemException("Cannot mount below the root"); throw new FileSystemException("Cannot mount below the root");
@ -313,7 +312,7 @@ public class FileSystem {
} catch (AccessDeniedException e) { } catch (AccessDeniedException e) {
throw new FileSystemException("Access denied"); throw new FileSystemException("Access denied");
} catch (IOException e) { } catch (IOException e) {
throw new FileSystemException(e.getMessage()); throw FileSystemException.of(e);
} }
} }
} }
@ -327,7 +326,7 @@ public class FileSystem {
} }
} }
private synchronized <T extends Closeable> FileSystemWrapper<T> openFile(@Nonnull MountWrapper mount, @Nonnull Channel channel, @Nonnull T file) throws FileSystemException { private synchronized <T extends Closeable> FileSystemWrapper<T> openFile(MountWrapper mount, Channel channel, T file) throws FileSystemException {
synchronized (openFiles) { synchronized (openFiles) {
if (CoreConfig.maximumFilesOpen > 0 && if (CoreConfig.maximumFilesOpen > 0 &&
openFiles.size() >= CoreConfig.maximumFilesOpen) { openFiles.size() >= CoreConfig.maximumFilesOpen) {
@ -355,7 +354,7 @@ public class FileSystem {
path = sanitizePath(path); path = sanitizePath(path);
var mount = getMount(path); var mount = getMount(path);
var channel = mount.openForRead(path); var channel = mount.openForRead(path);
return channel != null ? openFile(mount, channel, open.apply(channel)) : null; return openFile(mount, channel, open.apply(channel));
} }
public synchronized <T extends Closeable> FileSystemWrapper<T> openForWrite(String path, boolean append, Function<WritableByteChannel, T> open) throws FileSystemException { public synchronized <T extends Closeable> FileSystemWrapper<T> openForWrite(String path, boolean append, Function<WritableByteChannel, T> open) throws FileSystemException {
@ -364,7 +363,7 @@ public class FileSystem {
path = sanitizePath(path); path = sanitizePath(path);
var mount = getMount(path); var mount = getMount(path);
var channel = append ? mount.openForAppend(path) : mount.openForWrite(path); var channel = append ? mount.openForAppend(path) : mount.openForWrite(path);
return channel != null ? openFile(mount, channel, open.apply(channel)) : null; return openFile(mount, channel, open.apply(channel));
} }
public synchronized long getFreeSpace(String path) throws FileSystemException { public synchronized long getFreeSpace(String path) throws FileSystemException {
@ -373,7 +372,6 @@ public class FileSystem {
return mount.getFreeSpace(); return mount.getFreeSpace();
} }
@Nonnull
public synchronized OptionalLong getCapacity(String path) throws FileSystemException { public synchronized OptionalLong getCapacity(String path) throws FileSystemException {
path = sanitizePath(path); path = sanitizePath(path);
var mount = getMount(path); var mount = getMount(path);
@ -430,9 +428,8 @@ public class FileSystem {
path = cleanName.toString(); path = cleanName.toString();
// Collapse the string into its component parts, removing ..'s // Collapse the string into its component parts, removing ..'s
var parts = path.split("/"); var outputParts = new ArrayDeque<String>();
var outputParts = new Stack<String>(); for (var part : Splitter.on('/').split(path)) {
for (var part : parts) {
if (part.isEmpty() || part.equals(".") || threeDotsPattern.matcher(part).matches()) { if (part.isEmpty() || part.equals(".") || threeDotsPattern.matcher(part).matches()) {
// . is redundant // . is redundant
// ... and more are treated as . // ... and more are treated as .
@ -441,37 +438,26 @@ public class FileSystem {
if (part.equals("..")) { if (part.equals("..")) {
// .. can cancel out the last folder entered // .. can cancel out the last folder entered
if (!outputParts.empty()) { if (!outputParts.isEmpty()) {
var top = outputParts.peek(); var top = outputParts.peekLast();
if (!top.equals("..")) { if (!top.equals("..")) {
outputParts.pop(); outputParts.removeLast();
} else { } else {
outputParts.push(".."); outputParts.addLast("..");
} }
} else { } else {
outputParts.push(".."); outputParts.addLast("..");
} }
} else if (part.length() >= 255) { } else if (part.length() >= 255) {
// If part length > 255 and it is the last part // If part length > 255 and it is the last part
outputParts.push(part.substring(0, 255)); outputParts.addLast(part.substring(0, 255));
} else { } else {
// Anything else we add to the stack // Anything else we add to the stack
outputParts.push(part); outputParts.addLast(part);
} }
} }
// Recombine the output parts into a new string return String.join("/", outputParts);
var result = new StringBuilder();
var it = outputParts.iterator();
while (it.hasNext()) {
var part = it.next();
result.append(part);
if (it.hasNext()) {
result.append('/');
}
}
return result.toString();
} }
public static boolean contains(String pathA, String pathB) { public static boolean contains(String pathA, String pathB) {

View File

@ -5,6 +5,7 @@
*/ */
package dan200.computercraft.core.filesystem; package dan200.computercraft.core.filesystem;
import java.io.IOException;
import java.io.Serial; import java.io.Serial;
public class FileSystemException extends Exception { public class FileSystemException extends Exception {
@ -14,4 +15,12 @@ public class FileSystemException extends Exception {
FileSystemException(String s) { FileSystemException(String s) {
super(s); super(s);
} }
public static FileSystemException of(IOException e) {
return new FileSystemException(getMessage(e));
}
public static String getMessage(IOException e) {
return e.getMessage() == null ? "Access denied" : e.getMessage();
}
} }

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import javax.annotation.Nonnull;
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.lang.ref.ReferenceQueue; import java.lang.ref.ReferenceQueue;
@ -57,7 +56,6 @@ public class FileSystemWrapper<T extends Closeable> implements TrackingCloseable
return isOpen; return isOpen;
} }
@Nonnull
public T get() { public T get() {
return closeable.get(); return closeable.get();
} }

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IFileSystem; import dan200.computercraft.api.filesystem.IFileSystem;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
@ -23,7 +22,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public void makeDirectory(@Nonnull String path) throws IOException { public void makeDirectory(String path) throws IOException {
try { try {
filesystem.makeDir(path); filesystem.makeDir(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -32,7 +31,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public void delete(@Nonnull String path) throws IOException { public void delete(String path) throws IOException {
try { try {
filesystem.delete(path); filesystem.delete(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -40,9 +39,8 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
} }
@Nonnull
@Override @Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException { public ReadableByteChannel openForRead(String path) throws IOException {
try { try {
// FIXME: Think of a better way of implementing this, so closing this will close on the computer. // FIXME: Think of a better way of implementing this, so closing this will close on the computer.
return filesystem.openForRead(path, Function.identity()).get(); return filesystem.openForRead(path, Function.identity()).get();
@ -51,9 +49,8 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
} }
@Nonnull
@Override @Override
public WritableByteChannel openForWrite(@Nonnull String path) throws IOException { public WritableByteChannel openForWrite(String path) throws IOException {
try { try {
return filesystem.openForWrite(path, false, Function.identity()).get(); return filesystem.openForWrite(path, false, Function.identity()).get();
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -61,9 +58,8 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
} }
@Nonnull
@Override @Override
public WritableByteChannel openForAppend(@Nonnull String path) throws IOException { public WritableByteChannel openForAppend(String path) throws IOException {
try { try {
return filesystem.openForWrite(path, true, Function.identity()).get(); return filesystem.openForWrite(path, true, Function.identity()).get();
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -81,7 +77,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public boolean exists(@Nonnull String path) throws IOException { public boolean exists(String path) throws IOException {
try { try {
return filesystem.exists(path); return filesystem.exists(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -90,7 +86,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public boolean isDirectory(@Nonnull String path) throws IOException { public boolean isDirectory(String path) throws IOException {
try { try {
return filesystem.isDir(path); return filesystem.isDir(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -99,7 +95,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public void list(@Nonnull String path, @Nonnull List<String> contents) throws IOException { public void list(String path, List<String> contents) throws IOException {
try { try {
Collections.addAll(contents, filesystem.list(path)); Collections.addAll(contents, filesystem.list(path));
} catch (FileSystemException e) { } catch (FileSystemException e) {
@ -108,7 +104,7 @@ public class FileSystemWrapperMount implements IFileSystem {
} }
@Override @Override
public long getSize(@Nonnull String path) throws IOException { public long getSize(String path) throws IOException {
try { try {
return filesystem.getSize(path); return filesystem.getSize(path);
} catch (FileSystemException e) { } catch (FileSystemException e) {

View File

@ -8,12 +8,13 @@ package dan200.computercraft.core.filesystem;
import com.google.common.cache.Cache; import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheBuilder;
import com.google.common.io.ByteStreams; import com.google.common.io.ByteStreams;
import com.google.errorprone.annotations.concurrent.LazyInit;
import dan200.computercraft.api.filesystem.FileOperationException; import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.core.apis.handles.ArrayByteChannel; import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import dan200.computercraft.core.util.IoUtil; import dan200.computercraft.core.util.IoUtil;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
@ -100,6 +101,7 @@ public class JarMount implements IMount {
} }
} }
@Nullable
private FileEntry get(String path) { private FileEntry get(String path) {
var lastEntry = root; var lastEntry = root;
var lastIndex = 0; var lastIndex = 0;
@ -139,18 +141,18 @@ public class JarMount implements IMount {
} }
@Override @Override
public boolean exists(@Nonnull String path) { public boolean exists(String path) {
return get(path) != null; return get(path) != null;
} }
@Override @Override
public boolean isDirectory(@Nonnull String path) { public boolean isDirectory(String path) {
var file = get(path); var file = get(path);
return file != null && file.isDirectory(); return file != null && file.isDirectory();
} }
@Override @Override
public void list(@Nonnull String path, @Nonnull List<String> contents) throws IOException { public void list(String path, List<String> contents) throws IOException {
var file = get(path); var file = get(path);
if (file == null || !file.isDirectory()) throw new FileOperationException(path, "Not a directory"); if (file == null || !file.isDirectory()) throw new FileOperationException(path, "Not a directory");
@ -158,15 +160,14 @@ public class JarMount implements IMount {
} }
@Override @Override
public long getSize(@Nonnull String path) throws IOException { public long getSize(String path) throws IOException {
var file = get(path); var file = get(path);
if (file != null) return file.size; if (file != null) return file.size;
throw new FileOperationException(path, "No such file"); throw new FileOperationException(path, "No such file");
} }
@Nonnull
@Override @Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException { public ReadableByteChannel openForRead(String path) throws IOException {
var file = get(path); var file = get(path);
if (file != null && !file.isDirectory()) { if (file != null && !file.isDirectory()) {
var contents = CONTENTS_CACHE.getIfPresent(file); var contents = CONTENTS_CACHE.getIfPresent(file);
@ -191,9 +192,8 @@ public class JarMount implements IMount {
throw new FileOperationException(path, "No such file"); throw new FileOperationException(path, "No such file");
} }
@Nonnull
@Override @Override
public BasicFileAttributes getAttributes(@Nonnull String path) throws IOException { public BasicFileAttributes getAttributes(String path) throws IOException {
var file = get(path); var file = get(path);
if (file != null) { if (file != null) {
var entry = zip.getEntry(file.path); var entry = zip.getEntry(file.path);
@ -204,8 +204,12 @@ public class JarMount implements IMount {
} }
private static class FileEntry { private static class FileEntry {
@LazyInit // TODO: Might be nicer to use @Initializer on setup(...)
String path; String path;
long size; long size;
@Nullable
Map<String, FileEntry> children; Map<String, FileEntry> children;
void setup(ZipEntry entry) { void setup(ZipEntry entry) {
@ -285,6 +289,7 @@ public class JarMount implements IMount {
return entry.getSize(); return entry.getSize();
} }
@Nullable
@Override @Override
public Object fileKey() { public Object fileKey() {
return null; return null;
@ -292,7 +297,7 @@ public class JarMount implements IMount {
private static final FileTime EPOCH = FileTime.from(Instant.EPOCH); private static final FileTime EPOCH = FileTime.from(Instant.EPOCH);
private static FileTime orEpoch(FileTime time) { private static FileTime orEpoch(@Nullable FileTime time) {
return time == null ? EPOCH : time; return time == null ? EPOCH : time;
} }
} }

View File

@ -9,7 +9,6 @@ import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
@ -24,7 +23,7 @@ class MountWrapper {
private final String location; private final String location;
private final IMount mount; private final IMount mount;
private final IWritableMount writableMount; private final @Nullable IWritableMount writableMount;
MountWrapper(String label, String location, IMount mount) { MountWrapper(String label, String location, IMount mount) {
this.label = label; this.label = label;
@ -107,7 +106,6 @@ class MountWrapper {
} }
} }
@Nonnull
public BasicFileAttributes getAttributes(String path) throws FileSystemException { public BasicFileAttributes getAttributes(String path) throws FileSystemException {
path = toLocal(path); path = toLocal(path);
try { try {
@ -213,9 +211,9 @@ class MountWrapper {
return FileSystem.toLocal(path, location); return FileSystem.toLocal(path, location);
} }
private FileSystemException localExceptionOf(@Nullable String localPath, @Nonnull IOException e) { private FileSystemException localExceptionOf(@Nullable String localPath, IOException e) {
if (!location.isEmpty() && e instanceof FileOperationException ex) { if (!location.isEmpty() && e instanceof FileOperationException ex) {
if (ex.getFilename() != null) return localExceptionOf(ex.getFilename(), ex.getMessage()); if (ex.getFilename() != null) return localExceptionOf(ex.getFilename(), FileSystemException.getMessage(ex));
} }
if (e instanceof java.nio.file.FileSystemException ex) { if (e instanceof java.nio.file.FileSystemException ex) {
@ -225,7 +223,7 @@ class MountWrapper {
return localPath == null ? new FileSystemException(message) : localExceptionOf(localPath, message); return localPath == null ? new FileSystemException(message) : localExceptionOf(localPath, message);
} }
return new FileSystemException(e.getMessage()); return FileSystemException.of(e);
} }
private FileSystemException localExceptionOf(String path, String message) { private FileSystemException localExceptionOf(String path, String message) {

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.filesystem;
import dan200.computercraft.api.filesystem.IMount; import dan200.computercraft.api.filesystem.IMount;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
@ -23,34 +22,32 @@ public class SubMount implements IMount {
} }
@Override @Override
public boolean exists(@Nonnull String path) throws IOException { public boolean exists(String path) throws IOException {
return parent.exists(getFullPath(path)); return parent.exists(getFullPath(path));
} }
@Override @Override
public boolean isDirectory(@Nonnull String path) throws IOException { public boolean isDirectory(String path) throws IOException {
return parent.isDirectory(getFullPath(path)); return parent.isDirectory(getFullPath(path));
} }
@Override @Override
public void list(@Nonnull String path, @Nonnull List<String> contents) throws IOException { public void list(String path, List<String> contents) throws IOException {
parent.list(getFullPath(path), contents); parent.list(getFullPath(path), contents);
} }
@Override @Override
public long getSize(@Nonnull String path) throws IOException { public long getSize(String path) throws IOException {
return parent.getSize(getFullPath(path)); return parent.getSize(getFullPath(path));
} }
@Nonnull
@Override @Override
public ReadableByteChannel openForRead(@Nonnull String path) throws IOException { public ReadableByteChannel openForRead(String path) throws IOException {
return parent.openForRead(getFullPath(path)); return parent.openForRead(getFullPath(path));
} }
@Nonnull
@Override @Override
public BasicFileAttributes getAttributes(@Nonnull String path) throws IOException { public BasicFileAttributes getAttributes(String path) throws IOException {
return parent.getAttributes(getFullPath(path)); return parent.getAttributes(getFullPath(path));
} }

View File

@ -28,14 +28,14 @@ class BasicFunction extends VarArgFunction {
private final LuaMethod method; private final LuaMethod method;
private final Object instance; private final Object instance;
private final ILuaContext context; private final ILuaContext context;
private final String name; private final String funcName;
BasicFunction(CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name) { BasicFunction(CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name) {
this.machine = machine; this.machine = machine;
this.method = method; this.method = method;
this.instance = instance; this.instance = instance;
this.context = context; this.context = context;
this.name = name; funcName = name;
} }
@Override @Override
@ -47,7 +47,7 @@ class BasicFunction extends VarArgFunction {
} catch (LuaException e) { } catch (LuaException e) {
throw wrap(e); throw wrap(e);
} catch (Throwable t) { } catch (Throwable t) {
LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", name, instance, t); LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", funcName, instance, t);
throw new LuaError("Java Exception Thrown: " + t, 0); throw new LuaError("Java Exception Thrown: " + t, 0);
} finally { } finally {
arguments.close(); arguments.close();

View File

@ -27,7 +27,6 @@ import org.squiddev.cobalt.debug.DebugState;
import org.squiddev.cobalt.lib.*; import org.squiddev.cobalt.lib.*;
import org.squiddev.cobalt.lib.platform.VoidResourceManipulator; import org.squiddev.cobalt.lib.platform.VoidResourceManipulator;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serial; import java.io.Serial;
@ -58,11 +57,11 @@ public class CobaltLuaMachine implements ILuaMachine {
private final TimeoutDebugHandler debug; private final TimeoutDebugHandler debug;
private final ILuaContext context; private final ILuaContext context;
private LuaState state; private @Nullable LuaState state;
private LuaTable globals; private @Nullable LuaTable globals;
private LuaThread mainRoutine = null; private @Nullable LuaThread mainRoutine = null;
private String eventFilter = null; private @Nullable String eventFilter = null;
public CobaltLuaMachine(MachineEnvironment environment) { public CobaltLuaMachine(MachineEnvironment environment) {
timeout = environment.timeout(); timeout = environment.timeout();
@ -115,7 +114,9 @@ public class CobaltLuaMachine implements ILuaMachine {
} }
@Override @Override
public void addAPI(@Nonnull ILuaAPI api) { public void addAPI(ILuaAPI api) {
if (globals == null) throw new IllegalStateException("Machine has been closed");
// Add the methods of an API to the global table // Add the methods of an API to the global table
var table = wrapLuaObject(api); var table = wrapLuaObject(api);
if (table == null) { if (table == null) {
@ -128,9 +129,9 @@ public class CobaltLuaMachine implements ILuaMachine {
} }
@Override @Override
public MachineResult loadBios(@Nonnull InputStream bios) { public MachineResult loadBios(InputStream bios) {
// Begin executing a file (ie, the bios) if (mainRoutine != null) throw new IllegalStateException("Already set up the machine");
if (mainRoutine != null) return MachineResult.OK; if (state == null || globals == null) throw new IllegalStateException("Machine has been destroyed.");
try { try {
var value = LoadState.load(state, bios, "@bios.lua", globals); var value = LoadState.load(state, bios, "@bios.lua", globals);
@ -147,8 +148,8 @@ public class CobaltLuaMachine implements ILuaMachine {
} }
@Override @Override
public MachineResult handleEvent(String eventName, Object[] arguments) { public MachineResult handleEvent(@Nullable String eventName, @Nullable Object[] arguments) {
if (mainRoutine == null) return MachineResult.OK; if (mainRoutine == null || state == null) throw new IllegalStateException("Machine has been closed");
if (eventFilter != null && eventName != null && !eventName.equals(eventFilter) && !eventName.equals("terminate")) { if (eventFilter != null && eventName != null && !eventName.equals(eventFilter) && !eventName.equals("terminate")) {
return MachineResult.OK; return MachineResult.OK;
@ -232,13 +233,13 @@ public class CobaltLuaMachine implements ILuaMachine {
try { try {
if (table.keyCount() == 0) return null; if (table.keyCount() == 0) return null;
} catch (LuaError ignored) { } catch (LuaError ignored) {
// next should never throw on nil.
} }
return table; return table;
} }
@Nonnull private LuaValue toValue(@Nullable Object object, @Nullable IdentityHashMap<Object, LuaValue> values) {
private LuaValue toValue(@Nullable Object object, @Nullable Map<Object, LuaValue> values) {
if (object == null) return Constants.NIL; if (object == null) return Constants.NIL;
if (object instanceof Number num) return valueOf(num.doubleValue()); if (object instanceof Number num) return valueOf(num.doubleValue());
if (object instanceof Boolean bool) return valueOf(bool); if (object instanceof Boolean bool) return valueOf(bool);
@ -304,11 +305,11 @@ public class CobaltLuaMachine implements ILuaMachine {
return Constants.NIL; return Constants.NIL;
} }
Varargs toValues(Object[] objects) { Varargs toValues(@Nullable Object[] objects) {
if (objects == null || objects.length == 0) return Constants.NONE; if (objects == null || objects.length == 0) return Constants.NONE;
if (objects.length == 1) return toValue(objects[0], null); if (objects.length == 1) return toValue(objects[0], null);
Map<Object, LuaValue> result = new IdentityHashMap<>(0); var result = new IdentityHashMap<Object, LuaValue>(0);
var values = new LuaValue[objects.length]; var values = new LuaValue[objects.length];
for (var i = 0; i < values.length; i++) { for (var i = 0; i < values.length; i++) {
var object = objects[i]; var object = objects[i];
@ -317,7 +318,8 @@ public class CobaltLuaMachine implements ILuaMachine {
return varargsOf(values); return varargsOf(values);
} }
static Object toObject(LuaValue value, Map<LuaValue, Object> objects) { @Nullable
static Object toObject(LuaValue value, @Nullable IdentityHashMap<LuaValue, Object> objects) {
switch (value.type()) { switch (value.type()) {
case Constants.TNIL: case Constants.TNIL:
case Constants.TNONE: case Constants.TNONE:
@ -448,6 +450,7 @@ public class CobaltLuaMachine implements ILuaMachine {
@Serial @Serial
private static final long serialVersionUID = 7954092008586367501L; private static final long serialVersionUID = 7954092008586367501L;
@SuppressWarnings("StaticAssignmentOfThrowable")
static final HardAbortError INSTANCE = new HardAbortError(); static final HardAbortError INSTANCE = new HardAbortError();
private HardAbortError() { private HardAbortError() {

View File

@ -8,7 +8,6 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.api.lua.IDynamicLuaObject; import dan200.computercraft.api.lua.IDynamicLuaObject;
import dan200.computercraft.api.lua.ILuaAPI; import dan200.computercraft.api.lua.ILuaAPI;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.InputStream; import java.io.InputStream;
@ -32,7 +31,7 @@ public interface ILuaMachine {
* *
* @param api The API to register. * @param api The API to register.
*/ */
void addAPI(@Nonnull ILuaAPI api); void addAPI(ILuaAPI api);
/** /**
* Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is * Create a function from the provided program, and set it up to run when {@link #handleEvent(String, Object[])} is
@ -43,7 +42,7 @@ public interface ILuaMachine {
* @param bios The stream containing the boot program. * @param bios The stream containing the boot program.
* @return The result of loading this machine. Will either be OK, or the error message when loading the bios. * @return The result of loading this machine. Will either be OK, or the error message when loading the bios.
*/ */
MachineResult loadBios(@Nonnull InputStream bios); MachineResult loadBios(InputStream bios);
/** /**
* Resume the machine, either starting or resuming the coroutine. * Resume the machine, either starting or resuming the coroutine.

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.lua;
import dan200.computercraft.core.computer.TimeoutState; import dan200.computercraft.core.computer.TimeoutState;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.InputStream; import java.io.InputStream;
@ -42,19 +41,19 @@ public final class MachineResult {
private final boolean error; private final boolean error;
private final boolean pause; private final boolean pause;
private final String message; private final @Nullable String message;
private MachineResult(boolean error, boolean pause, String message) { private MachineResult(boolean error, boolean pause, @Nullable String message) {
this.pause = pause; this.pause = pause;
this.message = message; this.message = message;
this.error = error; this.error = error;
} }
public static MachineResult error(@Nonnull String error) { public static MachineResult error(String error) {
return new MachineResult(true, false, error); return new MachineResult(true, false, error);
} }
public static MachineResult error(@Nonnull Exception error) { public static MachineResult error(Exception error) {
return new MachineResult(true, false, error.getMessage()); return new MachineResult(true, false, error.getMessage());
} }

View File

@ -17,7 +17,6 @@ import org.squiddev.cobalt.*;
import org.squiddev.cobalt.debug.DebugFrame; import org.squiddev.cobalt.debug.DebugFrame;
import org.squiddev.cobalt.function.ResumableVarArgFunction; import org.squiddev.cobalt.function.ResumableVarArgFunction;
import javax.annotation.Nonnull;
/** /**
* Calls a {@link LuaMethod}, and interprets the resulting {@link MethodResult}, either returning the result or yielding * Calls a {@link LuaMethod}, and interprets the resulting {@link MethodResult}, either returning the result or yielding
@ -26,7 +25,6 @@ import javax.annotation.Nonnull;
class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterpreterFunction.Container> { class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterpreterFunction.Container> {
private static final Logger LOG = LoggerFactory.getLogger(ResultInterpreterFunction.class); private static final Logger LOG = LoggerFactory.getLogger(ResultInterpreterFunction.class);
@Nonnull
static class Container { static class Container {
ILuaCallback callback; ILuaCallback callback;
final int errorAdjust; final int errorAdjust;
@ -41,14 +39,14 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
private final LuaMethod method; private final LuaMethod method;
private final Object instance; private final Object instance;
private final ILuaContext context; private final ILuaContext context;
private final String name; private final String funcName;
ResultInterpreterFunction(CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name) { ResultInterpreterFunction(CobaltLuaMachine machine, LuaMethod method, Object instance, ILuaContext context, String name) {
this.machine = machine; this.machine = machine;
this.method = method; this.method = method;
this.instance = instance; this.instance = instance;
this.context = context; this.context = context;
this.name = name; funcName = name;
} }
@Override @Override
@ -60,7 +58,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
} catch (LuaException e) { } catch (LuaException e) {
throw wrap(e, 0); throw wrap(e, 0);
} catch (Throwable t) { } catch (Throwable t) {
LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", name, instance, t); LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", funcName, instance, t);
throw new LuaError("Java Exception Thrown: " + t, 0); throw new LuaError("Java Exception Thrown: " + t, 0);
} finally { } finally {
arguments.close(); arguments.close();
@ -84,7 +82,7 @@ class ResultInterpreterFunction extends ResumableVarArgFunction<ResultInterprete
} catch (LuaException e) { } catch (LuaException e) {
throw wrap(e, container.errorAdjust); throw wrap(e, container.errorAdjust);
} catch (Throwable t) { } catch (Throwable t) {
LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", name, container.callback, t); LOG.error(Logging.JAVA_ERROR, "Error calling {} on {}", funcName, container.callback, t);
throw new LuaError("Java Exception Thrown: " + t, 0); throw new LuaError("Java Exception Thrown: " + t, 0);
} }

View File

@ -9,7 +9,7 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.api.lua.LuaValues;
import org.squiddev.cobalt.*; import org.squiddev.cobalt.*;
import javax.annotation.Nonnull; import javax.annotation.Nullable;
import java.util.*; import java.util.*;
import static dan200.computercraft.api.lua.LuaValues.badTableItem; import static dan200.computercraft.api.lua.LuaValues.badTableItem;
@ -18,7 +18,7 @@ import static dan200.computercraft.api.lua.LuaValues.getNumericType;
class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object> { class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object> {
private final VarargArguments arguments; private final VarargArguments arguments;
private final LuaTable table; private final LuaTable table;
private Map<Object, Object> backingMap; private @Nullable Map<Object, Object> backingMap;
TableImpl(VarargArguments arguments, LuaTable table) { TableImpl(VarargArguments arguments, LuaTable table) {
this.arguments = arguments; this.arguments = arguments;
@ -61,7 +61,6 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
} }
} }
@Nonnull
private LuaValue getImpl(Object o) { private LuaValue getImpl(Object o) {
checkValid(); checkValid();
if (o instanceof String) return table.rawget((String) o); if (o instanceof String) return table.rawget((String) o);
@ -74,12 +73,12 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
return !getImpl(o).isNil(); return !getImpl(o).isNil();
} }
@Nullable
@Override @Override
public Object get(Object o) { public Object get(Object o) {
return CobaltLuaMachine.toObject(getImpl(o), null); return CobaltLuaMachine.toObject(getImpl(o), null);
} }
@Nonnull
private Map<Object, Object> getBackingMap() { private Map<Object, Object> getBackingMap() {
checkValid(); checkValid();
if (backingMap != null) return backingMap; if (backingMap != null) return backingMap;
@ -93,19 +92,16 @@ class TableImpl implements dan200.computercraft.api.lua.LuaTable<Object, Object>
return getBackingMap().containsKey(o); return getBackingMap().containsKey(o);
} }
@Nonnull
@Override @Override
public Set<Object> keySet() { public Set<Object> keySet() {
return getBackingMap().keySet(); return getBackingMap().keySet();
} }
@Nonnull
@Override @Override
public Collection<Object> values() { public Collection<Object> values() {
return getBackingMap().values(); return getBackingMap().values();
} }
@Nonnull
@Override @Override
public Set<Entry<Object, Object>> entrySet() { public Set<Entry<Object, Object>> entrySet() {
return getBackingMap().entrySet(); return getBackingMap().entrySet();

View File

@ -10,7 +10,6 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaValues; import dan200.computercraft.api.lua.LuaValues;
import org.squiddev.cobalt.*; import org.squiddev.cobalt.*;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.Optional; import java.util.Optional;
@ -20,7 +19,7 @@ final class VarargArguments implements IArguments {
boolean closed; boolean closed;
private final Varargs varargs; private final Varargs varargs;
private Object[] cache; private @Nullable Object[] cache;
private VarargArguments(Varargs varargs) { private VarargArguments(Varargs varargs) {
this.varargs = varargs; this.varargs = varargs;
@ -72,7 +71,6 @@ final class VarargArguments implements IArguments {
return value instanceof LuaInteger ? value.toInteger() : (long) LuaValues.checkFinite(index, value.toDouble()); return value instanceof LuaInteger ? value.toInteger() : (long) LuaValues.checkFinite(index, value.toDouble());
} }
@Nonnull
@Override @Override
public ByteBuffer getBytes(int index) throws LuaException { public ByteBuffer getBytes(int index) throws LuaException {
var value = varargs.arg(index + 1); var value = varargs.arg(index + 1);
@ -92,7 +90,6 @@ final class VarargArguments implements IArguments {
return Optional.of(ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer()); return Optional.of(ByteBuffer.wrap(str.bytes, str.offset, str.length).asReadOnlyBuffer());
} }
@Nonnull
@Override @Override
public dan200.computercraft.api.lua.LuaTable<?, ?> getTableUnsafe(int index) throws LuaException { public dan200.computercraft.api.lua.LuaTable<?, ?> getTableUnsafe(int index) throws LuaException {
if (closed) { if (closed) {
@ -104,7 +101,6 @@ final class VarargArguments implements IArguments {
return new TableImpl(this, (LuaTable) value); return new TableImpl(this, (LuaTable) value);
} }
@Nonnull
@Override @Override
public Optional<dan200.computercraft.api.lua.LuaTable<?, ?>> optTableUnsafe(int index) throws LuaException { public Optional<dan200.computercraft.api.lua.LuaTable<?, ?>> optTableUnsafe(int index) throws LuaException {
if (closed) { if (closed) {

View File

@ -0,0 +1,22 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
/**
* ComputerCraft's core Lua runtime and APIs.
* <p>
* This is not considered part of the stable API, and so should not be consumed by other Minecraft mods. However,
* emulators or other CC-tooling may find this useful.
*/
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,
TypeUseLocation.PARAMETER,
TypeUseLocation.FIELD,
})
package dan200.computercraft.core;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.terminal;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import javax.annotation.Nonnull;
public class Palette { public class Palette {
public static final int PALETTE_SIZE = 16; public static final int PALETTE_SIZE = 16;
@ -46,7 +45,7 @@ public class Palette {
} }
public double[] getColour(int i) { public double[] getColour(int i) {
return i >= 0 && i < PALETTE_SIZE ? colours[i] : null; return colours[i];
} }
/** /**
@ -58,7 +57,6 @@ public class Palette {
* @param i The colour index. * @param i The colour index.
* @return The number as a tuple of bytes. * @return The number as a tuple of bytes.
*/ */
@Nonnull
public byte[] getRenderColours(int i) { public byte[] getRenderColours(int i) {
return byteColours[i]; return byteColours[i];
} }

View File

@ -7,7 +7,6 @@ package dan200.computercraft.core.terminal;
import dan200.computercraft.core.util.Colour; import dan200.computercraft.core.util.Colour;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -36,7 +35,7 @@ public class Terminal {
this(width, height, colour, null); this(width, height, colour, null);
} }
public Terminal(int width, int height, boolean colour, Runnable changedCallback) { public Terminal(int width, int height, boolean colour, @Nullable Runnable changedCallback) {
this.width = width; this.width = width;
this.height = height; this.height = height;
this.colour = colour; this.colour = colour;
@ -163,7 +162,6 @@ public class Terminal {
return cursorBackgroundColour; return cursorBackgroundColour;
} }
@Nonnull
public Palette getPalette() { public Palette getPalette() {
return palette; return palette;
} }
@ -234,10 +232,7 @@ public class Terminal {
} }
public synchronized TextBuffer getLine(int y) { public synchronized TextBuffer getLine(int y) {
if (y >= 0 && y < height) { return text[y];
return text[y];
}
return null;
} }
public synchronized void setLine(int y, String text, String textColour, String backgroundColour) { public synchronized void setLine(int y, String text, String textColour, String backgroundColour) {
@ -248,17 +243,11 @@ public class Terminal {
} }
public synchronized TextBuffer getTextColourLine(int y) { public synchronized TextBuffer getTextColourLine(int y) {
if (y >= 0 && y < height) { return textColour[y];
return textColour[y];
}
return null;
} }
public synchronized TextBuffer getBackgroundColourLine(int y) { public synchronized TextBuffer getBackgroundColourLine(int y) {
if (y >= 0 && y < height) { return backgroundColour[y];
return backgroundColour[y];
}
return null;
} }
public final void setChanged() { public final void setChanged() {

View File

@ -5,6 +5,8 @@
*/ */
package dan200.computercraft.core.util; package dan200.computercraft.core.util;
import javax.annotation.Nullable;
public enum Colour { public enum Colour {
BLACK(0x111111), BLACK(0x111111),
RED(0xcc4c4c), RED(0xcc4c4c),
@ -29,6 +31,7 @@ public enum Colour {
return Colour.VALUES[colour]; return Colour.VALUES[colour];
} }
@Nullable
public static Colour fromHex(int colour) { public static Colour fromHex(int colour) {
for (var entry : VALUES) { for (var entry : VALUES) {
if (entry.getHex() == colour) return entry; if (entry.getHex() == colour) return entry;

View File

@ -17,6 +17,7 @@ public final class IoUtil {
try { try {
if (closeable != null) closeable.close(); if (closeable != null) closeable.close();
} catch (IOException ignored) { } catch (IOException ignored) {
// The whole point here is to suppress these exceptions!
} }
} }
} }

View File

@ -12,8 +12,6 @@ public final class StringUtil {
} }
public static String normaliseLabel(String label) { public static String normaliseLabel(String label) {
if (label == null) return null;
var length = Math.min(32, label.length()); var length = Math.min(32, label.length());
var builder = new StringBuilder(length); var builder = new StringBuilder(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {

View File

@ -23,7 +23,6 @@ import org.opentest4j.AssertionFailedError;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
@ -249,7 +248,6 @@ public class ComputerTestDelegate {
} }
public static class FakeModem implements IPeripheral { public static class FakeModem implements IPeripheral {
@Nonnull
@Override @Override
public String getType() { public String getType() {
return "modem"; return "modem";
@ -267,7 +265,6 @@ public class ComputerTestDelegate {
} }
public static class FakePeripheralHub implements IPeripheral { public static class FakePeripheralHub implements IPeripheral {
@Nonnull
@Override @Override
public String getType() { public String getType() {
return "peripheral_hub"; return "peripheral_hub";

View File

@ -8,7 +8,6 @@ package dan200.computercraft.core.apis;
import dan200.computercraft.api.lua.*; import dan200.computercraft.api.lua.*;
import dan200.computercraft.core.asm.LuaMethod; import dan200.computercraft.core.asm.LuaMethod;
import javax.annotation.Nonnull;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -51,7 +50,7 @@ public class ObjectWrapper implements ILuaContext {
} }
@Override @Override
public long issueMainThreadTask(@Nonnull ILuaTask task) { public long issueMainThreadTask(ILuaTask task) {
throw new IllegalStateException("Method should never queue events"); throw new IllegalStateException("Method should never queue events");
} }
} }

View File

@ -11,7 +11,6 @@ import dan200.computercraft.core.computer.ComputerSide;
import org.hamcrest.Matcher; import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Map; import java.util.Map;
@ -241,7 +240,7 @@ public class GeneratorTest {
private static final ILuaContext CONTEXT = new ILuaContext() { private static final ILuaContext CONTEXT = new ILuaContext() {
@Override @Override
public long issueMainThreadTask(@Nonnull ILuaTask task) { public long issueMainThreadTask(ILuaTask task) {
return 0; return 0;
} }
}; };

View File

@ -13,7 +13,6 @@ import dan200.computercraft.core.computer.ComputerBootstrap;
import dan200.computercraft.core.computer.ComputerSide; import dan200.computercraft.core.computer.ComputerSide;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.util.Collections; import java.util.Collections;
@ -99,7 +98,6 @@ public class MethodTest {
return 123; return 123;
} }
@Nonnull
@Override @Override
public String getType() { public String getType() {
return "main_thread"; return "main_thread";
@ -112,21 +110,18 @@ public class MethodTest {
} }
public static class Dynamic implements IDynamicLuaObject, ILuaAPI, IDynamicPeripheral { public static class Dynamic implements IDynamicLuaObject, ILuaAPI, IDynamicPeripheral {
@Nonnull
@Override @Override
public String[] getMethodNames() { public String[] getMethodNames() {
return new String[]{ "foo" }; return new String[]{ "foo" };
} }
@Nonnull
@Override @Override
public MethodResult callMethod(@Nonnull ILuaContext context, int method, @Nonnull IArguments arguments) { public MethodResult callMethod(ILuaContext context, int method, IArguments arguments) {
return MethodResult.of(123); return MethodResult.of(123);
} }
@Nonnull
@Override @Override
public MethodResult callMethod(@Nonnull IComputerAccess computer, @Nonnull ILuaContext context, int method, @Nonnull IArguments arguments) { public MethodResult callMethod(IComputerAccess computer, ILuaContext context, int method, IArguments arguments) {
return callMethod(context, method, arguments); return callMethod(context, method, arguments);
} }
@ -140,7 +135,6 @@ public class MethodTest {
return new String[]{ "dynamic" }; return new String[]{ "dynamic" };
} }
@Nonnull
@Override @Override
public String getType() { public String getType() {
return "dynamic"; return "dynamic";
@ -179,7 +173,6 @@ public class MethodTest {
throw new LuaException("!"); throw new LuaException("!");
} }
@Nonnull
@Override @Override
public String getType() { public String getType() {
return "throw"; return "throw";
@ -192,7 +185,6 @@ public class MethodTest {
} }
public static class ManyMethods implements IDynamicLuaObject, ILuaAPI { public static class ManyMethods implements IDynamicLuaObject, ILuaAPI {
@Nonnull
@Override @Override
public String[] getMethodNames() { public String[] getMethodNames() {
var methods = new String[40]; var methods = new String[40];
@ -200,9 +192,8 @@ public class MethodTest {
return methods; return methods;
} }
@Nonnull
@Override @Override
public MethodResult callMethod(@Nonnull ILuaContext context, int method, @Nonnull IArguments arguments) throws LuaException { public MethodResult callMethod(ILuaContext context, int method, IArguments arguments) throws LuaException {
return MethodResult.of(); return MethodResult.of();
} }

View File

@ -34,20 +34,6 @@ class TerminalTest {
assertEquals("fedcba9876543210", terminal.getBackgroundColourLine(1).toString()); assertEquals("fedcba9876543210", terminal.getBackgroundColourLine(1).toString());
} }
@Test
void testGetLineOutOfBounds() {
var terminal = new Terminal(16, 9, true);
assertNull(terminal.getLine(-5));
assertNull(terminal.getLine(12));
assertNull(terminal.getTextColourLine(-5));
assertNull(terminal.getTextColourLine(12));
assertNull(terminal.getBackgroundColourLine(-5));
assertNull(terminal.getBackgroundColourLine(12));
}
@Test @Test
void testDefaults() { void testDefaults() {
var terminal = new Terminal(16, 9, true); var terminal = new Terminal(16, 9, true);

View File

@ -8,7 +8,6 @@ package dan200.computercraft.test.core;
import net.jqwik.api.*; import net.jqwik.api.*;
import net.jqwik.api.arbitraries.SizableArbitrary; import net.jqwik.api.arbitraries.SizableArbitrary;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import java.math.BigInteger; import java.math.BigInteger;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
@ -43,25 +42,21 @@ public final class ArbitraryByteBuffer implements SizableArbitrary<ByteBuffer> {
return DEFAULT; return DEFAULT;
} }
@Nonnull
@Override @Override
public SizableArbitrary<ByteBuffer> ofMinSize(int minSize) { public SizableArbitrary<ByteBuffer> ofMinSize(int minSize) {
return new ArbitraryByteBuffer(minSize, maxSize, distribution); return new ArbitraryByteBuffer(minSize, maxSize, distribution);
} }
@Nonnull
@Override @Override
public SizableArbitrary<ByteBuffer> ofMaxSize(int maxSize) { public SizableArbitrary<ByteBuffer> ofMaxSize(int maxSize) {
return new ArbitraryByteBuffer(minSize, maxSize, distribution); return new ArbitraryByteBuffer(minSize, maxSize, distribution);
} }
@Nonnull
@Override @Override
public SizableArbitrary<ByteBuffer> withSizeDistribution(@Nonnull RandomDistribution distribution) { public SizableArbitrary<ByteBuffer> withSizeDistribution(RandomDistribution distribution) {
return new ArbitraryByteBuffer(minSize, maxSize, distribution); return new ArbitraryByteBuffer(minSize, maxSize, distribution);
} }
@Nonnull
@Override @Override
public RandomGenerator<ByteBuffer> generator(int genSize) { public RandomGenerator<ByteBuffer> generator(int genSize) {
var min = BigInteger.valueOf(minSize); var min = BigInteger.valueOf(minSize);
@ -78,7 +73,6 @@ public final class ArbitraryByteBuffer implements SizableArbitrary<ByteBuffer> {
}; };
} }
@Nonnull
@Override @Override
public EdgeCases<ByteBuffer> edgeCases(int maxEdgeCases) { public EdgeCases<ByteBuffer> edgeCases(int maxEdgeCases) {
return EdgeCases.fromSuppliers(Arrays.asList( return EdgeCases.fromSuppliers(Arrays.asList(
@ -133,13 +127,11 @@ public final class ArbitraryByteBuffer implements SizableArbitrary<ByteBuffer> {
this.minSize = minSize; this.minSize = minSize;
} }
@Nonnull
@Override @Override
public ByteBuffer value() { public ByteBuffer value() {
return value; return value;
} }
@Nonnull
@Override @Override
public Stream<Shrinkable<ByteBuffer>> shrink() { public Stream<Shrinkable<ByteBuffer>> shrink() {
return StreamSupport.stream(new Spliterators.AbstractSpliterator<Shrinkable<ByteBuffer>>(3, 0) { return StreamSupport.stream(new Spliterators.AbstractSpliterator<Shrinkable<ByteBuffer>>(3, 0) {
@ -160,7 +152,6 @@ public final class ArbitraryByteBuffer implements SizableArbitrary<ByteBuffer> {
}, false); }, false);
} }
@Nonnull
@Override @Override
public ShrinkingDistance distance() { public ShrinkingDistance distance() {
return ShrinkingDistance.of(value.remaining() - minSize); return ShrinkingDistance.of(value.remaining() - minSize);

View File

@ -16,7 +16,6 @@ import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.terminal.Terminal; import dan200.computercraft.core.terminal.Terminal;
import dan200.computercraft.test.core.computer.BasicEnvironment; import dan200.computercraft.test.core.computer.BasicEnvironment;
import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
public abstract class BasicApiEnvironment implements IAPIEnvironment { public abstract class BasicApiEnvironment implements IAPIEnvironment {
@ -32,25 +31,21 @@ public abstract class BasicApiEnvironment implements IAPIEnvironment {
return 0; return 0;
} }
@Nonnull
@Override @Override
public ComputerEnvironment getComputerEnvironment() { public ComputerEnvironment getComputerEnvironment() {
return environment; return environment;
} }
@Nonnull
@Override @Override
public GlobalEnvironment getGlobalEnvironment() { public GlobalEnvironment getGlobalEnvironment() {
return environment; return environment;
} }
@Nonnull
@Override @Override
public IWorkMonitor getMainThreadMonitor() { public IWorkMonitor getMainThreadMonitor() {
throw new IllegalStateException("Main thread monitor not available"); throw new IllegalStateException("Main thread monitor not available");
} }
@Nonnull
@Override @Override
public Terminal getTerminal() { public Terminal getTerminal() {
throw new IllegalStateException("Terminal not available"); throw new IllegalStateException("Terminal not available");
@ -128,10 +123,10 @@ public abstract class BasicApiEnvironment implements IAPIEnvironment {
} }
@Override @Override
public void observe(@Nonnull Metric.Event summary, long value) { public void observe(Metric.Event summary, long value) {
} }
@Override @Override
public void observe(@Nonnull Metric.Counter counter) { public void observe(Metric.Counter counter) {
} }
} }

View File

@ -16,7 +16,6 @@ import dan200.computercraft.core.metrics.Metric;
import dan200.computercraft.core.metrics.MetricsObserver; import dan200.computercraft.core.metrics.MetricsObserver;
import dan200.computercraft.test.core.filesystem.MemoryMount; import dan200.computercraft.test.core.filesystem.MemoryMount;
import javax.annotation.Nonnull;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -60,13 +59,11 @@ public class BasicEnvironment implements ComputerEnvironment, GlobalEnvironment,
return this; return this;
} }
@Nonnull
@Override @Override
public String getHostString() { public String getHostString() {
return "ComputerCraft 1.0 (Test environment)"; return "ComputerCraft 1.0 (Test environment)";
} }
@Nonnull
@Override @Override
public String getUserAgent() { public String getUserAgent() {
return "ComputerCraft/1.0"; return "ComputerCraft/1.0";

View File

@ -5,16 +5,17 @@
*/ */
package dan200.computercraft.test.core.filesystem; package dan200.computercraft.test.core.filesystem;
import dan200.computercraft.api.filesystem.FileOperationException;
import dan200.computercraft.api.filesystem.IWritableMount; import dan200.computercraft.api.filesystem.IWritableMount;
import dan200.computercraft.core.apis.handles.ArrayByteChannel; import dan200.computercraft.core.apis.handles.ArrayByteChannel;
import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.channels.Channels; import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel; import java.nio.channels.ReadableByteChannel;
import java.nio.channels.WritableByteChannel; import java.nio.channels.WritableByteChannel;
import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
/** /**
@ -30,7 +31,7 @@ public class MemoryMount implements IWritableMount {
@Override @Override
public void makeDirectory(@Nonnull String path) { public void makeDirectory(String path) {
var file = new File(path); var file = new File(path);
while (file != null) { while (file != null) {
directories.add(file.getPath()); directories.add(file.getPath());
@ -39,7 +40,7 @@ public class MemoryMount implements IWritableMount {
} }
@Override @Override
public void delete(@Nonnull String path) { public void delete(String path) {
if (files.containsKey(path)) { if (files.containsKey(path)) {
files.remove(path); files.remove(path);
} else { } else {
@ -55,9 +56,8 @@ public class MemoryMount implements IWritableMount {
} }
} }
@Nonnull
@Override @Override
public WritableByteChannel openForWrite(@Nonnull final String path) { public WritableByteChannel openForWrite(final String path) {
return Channels.newChannel(new ByteArrayOutputStream() { return Channels.newChannel(new ByteArrayOutputStream() {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
@ -67,9 +67,8 @@ public class MemoryMount implements IWritableMount {
}); });
} }
@Nonnull
@Override @Override
public WritableByteChannel openForAppend(@Nonnull final String path) throws IOException { public WritableByteChannel openForAppend(final String path) throws IOException {
var stream = new ByteArrayOutputStream() { var stream = new ByteArrayOutputStream() {
@Override @Override
public void close() throws IOException { public void close() throws IOException {
@ -90,35 +89,36 @@ public class MemoryMount implements IWritableMount {
} }
@Override @Override
public boolean exists(@Nonnull String path) { public boolean exists(String path) {
return files.containsKey(path) || directories.contains(path); return files.containsKey(path) || directories.contains(path);
} }
@Override @Override
public boolean isDirectory(@Nonnull String path) { public boolean isDirectory(String path) {
return directories.contains(path); return directories.contains(path);
} }
@Override @Override
public void list(@Nonnull String path, @Nonnull List<String> files) { public void list(String path, List<String> files) {
for (var file : this.files.keySet()) { for (var file : this.files.keySet()) {
if (file.startsWith(path)) files.add(file.substring(path.length() + 1)); if (file.startsWith(path)) files.add(file.substring(path.length() + 1));
} }
} }
@Override @Override
public long getSize(@Nonnull String path) { public long getSize(String path) {
throw new RuntimeException("Not implemented"); throw new RuntimeException("Not implemented");
} }
@Nonnull
@Override @Override
public ReadableByteChannel openForRead(@Nonnull String path) { public ReadableByteChannel openForRead(String path) throws FileOperationException {
return new ArrayByteChannel(files.get(path)); var file = files.get(path);
if (file == null) throw new FileOperationException(path, "File not found");
return new ArrayByteChannel(file);
} }
public MemoryMount addFile(String file, String contents) { public MemoryMount addFile(String file, String contents) {
files.put(file, contents.getBytes()); files.put(file, contents.getBytes(StandardCharsets.UTF_8));
return this; return this;
} }
} }

View File

@ -0,0 +1,19 @@
/*
* This file is part of ComputerCraft - http://www.computercraft.info
* Copyright Daniel Ratcliffe, 2011-2022. Do not distribute without permission.
* Send enquiries to dratcliffe@gmail.com
*/
/**
* Test helpers for ComputerCraft's core Lua runtime and APIs.
*/
@DefaultQualifier(value = NonNull.class, locations = {
TypeUseLocation.RETURN,
TypeUseLocation.PARAMETER,
TypeUseLocation.FIELD,
})
package dan200.computercraft.test.core;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.framework.qual.DefaultQualifier;
import org.checkerframework.framework.qual.TypeUseLocation;

View File

@ -5,7 +5,6 @@
*/ */
package dan200.computercraft.shared.computer.terminal; package dan200.computercraft.shared.computer.terminal;
import dan200.computercraft.core.util.IoUtil;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufInputStream; import io.netty.buffer.ByteBufInputStream;
import io.netty.buffer.ByteBufOutputStream; import io.netty.buffer.ByteBufOutputStream;
@ -119,14 +118,10 @@ public class TerminalState {
if (compressed != null) return compressed; if (compressed != null) return compressed;
var compressed = Unpooled.buffer(); var compressed = Unpooled.buffer();
OutputStream stream = null; try (OutputStream stream = new GZIPOutputStream(new ByteBufOutputStream(compressed))) {
try {
stream = new GZIPOutputStream(new ByteBufOutputStream(compressed));
stream.write(buffer.array(), buffer.arrayOffset(), buffer.readableBytes()); stream.write(buffer.array(), buffer.arrayOffset(), buffer.readableBytes());
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} finally {
IoUtil.closeQuietly(stream);
} }
return this.compressed = compressed; return this.compressed = compressed;
@ -135,9 +130,7 @@ public class TerminalState {
private static ByteBuf readCompressed(ByteBuf buf, int length, boolean compress) { private static ByteBuf readCompressed(ByteBuf buf, int length, boolean compress) {
if (compress) { if (compress) {
var buffer = Unpooled.buffer(); var buffer = Unpooled.buffer();
InputStream stream = null; try (InputStream stream = new GZIPInputStream(new ByteBufInputStream(buf, length))) {
try {
stream = new GZIPInputStream(new ByteBufInputStream(buf, length));
var swap = new byte[8192]; var swap = new byte[8192];
while (true) { while (true) {
var bytes = stream.read(swap); var bytes = stream.read(swap);
@ -146,8 +139,6 @@ public class TerminalState {
} }
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} finally {
IoUtil.closeQuietly(stream);
} }
return buffer; return buffer;
} else { } else {

View File

@ -9,9 +9,9 @@ import dan200.computercraft.api.lua.LuaException;
import dan200.computercraft.api.lua.LuaFunction; import dan200.computercraft.api.lua.LuaFunction;
import dan200.computercraft.api.peripheral.IComputerAccess; import dan200.computercraft.api.peripheral.IComputerAccess;
import dan200.computercraft.api.peripheral.IPeripheral; import dan200.computercraft.api.peripheral.IPeripheral;
import dan200.computercraft.core.util.StringUtil;
import dan200.computercraft.shared.MediaProviders; import dan200.computercraft.shared.MediaProviders;
import dan200.computercraft.shared.media.items.ItemDisk; import dan200.computercraft.shared.media.items.ItemDisk;
import dan200.computercraft.core.util.StringUtil;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -79,17 +79,16 @@ public class DiskDrivePeripheral implements IPeripheral {
* If the inserted disk's label can't be changed (for example, a record), * If the inserted disk's label can't be changed (for example, a record),
* an error will be thrown. * an error will be thrown.
* *
* @param labelA The new label of the disk, or {@code nil} to clear. * @param label The new label of the disk, or {@code nil} to clear.
* @throws LuaException If the disk's label can't be changed. * @throws LuaException If the disk's label can't be changed.
*/ */
@LuaFunction(mainThread = true) @LuaFunction(mainThread = true)
public final void setDiskLabel(Optional<String> labelA) throws LuaException { public final void setDiskLabel(Optional<String> label) throws LuaException {
var label = labelA.orElse(null);
var stack = diskDrive.getDiskStack(); var stack = diskDrive.getDiskStack();
var media = MediaProviders.get(stack); var media = MediaProviders.get(stack);
if (media == null) return; if (media == null) return;
if (!media.setLabel(stack, StringUtil.normaliseLabel(label))) { if (!media.setLabel(stack, label.map(StringUtil::normaliseLabel).orElse(null))) {
throw new LuaException("Disk label cannot be changed"); throw new LuaException("Disk label cannot be changed");
} }
diskDrive.setDiskStack(stack); diskDrive.setDiskStack(stack);

View File

@ -135,7 +135,7 @@ public class PrinterPeripheral implements IPeripheral {
@LuaFunction @LuaFunction
public final void setPageTitle(Optional<String> title) throws LuaException { public final void setPageTitle(Optional<String> title) throws LuaException {
getCurrentPage(); getCurrentPage();
printer.setPageTitle(StringUtil.normaliseLabel(title.orElse(""))); printer.setPageTitle(title.map(StringUtil::normaliseLabel).orElse(null));
} }
/** /**

View File

@ -73,13 +73,15 @@ object ManagedComputers : ILuaMachine.Factory {
apis.add(api) apis.add(api)
if (api is OSAPI) { if (api is OSAPI) {
val newMachine = if (api.computerID != 1) { val id = api.computerID
CobaltLuaMachine(environment) val label = api.computerLabel
} else if (api.computerLabel != null) { val newMachine = when {
KotlinMachine(environment, api.computerLabel[0] as String) id != 1 -> CobaltLuaMachine(environment)
} else { label != null && label[0] != null -> KotlinMachine(environment, label[0] as String)
LOGGER.error("Kotlin Lua machine must have a label") else -> {
CobaltLuaMachine(environment) LOGGER.error("Kotlin Lua machine must have a label")
CobaltLuaMachine(environment)
}
} }
this.delegate = newMachine this.delegate = newMachine
@ -106,7 +108,8 @@ object ManagedComputers : ILuaMachine.Factory {
} }
} }
private class KotlinMachine(environment: MachineEnvironment, private val label: String) : KotlinLuaMachine(environment) { private class KotlinMachine(environment: MachineEnvironment, private val label: String) :
KotlinLuaMachine(environment) {
override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? = computers[label]?.poll() override fun getTask(): (suspend KotlinLuaMachine.() -> Unit)? = computers[label]?.poll()
} }