All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.engine.index.shared.CommitDescriptor Maven / Gradle / Ivy

/*
 * Copyright (c) CQSE GmbH
 *
 * 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 org.conqat.engine.index.shared;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Objects;

import org.checkerframework.checker.nullness.qual.NonNull;
import org.conqat.engine.commons.util.JsonUtils;
import org.conqat.engine.core.core.ConQATException;
import org.conqat.lib.commons.assertion.CCSMAssert;
import org.conqat.lib.commons.io.ByteArrayUtils;
import org.conqat.lib.commons.net.UrlUtils;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.test.IndexValueClass;

import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;

/**
 * Immutable class describing a single commit by its branch name and a timestamp. The timestamp must
 * be unique within the branch. They are comparable by timestamp, where equal timestamps are
 * resolved alphabetically by the branch name.
 * 

* This class is used as DTO during communication with IDE clients via * {@link com.teamscale.ide.commons.client.IIdeServiceClient}, special care has to be taken when * changing its signature! */ @JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type", defaultImpl = CommitDescriptor.class) @JsonSubTypes({ @JsonSubTypes.Type(value = CommitDescriptor.class, name = "simple"), @JsonSubTypes.Type(value = ParentedCommitDescriptor.class, name = "parented") }) @IndexValueClass(containedInBackup = true) public class CommitDescriptor implements Serializable, Comparable { /** * The separator used between branch name and timestamp in * {@link #toBranchTimestampKeyWithSeparator()}. */ public static final byte[] SEPARATOR = { 1, 2, 1 }; /** * Name of the branch if no branch is specified. * * @see org.conqat.engine.persistence.store.hist.HistoryAccessOption#NO_BRANCH_NAME */ // HistoryAccessOption is not accessible in all project where this class is // used as external source file @SuppressWarnings("javadoc") private static final String NO_BRANCH_NAME = "##no-branch##"; /** Version for serialization. */ private static final long serialVersionUID = 1L; /** Special value to indicate the HEAD of a branch. */ public static final String HEAD_TIMESTAMP = "HEAD"; /** * A comparator for comparison by timestamps. Null commits are ordered to the front. *

