io.github.davejoyce.id.NamespaceId Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of versioned-ids Show documentation
Show all versions of versioned-ids Show documentation
Versioned, namespace-aware ID types
The newest version!
/*
* Copyright 2016 David Joyce
*
* 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 io.github.davejoyce.id;
import io.github.davejoyce.versioned.Versioned;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import static io.github.davejoyce.util.Arguments.requireNonEmpty;
import static io.github.davejoyce.util.Arguments.requireNonNull;
/**
* Basic unique identifier within a particular namespace. An instance of this
* class is {@link Versioned}; it provides 'natural' sort order and can
* determine whether it occurs before or after another version of itself.
* Additionally, a {@code NamespaceId} object produces a string-encoded
* representation of itself from which it can later be reconstructed, supported
* by all serialization protocols.
*
* @param comparable type of ID attribute
* @author Dave Joyce
*/
public class NamespaceId>
implements Versioned> {
/**
* Separator of fields in string-encoded {@code NamespaceId} instance.
*/
public static final char SEPARATOR = '/';
/**
* Magic number prime for hashcode calculation.
*/
protected static final int HASHCODE_MULTIPLIER = 31;
/**
* Create a new {@code NamespaceId} instance with ID attribute of type
* T from the specified string representation.
*
* @param idString '/' separated ID string to be parsed
* @param idType class of ID attribute type
* @param comparable type of ID attribute
* @return new NamespaceId object
* @throws IllegalArgumentException if idString cannot be converted
* back to NamespaceId with ID attribute of
* type T
*/
public static > NamespaceId fromString(
final String idString,
final Class idType) {
String s = requireNonEmpty(idString, "ID string cannot be empty");
int separatorPos1 = s.indexOf(SEPARATOR);
if (-1 == separatorPos1) {
throw new IllegalArgumentException(
"NamespaceId string must contain at least 1 '"
+ SEPARATOR
+ "' separator");
}
// ID string may be temporal or bi-temporal ID string being downcast;
// disregard temporal components past 2nd slash
int separatorPosLast = s.lastIndexOf(SEPARATOR);
int endPos = s.length();
if (separatorPos1 < separatorPosLast) {
endPos = separatorPosLast;
}
final String ns = requireNonEmpty(s.substring(0, separatorPos1),
"Namespace segment cannot be empty");
String idVal = requireNonEmpty(s.substring((separatorPos1 + 1), endPos),
"Identifier segment cannot be empty");
final T id = castId(idVal, idType);
return new NamespaceId<>(ns, id);
}
/**
* Create a new {@code NamespaceId} instance with ID attribute of type
* String from the specified string representation.
*
* @param idString '/' separated ID string to be parsed
* @return new NamespaceId object
* @throws IllegalArgumentException if idString cannot be converted
* back to NamespaceId with ID attribute of
* type String
*/
public static NamespaceId fromString(final String idString) {
return fromString(idString, String.class);
}
/**
* Convert the specified string representation of the ID value to its actual
* type. This utility method is protected for use within {@code NamespaceId}
* and subclasses only.
*
* @param idValue string to be converted
* @param idType class of ID attribute type
* @param comparable type of ID attribute
* @return converted ID attribute
*/
protected static > T castId(final String idValue,
final Class idType) {
T id;
try {
// 1. Look for standard 'valueOf' factory method on type
Method valueOfMethod = idType.getMethod("valueOf", String.class);
id = idType.cast(valueOfMethod.invoke(null, idValue));
} catch (IllegalAccessException
| InvocationTargetException
| NoSuchMethodException e1) {
try {
// 2. Look for constructor that takes a string representation of
// value
Constructor c = idType.getConstructor(String.class);
id = c.newInstance(idValue);
} catch (IllegalAccessException
| InstantiationException
| InvocationTargetException
| NoSuchMethodException e2) {
try {
// 3. See if we can just cast it to the target type
// (last resort)
id = idType.cast(idValue);
} catch (ClassCastException cce) {
throw new IllegalArgumentException(
"Identifier segment cannot be converted to type: "
+ idType.getCanonicalName());
}
}
}
return id;
}
/**
* Namespace of this object.
*/
private final String namespace;
/**
* Unique, comparable ID value of this object.
*/
private final T id;
/**
* Construct a {@code NamespaceId} object in the given namespace and with
* the given unique identifier value.
*
* @param ns namespace to be occupied
* @param idValue unique identifier value
* @throws IllegalArgumentException if namespace is empty or
* id is null
*/
public NamespaceId(final String ns, final T idValue) {
this.namespace = requireNonEmpty(ns, "Namespace cannot be empty");
this.id = requireNonNull(idValue, "ID cannot be null");
}
/**
* Get the namespace occupied by this object.
*
* @return namespace (never null)
*/
public final String getNamespace() {
return namespace;
}
/**
* Get the unique identifier value of this object.
*
* @return ID (never null)
*/
public final T getId() {
return id;
}
@Override
public boolean equals(final Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
NamespaceId> that = (NamespaceId>) o;
return ((null == namespace ? null == that.namespace : namespace.equals(that.namespace)) &&
(null == id ? null == that.id : id.equals(that.id)));
}
@Override
public int hashCode() {
int result = namespace.hashCode();
result = HASHCODE_MULTIPLIER * result + id.hashCode();
return result;
}
@Override
public int compareTo(final NamespaceId o) {
if (this == o) {
return 0;
}
int comp = this.namespace.compareTo(o.namespace);
if (0 != comp) {
return comp;
}
return this.id.compareTo(o.id);
}
@Override
public boolean after(final NamespaceId o) {
return (0 < this.compareTo(o));
}
@Override
public boolean before(final NamespaceId o) {
return (0 > this.compareTo(o));
}
/**
* Get {@code NamespaceId} representation of this object.
*
* @return NamespaceId representation
*/
public NamespaceId toNamespaceId() {
return this;
}
@Override
public String toString() {
return namespace + SEPARATOR + id.toString();
}
}