com.almworks.jira.structure.api.item.ItemIdentity Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.item;
import com.almworks.jira.structure.api.util.Limits;
import com.atlassian.annotations.PublicApi;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.annotation.concurrent.Immutable;
import java.io.Serializable;
import java.text.ParseException;
/**
* {@code ItemIdentity} represents an item, a core concept in Structure's architecture.
*
* An item is an abstract notion that generalizes everything that can be put into a structure. Issues, projects,
* users and other JIRA objects — all can be represented as items. Structures themselves may be represented
* as items. Structure can be extended and new types of items can be added to it — for example, Structure.Pages
* extension adds "Confluence page" item type.
*
* Because of such diversity, there's no single class that represents an item in the low-level API. Instead,
* there's {@code ItemIdentity} which represents the only single property that each item must have — its
* unique ID.
*
* Anatomy of the item ID
*
* Each {@code ItemIdentity} is a pair of item type and item id within that
* type.
*
* Item type is nothing else but the complete module key of the {@code } module, which
* supports this item type. Main item types are listed in {@link CoreItemTypes}.
*
* The actual item ID can be either {@code long} or {@code String}. Numeric item IDs are used whenever possible,
* as that allows Structure to save consumed memory and speed up calculations. Issues, Folders, Projects, Sprints
* and most other items are identified with {@code long} ID.
*
* Text item IDs are used in other cases. For example, Users and Special Folders are represented with
* {@code String} IDs.
*
* Item type does not prescribe whether IDs are long or string based. For one type there might be issues with
* long IDs and string IDs.
*
* Uniqueness
*
* It is important to remember that an item ID is unique only within a single instance of JIRA (or within a
* single cluster running JIRA Data Center). It is not globally unique. So when synchronizing multiple JIRAs or
* when restoring Structure data on another instance, all items must be mapped to the new instance.
*
* Value Range
*
*
* - Both item type and string ID must not be null or empty and must not exceed 190 characters.
* - There are no limit on the value of long ID, it can be 0.
*
*
* Canonical notation
*
* {@code ItemIdentity} can be serialized into a {@code String} by calling {@code toString()} on it. The serialized
* form is either:
*
*
* - {@code
/} for long-based identities, for example: {@code com.almworks.jira.structure:type-issue/10000}
* - {@code
//} for string-based identities, for example: {@code com.almworks.jira.structure:type-user//admin}
*
*
* @see CoreItemTypes
* @see CoreIdentities
*/
@PublicApi
@Immutable
public abstract class ItemIdentity implements Serializable {
private static final long serialVersionUID = 2016_11_18_0000L;
/**
* Represents non-existing item.
*/
public static final ItemIdentity ITEM_ZERO = longId("0", 0);
/**
* Item type.
*/
@NotNull
private final String myItemType;
private ItemIdentity(@NotNull String itemType) {
assert !StringUtils.isBlank(itemType) : itemType;
if (StringUtils.isEmpty(itemType)) {
// not using isBlank() here to avoid relatively costly isWhitespace() check
throw new IllegalArgumentException("item type must not be empty");
}
if (itemType.length() > Limits.MAX_MODULE_KEY_LENGTH) {
throw new IllegalArgumentException("item type must not be longer than " + Limits.MAX_MODULE_KEY_LENGTH + " chars");
}
myItemType = itemType;
}
/**
* Returns item type.
*/
@NotNull
public String getItemType() {
return myItemType;
}
/**
* Returns {@code true} if this ID is string-based.
*/
public boolean isStringId() {
return false;
}
/**
* Gets the string ID from a string-based {@code ItemIdentity}.
*
* @return string ID, not null, not empty
* @throws UnsupportedOperationException if this is not a string-based ID
*/
@NotNull
public String getStringId() {
throw new UnsupportedOperationException("Not a string ID");
}
/**
* Returns {@code true} if this ID is long-based.
*/
public boolean isLongId() {
return false;
}
/**
* Gets the long ID from a long-based {@code ItemIdentity}.
*
* @return long ID, not {@code 0}
* @throws UnsupportedOperationException if this is not a long-based ID
*/
public long getLongId() {
throw new UnsupportedOperationException("Not a long ID");
}
/**
* Creates a new string-based ID.
*
* @param itemType item type
* @param stringId item ID
* @return identity
* @throws IllegalArgumentException if the parameters are invalid
*/
@NotNull
public static ItemIdentity stringId(@NotNull String itemType, @NotNull String stringId) {
return new StringIdentity(itemType, stringId);
}
/**
* Creates a new long-based ID.
*
* @param itemType item type
* @param longId item ID
* @return identity
* @throws IllegalArgumentException if the parameters are invalid
*/
@NotNull
public static ItemIdentity longId(@NotNull String itemType, long longId) {
return new LongIdentity(itemType, longId);
}
/**
* Parses canonical string representation of the item ID, which can be retrieved with {@code toString()} method.
*
* @param id string representation of the item ID.
* @return identity
* @throws ParseException if there were errors parsing the string or if the parsed parameters were invalids
*/
@NotNull
public static ItemIdentity parse(@Nullable String id) throws ParseException {
if (id == null || id.isEmpty()) {
throw new ParseException("cannot parse empty id", 0);
}
int k = id.indexOf('/');
if (k < 0) {
try {
long longId = Long.parseLong(id);
return CoreIdentities.issue(longId);
} catch (NumberFormatException e) {
throw new ParseException("unknown id format [" + id + "]", id.length());
}
}
if (k == 0) {
throw new ParseException("empty type id [" + id + "]", 0);
}
if (k == id.length() - 1) {
throw new ParseException("unexpected end of line [" + id + "]", id.length());
}
String typeId = id.substring(0, k);
if (id.charAt(k + 1) == '/') {
String sid = id.substring(k + 2);
if (sid.isEmpty()) {
throw new ParseException("empty sid [" + id + "]", k + 2);
}
try {
return stringId(typeId, sid);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage(), k + 1);
}
} else {
String lid = id.substring(k + 1);
if (lid.isEmpty()) {
throw new ParseException("empty id [" + id + "]", k + 1);
}
try {
return longId(typeId, Long.parseLong(lid));
} catch (NumberFormatException e) {
throw new ParseException("bad id format [" + id + "]", k + 1);
} catch (IllegalArgumentException e) {
throw new ParseException(e.getMessage(), k + 1);
}
}
}
public abstract String toSimplifiedString();
/**
* Represents string-based ID.
*
* @see ItemIdentity
*/
@PublicApi
public static final class StringIdentity extends ItemIdentity implements Serializable {
private static final long serialVersionUID = 2016_11_18_0000L;
@NotNull
private final String myStringId;
private StringIdentity(@NotNull String itemType, @NotNull String stringId) {
super(itemType);
if (StringUtils.isBlank(stringId)) {
throw new IllegalArgumentException("string id must not be empty");
}
if (stringId.length() > Limits.MAX_ITEM_STRING_ID_LENGTH) {
throw new IllegalArgumentException("string id must not be longer than " + Limits.MAX_ITEM_STRING_ID_LENGTH + " chars");
}
myStringId = stringId;
}
@Override
public boolean isStringId() {
return true;
}
@Override
@NotNull
public String getStringId() {
return myStringId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
StringIdentity that = (StringIdentity) o;
return myStringId.equals(that.myStringId) && getItemType().equals(that.getItemType());
}
@Override
public int hashCode() {
return myStringId.hashCode() * 31 + getItemType().hashCode();
}
@Override
public String toString() {
return getItemType() + "//" + myStringId;
}
@Override
public String toSimplifiedString() {
return CoreItemTypes.simplifyType(getItemType()) + "//" + myStringId;
}
}
/**
* Represents long-based ID.
*
* @see ItemIdentity
*/
@PublicApi
public static final class LongIdentity extends ItemIdentity implements Serializable {
private static final long serialVersionUID = 2016_11_18_0000L;
private final long myLongId;
private LongIdentity(@NotNull String itemType, long longId) {
super(itemType);
// watch out for reasons to establish a contract where longId cannot be 0
myLongId = longId;
}
@Override
public boolean isLongId() {
return true;
}
@Override
public long getLongId() {
return myLongId;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
LongIdentity that = (LongIdentity) o;
return myLongId == that.myLongId && getItemType().equals(that.getItemType());
}
@Override
public int hashCode() {
return (int) (myLongId ^ (myLongId >>> 32)) * 31 + getItemType().hashCode();
}
@Override
public String toString() {
return getItemType() + "/" + myLongId;
}
@Override
public String toSimplifiedString() {
return CoreItemTypes.simplifyType(getItemType()) + "/" + myLongId;
}
}
}