com.almworks.jira.structure.api.attribute.AttributeSpec Maven / Gradle / Ivy
Show all versions of structure-api Show documentation
package com.almworks.jira.structure.api.attribute;
import com.almworks.jira.structure.api.util.*;
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.util.Collections;
import java.util.Map;
/**
* {@code AttributeSpec} is the "attribute specification", a composite identifier of an attribute. See
* {@link com.almworks.jira.structure.api.attribute package documentation} for the definition of an attribute.
*
* Attribute specification contains the following parts:
*
*
* - A unique {@code String} identifier. Attributes that have the same identifier are considered to be semantically
* same or similar.
*
* - An unbounded parameters map, which should be serializable to JSON (contain only simple types, maps
* and arrays). Two specifications with the same ID and parameters are considered to be
* semantically same.
*
* - {@link ValueFormat}, which defines the type and possible operations on the resulting value.
*
*
* Use {@link AttributeSpecBuilder} to build an instance of {@code AttributeSpec} in the code.
*
* Parameter normalization
*
* Attribute spec parameters are normalized. This means that if the value of a parameter is the default for that
* parameter, it is not stored in the map. For example, {@code int} parameters have default equal to {@code 0}. If you try
* to build an attribute spec and set an {@code int} parameter to 0, the resulting attribute spec will not have that
* value.
*
* This is done to avoid unequal instances of {@code AttributeSpec} to represent the same attribute. If not normalized,
* multiple versions of the same attribute spec could lead to bugs.
*
* See {@link AttributeSpecNormalization} for more details.
*
* @param type of the value expected for this attribute
* @see StructureAttributeService
* @see AttributeSpecBuilder
*/
@PublicApi
@Immutable
public final class AttributeSpec {
@NotNull
private final String myId;
@NotNull
private final ValueFormat myFormat;
@NotNull
private final Map myParams;
private transient int myHashCode;
private transient SpecParams myParamsObject;
private transient String myStringRepresentation;
/**
* Constructs an attribute spec with the given ID and format, without parameters.
*
* @param id attribute ID
* @param format value format
*/
public AttributeSpec(String id, ValueFormat format) {
this(id, format, null, false);
}
/**
* Constructs an attribute spec with the given ID, format and parameters. The latter should contain only simple types
* and collections, the things serializable into JSON.
*
* @param id attribute ID
* @param format value format
* @param params parameters map
*/
public AttributeSpec(@NotNull String id, @NotNull ValueFormat format, @Nullable Map params) {
this(id, format, params, false);
}
AttributeSpec(@NotNull String id, @NotNull ValueFormat format, @Nullable Map params, boolean reuseParams) {
if (StringUtils.isBlank(id)) {
throw new IllegalArgumentException("attribute id must not be empty");
}
if (id.length() > Limits.MAX_SPEC_ID_LENGTH) {
throw new IllegalArgumentException("attribute id must not be longer than " + Limits.MAX_SPEC_ID_LENGTH + " chars");
}
if (id.indexOf(':') >= 0) {
throw new IllegalArgumentException("attribute id must not contain colon");
}
if (format == null) {
throw new IllegalArgumentException("attribute format must not be null");
}
myId = id;
myFormat = format;
Map map = reuseParams ? params : JsonMapUtil.copyParameters(params, true, true, true);
myParams = AttributeSpecNormalization.normalizeParams(id, map != null ? map : Collections.emptyMap());
}
/**
* Returns the attribute's ID.
*/
@NotNull
public String getId() {
return myId;
}
/**
* Returns the attribute's format.
*/
@NotNull
public ValueFormat getFormat() {
return myFormat;
}
/**
* Returns the attribute's parameters as a read-only map. If there are no parameters, empty map is returned.
*/
@NotNull
public Map getParamsMap() {
return myParams;
}
/**
* Returns the same as {@link #getParamsMap()}, but wrapped into accessor object.
*/
@NotNull
public SpecParams getParams() {
SpecParams paramsObject = myParamsObject;
if (paramsObject == null) {
myParamsObject = paramsObject = new SpecParams(myParams);
}
return paramsObject;
}
/**
* Checks if this attribute specification is for the given attribute ID.
*
* @param id attribute's ID
* @return true if this attribute has the same ID
*/
public boolean is(String id) {
return myId.equals(id);
}
/**
* Checks if this attribute specification contains the given format.
*
* @param format value format
* @return true if this attribute has this format
*/
public boolean is(ValueFormat> format) {
return myFormat.equals(format);
}
/**
* Checks if this attribute specification is for the given ID and format.
*
* @param id attribute's ID
* @param format value format
* @return true if this attribute has this ID and format
*/
public boolean is(String id, ValueFormat> format) {
return is(id) && is(format);
}
/**
* Returns an attribute spec with the same ID and parameters, but with the given {@code ValueFormat}.
*
* @param format the format for a new {@code AttributeSpec}
*/
public AttributeSpec as(ValueFormat format) {
return is(format) ? format.cast(this) : new AttributeSpec(myId, format, myParams);
}
/**
* Returns a new attribute spec with added parameter.
*
* @param name parameter name
* @param value parameter value
* @return a new, adjusted attribute specification
*/
public AttributeSpec withParam(String name, Object value) {
return AttributeSpecBuilder.create(this)
.params().set(name, value).done()
.build();
}
/**
* Returns a new attribute spec with parameters replaced with a new map.
*
* @param newParams replacement parameters
*/
public AttributeSpec replaceParams(Map newParams) {
return new AttributeSpec<>(getId(), getFormat(), newParams, true);
}
/**
* Returns a new attribute spec with all parameters removed.
*/
public AttributeSpec noParams() {
return new AttributeSpec<>(getId(), getFormat());
}
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
AttributeSpec that = (AttributeSpec) o;
if (!myFormat.equals(that.myFormat)) return false;
if (!myId.equals(that.myId)) return false;
if (!myParams.equals(that.myParams)) return false;
return true;
}
public int hashCode() {
int result = myHashCode;
if (result == 0) {
result = myId.hashCode();
result = 31 * result + myFormat.hashCode();
result = 31 * result + (myParams.hashCode());
myHashCode = result;
}
return result;
}
public String toString() {
String r = myStringRepresentation;
if (r == null) {
StringBuilder builder = new StringBuilder();
builder.append(myId);
if (!myParams.isEmpty()) {
builder.append(':').append(JsonUtil.toJson(myParams));
}
builder.append(':').append(myFormat);
myStringRepresentation = r = builder.toString();
}
return r;
}
}