8000 JavaScript RPC changes by knutwannheden · Pull Request #5636 · openrewrite/rewrite · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content

JavaScript RPC changes #5636

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 21 commits into from
Jun 25, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 28 additions & 11 deletions rewrite-core/src/main/java/org/openrewrite/Checksum.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
import org.jspecify.annotations.Nullable;
import org.openrewrite.ipc.http.HttpSender;
import org.openrewrite.remote.Remote;
import org.openrewrite.rpc.RpcCodec;
import org.openrewrite.rpc.RpcReceiveQueue;
import org.openrewrite.rpc.RpcSendQueue;

import java.io.IOException;
import java.io.InputStream;
Expand All @@ -30,7 +33,7 @@
import java.security.NoSuchAlgorithmException;

@Value
public class Checksum {
public class Checksum implements RpcCodec<Checksum> {
private static final byte[] HEX_ARRAY = "0123456789abcdef".getBytes(StandardCharsets.US_ASCII);

String algorithm;
Expand All @@ -47,7 +50,7 @@ public String getHexValue() {
}

public static Checksum fromHex(String algorithm, String hex) {
if(hex.length() % 2 != 0) {
if (hex.length() % 2 != 0) {
throw new IllegalArgumentException("Hex string must contain a set of hex pairs (length divisible by 2).");
}

Expand All @@ -66,20 +69,20 @@ public static Checksum fromHex(String algorithm, String hex) {
* checksum. The ending of the URL is used to determine the algorithm that should be used.
*/
public static Checksum fromUri(HttpSender httpSender, URI uri) {
String uriStr = uri.toString();
if(uriStr.endsWith(".sha256")) {
return fromUri(httpSender, uri, "SHA-256");
} else if(uriStr.endsWith(".md5")) {
return fromUri(httpSender, uri, "MD5");
}
throw new IllegalArgumentException("Unable to automatically determine checksum type from URI: " + uriStr);
String uriStr = uri.toString();
if (uriStr.endsWith(".sha256")) {
return fromUri(httpSender, uri, "SHA-256");
} else if (uriStr.endsWith(".md5")) {
return fromUri(httpSender, uri, "MD5");
}
throw new IllegalArgumentException("Unable to automatically determine checksum type from URI: " + uriStr);
}

public static Checksum fromUri(HttpSender httpSender, URI uri, String algorithm) {
HttpSender.Request request = HttpSender.Request.build(uri.toString(), httpSender)
.withMethod(HttpSender.Method.GET)
.build();
try(HttpSender.Response response = httpSender.send(request)) {
try (HttpSender.Response response = httpSender.send(request)) {
String hexString = new String(response.getBodyAsBytes(), StandardCharsets.UTF_8);
return Checksum.fromHex(algorithm, hexString);
}
Expand All @@ -94,7 +97,7 @@ public static SourceFile sha256(SourceFile sourceFile, ExecutionContext ctx) {
}

public static SourceFile checksum(SourceFile sourceFile, @Nullable String algorithm, ExecutionContext ctx) {
if(algorithm == null) {
if (algorithm == null) {
return sourceFile;
}

Expand All @@ -118,4 +121,18 @@ public static SourceFile checksum(SourceFile sourceFile, @Nullable String algori
throw new IllegalArgumentException(e);
}
}

@Override
public void rpcSend(Checksum after, RpcSendQueue q) {
q.getAndSend(after, Checksum::getAlgorithm);
q.getAndSend(after, Checksum::getValue);
}

@Override
public Checksum rpcReceive(Checksum before, RpcReceiveQueue q) {
return new Checksum(
q.receiveAndGet(before.getAlgorithm(), String::toString),
q.receive(before.getValue())
);
}
}
28 changes: 27 additions & 1 deletion rewrite-core/src/main/java/org/openrewrite/FileAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@
import lombok.Value;
import lombok.With;
import org.jspecify.annotations.Nullable;
import org.openrewrite.rpc.RpcCodec;
import org.openrewrite.rpc.RpcReceiveQueue;
import org.openrewrite.rpc.RpcSendQueue;

import java.io.IOException;
import java.nio.file.Files;
Expand All @@ -28,7 +31,7 @@

@Value
@With
public class FileAttributes {
public class FileAttributes implements RpcCodec<FileAttributes> {
@Nullable
ZonedDateTime creationTime;

Expand Down Expand Up @@ -61,4 +64,27 @@ public class FileAttributes {
}
return null;
}

@Override
public void rpcSend(FileAttributes after, RpcSendQueue q) {
q.getAndSend(after, FileAttributes::getCreationTime);
q.getAndSend(after, FileAttributes::getLastModifiedTime);
q.getAndSend(after, FileAttributes::getLastAccessTime);
q.getAndSend(after, FileAttributes::isReadable);
q.getAndSend(after, FileAttributes::isWritable);
q.getAndSend(after, FileAttributes::isExecutable);
q.getAndSend(after, FileAttributes::getSize);
}

@Override
public FileAttributes rpcReceive(FileAttributes before, RpcReceiveQueue q) {
return before
.withCreationTime(q.receive(before.getCreationTime()))
.withLastModifiedTime(q.receive(before.getLastModifiedTime()))
.withLastAccessTime(q.receive(before.getLastAccessTime()))
.withReadable(q.receive(before.isReadable()))
.withWritable(q.receive(before.isWritable()))
.withExecutable(q.receive(before.isExecutable()))
.withSize(q.receive(before.getSize()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
/*
* Copyright 2025 the original author or authors.
* <p>
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* <p>
* https://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openrewrite.internal;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.experimental.FieldDefaults;
import org.jspecify.annotations.Nullable;

import java.util.function.Function;
import java.util.function.Supplier;

/**
* ThreadLocal wrapper for managed resources that need lifecycle management.
* <p>
* Designed for AutoCloseable resources like database connections, file handles,
* network connections, etc. that require proper cleanup.
* <p>
* Typical usage:
* <pre>
* // At entry point (e.g., request handler)
* try (var managed = DatabaseConnection.current().requireOrCreate(() -> new DatabaseConnection(...))) {
* // Application code can use require() throughout call chain
* processRequest();
* } // Automatic cleanup of both ThreadLocal and resource
*
* // In application code
* public void processRequest() {
* DatabaseConnection conn = DatabaseConnection.current().require();
* conn.executeQuery("SELECT ...");
* }
* </pre>
*/
public class ManagedThreadLocal<T extends AutoCloseable> {
private final ThreadLocal<@Nullable T> threadLocal = new ThreadLocal<>();

/**
* Gets the current resource, throwing if not present.
* <p>
* This is the primary method for accessing the resource throughout your call chain.
* It fails fast with a clear error if no resource has been established.
*
* @return the current resource
* @throws IllegalStateException if no resource is present in the current thread
*/
public T require() {
T value = threadLocal.get();
if (value == null) {
throw new IllegalStateException("No managed resource found in current thread context");
}
return value;
}

/**
* Gets or creates a managed resource with automatic cleanup.
* <p>
* If a resource already exists, returns it in a scope with no-op cleanup.
* If no resource exists, creates one using the factory and sets up automatic cleanup
* of both the ThreadLocal state and the resource itself when the returned
* Scope is closed.
* <p>
* Use this at entry points where you want to establish the resource context.
*
* @param factory supplier to create the resource if none exists
* @return a Scope that provides access to the resource and handles cleanup
*/
public Scope<T> requireOrCreate(Supplier<T> factory) {
T existing = threadLocal.get();
if (existing != null) {
// Return existing value with no-op cleanup
return new Scope<>(existing, () -> {
});
}

T newValue = factory.get();
threadLocal.set(newValue);

return new Scope<>(newValue, () -> {
try {
threadLocal.remove();
} finally {
newValue.close(); // Close the resource
}
});
}

/**
* Temporarily replace the resource, returning an AutoCloseable that restores the original.
* <p>
* Use this when you need to temporarily use a different resource within a scope.
* The original resource is automatically restored when the returned AutoCloseable is closed.
* <p>
* Note: This does NOT close the temporary resource - you're responsible for its lifecycle.
*
* @param value the temporary resource to use
* @return a Scope that restores the previous resource when closed
*/
public Scope<T> using(T value) {
T previousValue = threadLocal.get();
threadLocal.set(value);

return new Scope<>(value, () -> {
if (previousValue != null) {
threadLocal.set(previousValue);
} else {
threadLocal.remove();
}
});
}

/**
* Provides access to the underlying ThreadLocal for advanced use cases.
* <p>
* Use with caution - direct ThreadLocal manipulation bypasses the managed
* resource lifecycle. Prefer the managed methods (require, requireOrCreate, using)
* for typical usage.
*
* @return the underlying ThreadLocal instance
*/
public ThreadLocal<@Nullable T> asThreadLocal() {
return threadLocal;
}

/**
* Checks if a resource is present in the current thread.
*
* @return true if a resource is present, false otherwise
*/
public boolean isPresent() {
return threadLocal.get() != null;
}

/**
* A scoped resource that provides both the resource value and automatic cleanup.
* <p>
* This is returned by requireOrCreate() and using(). It handles both ThreadLocal
* restoration and resource cleanup when the scope is closed.
*/
@RequiredArgsConstructor
@FieldDefaults(level = AccessLevel.PRIVATE)
public static class Scope<T extends AutoCloseable> implements AutoCloseable {
final T resource;
final AutoCloseable cleanup;

/**
* Applies a function to the scoped resource and returns the result.
* <p>
* This provides functional access to the resource without exposing it directly.
* The function is called with the current resource as its argument.
* <p>
* Example usage:
* <pre>
* try (var scope = DatabaseConnection.current().requireOrCreate(...)) {
* String result = scope.map(conn -> {
* conn.executeQuery("SELECT ...");
* return conn.getLastResult();
* });
* }
* </pre>
*
* @param <R> the type of the result
* @param mapper function to apply to the scoped resource
* @return the result of applying the function to the resource
*/
public <R> R map(Function<T, R> mapper) {
return mapper.apply(resource);
}

/**
* Performs cleanup of both ThreadLocal state and the resource.
* <p>
* This is called automatically when used with try-with-resources.
*/
@Override
public void close() {
try {
cleanup.close();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.openrewrite.marker.ci.BuildEnvironment;
import org.openrewrite.marker.ci.IncompleteGitConfigException;
import org.openrewrite.marker.ci.JenkinsBuildEnvironment;
import org.openrewrite.rpc.Reference;

import java.io.File;
import java.io.IOException;
Expand All @@ -49,7 +50,7 @@
@Value
@AllArgsConstructor(access = AccessLevel.PACKAGE) // required for @With and tests
@With
public class GitProvenance implements Marker {
public class GitProvenance extends Reference implements Marker {
UUID id;

/**
Expand Down Expand Up @@ -98,6 +99,11 @@ public GitProvenance(UUID id,
this.committers = committers;
}

@Override
public Object getValue() {
return this;
}

public @Nullable GitRemote getGitRemote() {
if (gitRemote == null && origin != null) {
gitRemote = new GitRemote.Parser().parse(origin);
Expand Down
Loading
0