* If there are commits with identical timestamps, we order them by comparing the branch names to * guarantee a stable order. This should only happen in special cases since teamscale ensures that * no timestamp is given to two commits. */ public static final Comparator BY_TIMESTAMP_COMPARATOR = Comparator.nullsFirst( Comparator.comparingLong(CommitDescriptor::getTimestamp).thenComparing(CommitDescriptor::getBranchName)); /** The name of the JSON property name for {@link #branchName}. */ protected static final String BRANCH_NAME_PROPERTY = "branchName"; /** The name of the JSON property name for {@link #timestamp}. */ protected static final String TIMESTAMP_PROPERTY = "timestamp"; /** The name of the branch. */ @JsonProperty(BRANCH_NAME_PROPERTY) private final @NonNull String branchName; /** The timestamp on the branch. */ @JsonProperty(TIMESTAMP_PROPERTY) private final long timestamp; /** * Constructor.
* use {@link CommitDescriptor#createUnbranchedDescriptor(long)} to create a unbranched commit * descriptor. */ @JsonCreator public CommitDescriptor(@JsonProperty(BRANCH_NAME_PROPERTY) @NonNull String branchName, @JsonProperty(TIMESTAMP_PROPERTY) long timestamp) { CCSMAssert.isTrue(timestamp >= 0, () -> "Timestamp must be >= 0 but is " + timestamp); CCSMAssert.isNotNull(branchName); this.branchName = branchName; this.timestamp = timestamp; } public CommitDescriptor(CommitDescriptor other) { this(other.branchName, other.timestamp); } /** * Create a {@link CommitDescriptor} without branch specification.
* Should be used with caution. */ public static CommitDescriptor createUnbranchedDescriptor(long timestamp) { return new CommitDescriptor(NO_BRANCH_NAME, timestamp); } /** * Create a copy of this commit descriptor with timestamp - 1. *

* Using this CommitDescriptor to store a new commit might overwrite an existing commit if the * timestamp is already used. */ public CommitDescriptor cloneWithDecrementedTimestamp() { return new CommitDescriptor(branchName, timestamp - 1); } /** * Create a copy of this commit descriptor with timestamp + 1. *

* Using this CommitDescriptor to store a new commit might overwrite an existing commit if the * timestamp is already used. */ public CommitDescriptor cloneWithIncrementedTimestamp() { return new CommitDescriptor(branchName, timestamp + 1); } /** * @see #branchName */ public @NonNull String getBranchName() { return branchName; } /** Return true if no branch is specified. */ public boolean isUnbranched() { return NO_BRANCH_NAME.equals(branchName); } /** Returns whether this commit is a head timestamp */ public boolean isHeadCommit() { return timestamp == Long.MAX_VALUE; } /** * @see #timestamp */ public long getTimestamp() { return timestamp; } /** {@inheritDoc} */ @Override public boolean equals(Object obj) { if (obj instanceof CommitDescriptor) { CommitDescriptor other = (CommitDescriptor) obj; return other.timestamp == timestamp && Objects.equals(branchName, other.branchName); } return false; } /** {@inheritDoc} */ @Override public int hashCode() { return Objects.hashCode(branchName) ^ Long.hashCode(timestamp); } /** {@inheritDoc} */ @Override public int compareTo(CommitDescriptor other) { if (timestamp == other.timestamp) { return branchName.compareTo(other.branchName); } return Long.compare(timestamp, other.timestamp); } /** * Returns the maximum of the two given commit descriptors based on * {@link #compareTo(CommitDescriptor)}. */ public static CommitDescriptor max(CommitDescriptor c1, CommitDescriptor c2) { return c1.compareTo(c2) > 0 ? c1 : c2; } /** Converts this to a JSON representation. */ public String toJson() { return JsonUtils.serializeToJSON(this); } /** Reads a {@link CommitDescriptor} from a JSON string. */ public static CommitDescriptor fromJson(String json) throws ConQATException { return JsonUtils.deserializeFromJsonWithNullCheck(json, CommitDescriptor.class); } /** Creates commit descriptor representing the head of the provided branch name */ public static CommitDescriptor createHead(String branchName) { return new CommitDescriptor(branchName, Long.MAX_VALUE); } /** branchName + "@" + timestamp */ @Override public String toString() { return branchName + "@" + timestamp; } /** Returns a timestamp+branchName key/byte[] representation. */ public byte[] toTimestampBranchKey() { return ByteArrayUtils.concat(ByteArrayUtils.longToByteArray(getTimestamp()), StringUtils.stringToBytes(getBranchName())); } /** Parses a commit descriptor from its {@link #toString} representation. */ public static CommitDescriptor fromStringRepresentation(String representation) { int separatorPosition = representation.lastIndexOf("@"); CCSMAssert.isTrue(separatorPosition >= 0, () -> "Invalid string representation of commit descriptor: " + representation); String branch = representation.substring(0, separatorPosition); String timestamp = representation.substring(separatorPosition + 1); return new CommitDescriptor(branch, Long.parseLong(timestamp)); } /** Parses a timestamp+branchName key/byte[] representation. */ public static CommitDescriptor fromTimestampBranchKey(byte[] key) { long timestamp = ByteArrayUtils.byteArrayToLong(Arrays.copyOf(key, Long.BYTES)); String branchName = StringUtils.bytesToString(Arrays.copyOfRange(key, Long.BYTES, key.length)); return new CommitDescriptor(branchName, timestamp); } /** * Returns a branchName+timestamp key/byte[] representation. NOTE: This is *DANGEROUS* since keys * generated with this function may cause unwanted branch names (prefixes of wanted branch names) to * be returned from store scans (TS-16367). To protect against this, use * {@link #toBranchTimestampKeyWithSeparator()}. */ public byte[] toBranchTimestampKey() { return ByteArrayUtils.concat(StringUtils.stringToBytes(getBranchName()), ByteArrayUtils.longToByteArray(getTimestamp())); } /** * Returns a branchName+timestamp key/byte[] representation with {@link #SEPARATOR} in between. */ public byte[] toBranchTimestampKeyWithSeparator() { return ByteArrayUtils.concat(StringUtils.stringToBytes(getBranchName()), SEPARATOR, ByteArrayUtils.longToByteArray(getTimestamp())); } /** Parses a branch+timestamp key/byte[] representation with separator. */ public static CommitDescriptor fromBranchTimestampKeyWithSeparator(byte[] key) { String branchName = StringUtils.bytesToString(Arrays.copyOf(key, key.length - Long.BYTES - SEPARATOR.length)); long timestamp = ByteArrayUtils.byteArrayToLong(Arrays.copyOfRange(key, key.length - Long.BYTES, key.length)); return new CommitDescriptor(branchName, timestamp); } /** Parses a branch+timestamp key/byte[] representation. */ public static CommitDescriptor fromBranchTimestampKey(byte[] key) { String branchName = StringUtils.bytesToString(Arrays.copyOf(key, key.length - Long.BYTES)); long timestamp = ByteArrayUtils.byteArrayToLong(Arrays.copyOfRange(key, key.length - Long.BYTES, key.length)); return new CommitDescriptor(branchName, timestamp); } /** Creates a commit descriptor for the latest revision on a branch */ public static CommitDescriptor latestOnBranch(String branchName) { return new CommitDescriptor(branchName, Long.MAX_VALUE); } /** * Returns a format used for service calls ("branch:timestamp", or "timestamp" if no branch is * specified). * * @see #toEncodedPathParam() * @see #toEncodedQueryParam() */ public String toServiceCallFormat() { String result = branchName + ":"; if (NO_BRANCH_NAME.equals(branchName)) { result = StringUtils.EMPTY_STRING; } if (timestamp == Long.MAX_VALUE) { return result + HEAD_TIMESTAMP; } return result + timestamp; } /** * Parses a commit descriptor from its {@link #toServiceCallFormat()} representation. */ public static CommitDescriptor fromServiceCallFormat(String representation) { int separatorPosition = representation.lastIndexOf(":"); String branchName; if (separatorPosition > 0) { branchName = representation.substring(0, separatorPosition); } else { branchName = NO_BRANCH_NAME; } String timestampPart = representation.substring(separatorPosition + 1); long timestamp; if (HEAD_TIMESTAMP.equals(timestampPart)) { timestamp = Long.MAX_VALUE; } else { timestamp = Long.parseLong(timestampPart); } return new CommitDescriptor(branchName, timestamp); } /** * Returns a url-encoded format used for service calls ("branch:timestamp", or "timestamp" if no * branch is specified). The format is suitable for calls that include the commit as query parameter * or in the arguments of the navigation hash. */ public String toEncodedQueryParam() { return UrlUtils.encodeQueryParameter(toServiceCallFormat()); } /** * Returns a url-encoded format used for service calls ("branch:timestamp", or "timestamp" if no * branch is specified). The format is suitable for calls that include the commit as path parameter. */ public String toEncodedPathParam() { return UrlUtils.encodePathSegment(toServiceCallFormat()); } /** Returns a {@link UnresolvedCommitDescriptor} for this commit. */ public UnresolvedCommitDescriptor toUnresolvedCommitDescriptor() { return new UnresolvedCommitDescriptor(this.getBranchName(), this.getTimestamp()); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy