/*
* Copyright 2025 the original author or authors.
*
* Licensed under the Moderne Source Available License (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://docs.moderne.io/licensing/moderne-source-available-license
*
* 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.
*/
const REFERENCE_KEY = Symbol.for("org.openrewrite.rpc.Reference");
export interface Reference {
[REFERENCE_KEY]: true;
}
export function asRef(obj: T): T extends undefined ? undefined : T & Reference {
if (obj === undefined) {
return undefined as any;
} else if (isRef(obj)) {
return obj as any;
}
try {
// Spread would create a new object. This can be used multiple times on the
// same object without changing the reference.
(obj as any)[REFERENCE_KEY] = true;
return obj as any;
} catch {
// The object is frozen/sealed/non-extensible. Clone to a new object.
const cloned = {...obj as any};
cloned[REFERENCE_KEY] = true;
return cloned;
}
}
export function isRef(obj: any): obj is Reference {
return obj !== undefined && obj !== null && obj[REFERENCE_KEY] === true;
}
export class ReferenceMap {
private refs = new WeakMap();
private refsById = new Map();
private refCount = 0;
has(obj: Reference): boolean {
return this.refs.has(obj);
}
get(obj: Reference): number | undefined {
return this.refs.get(obj);
}
getByRefId(refId: number): Reference | undefined {
return this.refsById.get(refId);
}
create(obj: Reference): number {
const ref = this.refCount++;
this.refs.set(obj, ref);
this.refsById.set(ref, obj);
return ref;
}
clear() {
this.refs = new WeakMap();
this.refsById = new Map();
this.refCount = 0;
}
deleteByRefId(refId: number) {
const obj = this.refsById.get(refId);
if (obj) {
this.refs.delete(obj);
this.refsById.delete(refId);
}
}
set(ref: Reference, refId: number) {
this.refs.set(ref, refId);
this.refsById.set(refId, ref);
}
snapshot(): number {
return this.refCount;
}
get size(): number {
return this.refsById.size;
}
rollbackTo(savedRefCount: number): void {
for (let i = savedRefCount; i < this.refCount; i++) {
const obj = this.refsById.get(i);
if (obj) {
this.refs.delete(obj);
this.refsById.delete(i);
}
}
this.refCount = savedRefCount;
}
}