-
Notifications
You must be signed in to change notification settings - Fork 168
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
feat: Add signal commands #20876
base: main
Are you sure you want to change the base?
feat: Add signal commands #20876
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
/* | ||
* Copyright 2000-2025 Vaadin Ltd. | ||
* | ||
* 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 | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* 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 com.vaadin.signals; | ||
|
||
import java.math.BigInteger; | ||
import java.util.Base64; | ||
import java.util.Base64.Encoder; | ||
import java.util.concurrent.ThreadLocalRandom; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
/** | ||
* Generated identifier for signals and other related resources. | ||
* <p> | ||
* The id is a random 64-bit number to be more compact than a full 128-bit UUID | ||
* or such. The ids don't need to be globally unique but only unique within a | ||
* smaller context so the risk of collisions is still negligible. The value is | ||
* JSON serialized as a base64-encoded string with a special case, | ||
* <code>""</code>, for the frequently used special 0 id. The ids are comparable | ||
* to facilitate consistent ordering to avoid deadlocks in certain situations. | ||
* | ||
* @param value | ||
* the id value as a 64-bit integer | ||
*/ | ||
public record Id(long value) implements Comparable<Id> { | ||
/** | ||
* Default or initial id in various contexts. Always used for the root node | ||
* in a signal hierarchy. The zero id is frequently used and has a custom | ||
* compact JSON representation. | ||
*/ | ||
public static final Id ZERO = new Id(0); | ||
|
||
/** | ||
* Special id value reserved for internal bookkeeping. | ||
*/ | ||
public static final Id MAX = new Id(Long.MAX_VALUE); | ||
|
||
/* | ||
* Padding refers to the trailing = characters that are only necessary when | ||
* base64 values are concatenated together | ||
*/ | ||
private static final Encoder base64Encoder = Base64.getEncoder() | ||
.withoutPadding(); | ||
|
||
/** | ||
* Creates a random id. Randomness is only needed to reduce the risk of | ||
* collisions but there's no security impact from being able to guess random | ||
* ids. | ||
* | ||
* @return a random id, not <code>null</code> | ||
*/ | ||
public static Id random() { | ||
var random = ThreadLocalRandom.current(); | ||
|
||
long value; | ||
do { | ||
value = random.nextLong(); | ||
} while (value == 0 || value == Long.MAX_VALUE); | ||
|
||
return new Id(value); | ||
} | ||
|
||
/** | ||
* Parses the given base64 string as an id. As a special case, the empty | ||
* string is parsed as {@link #ZERO}. | ||
* | ||
* @param base64 | ||
* the base64 string to parse, not <code>null</code> | ||
* @return the parsed id. | ||
*/ | ||
@JsonCreator | ||
public static Id parse(String base64) { | ||
if (base64.equals("")) { | ||
return ZERO; | ||
} | ||
byte[] bytes = Base64.getDecoder().decode(base64); | ||
return new Id(new BigInteger(bytes).longValue()); | ||
} | ||
|
||
/** | ||
* Returns this id value as a base64 string. | ||
* | ||
* @return the base64 string representing this id | ||
*/ | ||
@JsonValue | ||
public final String asBase64() { | ||
if (value == 0) { | ||
return ""; | ||
} | ||
byte[] bytes = BigInteger.valueOf(value).toByteArray(); | ||
return base64Encoder.encodeToString(bytes); | ||
} | ||
|
||
@Override | ||
public int compareTo(Id other) { | ||
return Long.compare(value, other.value); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
/* | ||
* Copyright 2000-2025 Vaadin Ltd. | ||
* | ||
* 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 | ||
* | ||
* http://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* 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 com.vaadin.signals; | ||
|
||
/** | ||
* The rest of this class will be implemented later. | ||
*/ | ||
public class ListSignal { | ||
|
||
/** | ||
* A list insertion position before and/or after the referenced entries. If | ||
* both entries are defined, then this position represents an exact match | ||
* that is valid only if the two entries are adjacent. If only one is | ||
* defined, then the position is relative to only that position. A position | ||
* with neither reference is not valid for inserts but it is valid to test a | ||
* parent-child relationship regardless of the child position. | ||
* {@link Id#ZERO} represents the edge of the list, i.e. the first or the | ||
* last position. | ||
* | ||
* @param after | ||
* id of the node to insert immediately after, nor | ||
* <code>null</code> to not define a constraint | ||
* @param before | ||
* id of the node to insert immediately before, nor | ||
* <code>null</code> to not define a constraint | ||
*/ | ||
public record ListPosition(Id after, Id before) { | ||
/** | ||
* Gets the insertion position that corresponds to the beginning of the | ||
* list. | ||
* | ||
* @return a list position for the beginning of the list, not | ||
* <code>null</code> | ||
*/ | ||
public static ListPosition first() { | ||
// After edge | ||
return new ListPosition(Id.ZERO, null); | ||
} | ||
|
||
/** | ||
* Gets the insertion position that corresponds to the end of the list. | ||
* | ||
* @return a list position for the end of the list, not | ||
* <code>null</code> | ||
*/ | ||
public static ListPosition last() { | ||
// Before edge | ||
return new ListPosition(null, Id.ZERO); | ||
} | ||
Comment on lines
+41
to
+62
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure if I easily understand this part: What is the role of the If so, calling Probably, something in the implementation is being optimized by this(?), but this representation seems a bit confusing. |
||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,116 @@ | ||||||||||||||
/* | ||||||||||||||
* Copyright 2000-2025 Vaadin Ltd. | ||||||||||||||
* | ||||||||||||||
* 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 | ||||||||||||||
* | ||||||||||||||
* http://www.apache.org/licenses/LICENSE-2.0 | ||||||||||||||
* | ||||||||||||||
* 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 com.vaadin.signals; | ||||||||||||||
|
||||||||||||||
import java.util.List; | ||||||||||||||
import java.util.Map; | ||||||||||||||
import java.util.Objects; | ||||||||||||||
|
||||||||||||||
import com.fasterxml.jackson.databind.JsonNode; | ||||||||||||||
import com.fasterxml.jackson.databind.node.NullNode; | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* A node in a signal tree. Each node represents as signal entry. Nodes are | ||||||||||||||
* immutable and referenced by an {@link Id} rather than directly referencing | ||||||||||||||
* the node instance. The node is either a {@link Data} node carrying actual | ||||||||||||||
* signal data or an {@link Alias} node that allows multiple signal ids to | ||||||||||||||
* reference the same data. | ||||||||||||||
*/ | ||||||||||||||
public sealed interface Node { | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* An empty data node without parent, scope owner, value or children and the | ||||||||||||||
* initial last update id. | ||||||||||||||
*/ | ||||||||||||||
public static final Data EMPTY = new Data(null, Id.ZERO, null, null, | ||||||||||||||
List.of(), Map.of()); | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* A node alias. An alias node allows multiple signal ids to reference the | ||||||||||||||
* same data. | ||||||||||||||
* | ||||||||||||||
* @param target | ||||||||||||||
* the id of the alias target, not <code>null</code> | ||||||||||||||
*/ | ||||||||||||||
public record Alias(Id target) implements Node { | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/** | ||||||||||||||
* A data node. The node represents the actual data behind a signal | ||||||||||||||
* instance. | ||||||||||||||
* | ||||||||||||||
* @param parent | ||||||||||||||
* the parent id, or <code>null</code> for the root node | ||||||||||||||
* @param lastUpdate | ||||||||||||||
* a unique id for the update that last updated this data node, | ||||||||||||||
* not <code>null</code> | ||||||||||||||
* @param scopeOwner | ||||||||||||||
* the id of the external owner of this node, or | ||||||||||||||
* <code>null</code> if the node has no owner. Any node with an | ||||||||||||||
* owner is deleted if the owner is disconnected. | ||||||||||||||
* @param value | ||||||||||||||
* the JSON value of this node, or <code>null</code> if there is | ||||||||||||||
* no value | ||||||||||||||
* @param listChildren | ||||||||||||||
* a list of child ids, or the an list if the node has no list | ||||||||||||||
* children | ||||||||||||||
Comment on lines
+67
to
+69
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seems the sentence misses an
Suggested change
|
||||||||||||||
* @param mapChildren | ||||||||||||||
* a sequenced map from key to child id, or an empty map if the | ||||||||||||||
* node has no map children | ||||||||||||||
*/ | ||||||||||||||
public record Data(Id parent, Id lastUpdate, Id scopeOwner, JsonNode value, | ||||||||||||||
List<Id> listChildren, | ||||||||||||||
Map<String, Id> mapChildren) implements Node { | ||||||||||||||
/** | ||||||||||||||
* Creates a new data node. | ||||||||||||||
* | ||||||||||||||
* @param parent | ||||||||||||||
* the parent id, or <code>null</code> for the root node | ||||||||||||||
* @param lastUpdate | ||||||||||||||
* a unique id for the update that last updated this data | ||||||||||||||
* node, not <code>null</code> | ||||||||||||||
* @param scopeOwner | ||||||||||||||
* the id of the external owner of this node, or | ||||||||||||||
* <code>null</code> if the node has no owner. Any node with | ||||||||||||||
* an owner is deleted if the owner is disconnected. | ||||||||||||||
* @param value | ||||||||||||||
* the JSON value of this node, or <code>null</code> if there | ||||||||||||||
* is no value | ||||||||||||||
* @param listChildren | ||||||||||||||
* a list of child ids, or the an list if the node has no | ||||||||||||||
* list children | ||||||||||||||
Comment on lines
+92
to
+94
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. Though, I prefer to get rid of this almost duplicate block. |
||||||||||||||
* @param mapChildren | ||||||||||||||
* a sequenced map from key to child id, or an empty map if | ||||||||||||||
* the node has no map children | ||||||||||||||
*/ | ||||||||||||||
Comment on lines
+77
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we need this in case of a compact constructor? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It has no practical purpose other than to stop the checker from complaining about missing |
||||||||||||||
/* | ||||||||||||||
* There's no point in copying the record components here since they are | ||||||||||||||
* already documented on the top level, but the Javadoc checker insist | ||||||||||||||
* that this constructor also has full documentation... | ||||||||||||||
*/ | ||||||||||||||
public Data { | ||||||||||||||
Objects.requireNonNull(lastUpdate); | ||||||||||||||
|
||||||||||||||
/* | ||||||||||||||
* Avoid accidentally making a distinction between the two different | ||||||||||||||
* nulls that will look the same after JSON deserialization | ||||||||||||||
*/ | ||||||||||||||
if (value instanceof NullNode) { | ||||||||||||||
value = null; | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
} | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't these two be
or
instead: