/* * #%L * Nazgul Project: nazgul-core-quickstart-model * %% * Copyright (C) 2010 - 2017 jGuru Europe AB * %% * Licensed under the jGuru Europe AB license (the "License"), based * on Apache License, Version 2.0; you may not use this file except * in compliance with the License. * * You may obtain a copy of the License at * * http://www.jguru.se/licenses/jguruCorporateSourceLicense-2.0.txt * * 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. * #L% * */ package se.jguru.nazgul.core.quickstart.model; import se.jguru.nazgul.core.algorithms.api.Validate; import se.jguru.nazgul.core.persistence.model.NazgulEntity; import se.jguru.nazgul.core.xmlbinding.api.XmlBinder; import se.jguru.nazgul.tools.validation.api.exception.InternalStateValidationException; import javax.persistence.Access; import javax.persistence.AccessType; import javax.persistence.Basic; import javax.persistence.Column; import javax.persistence.Entity; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlType; import java.util.StringTokenizer; /** * Specification for how to build names for use within project structures. * A name normally consists of up to three parts, on the form {@code prefix-name-type} * (i.e. {@code nazgul-foo-api-parent} or similar). The order of the parts are as follows: * <ol> * <li><strong>prefix</strong>. An optional prefix for all names within a given project.</li> * <li><strong>name</strong>. A mandatory name.</li> * <li><strong>type</strong>. The type of name which may be used to indicate the functionality * of the name, such as "api", "impl-jpa", "assembly" etc.</li> * </ol> * * @author <a href="mailto:lj@jguru.se">Lennart Jörelid</a>, jGuru Europe AB */ @Entity @Access(AccessType.FIELD) @XmlType(namespace = XmlBinder.CORE_NAMESPACE, propOrder = {"prefix", "name", "type", "separator"}) @XmlAccessorType(XmlAccessType.FIELD) public class Name extends NazgulEntity implements Comparable<Name> { /** * Comparison value constant for indeterminate comparisons. */ public static final int UNKNOWN = Integer.MIN_VALUE; /** * Default separator used between */ public static final String DEFAULT_SEPARATOR = "-"; // Internal state @Basic(optional = true) @Column(nullable = true) @XmlElement(required = false, nillable = true) private String prefix; @Basic(optional = false) @Column(nullable = false) @XmlElement(required = true, nillable = false) private String name; @Basic(optional = false) @Column(nullable = false) @XmlElement(required = true, nillable = false) private String type; @Basic(optional = true) @Column(nullable = true) @XmlElement(required = false, nillable = true) private String separator; /** * JAXB/JPA-friendly constructor. */ public Name() { } /** * Compound constructor creating a new Name object wrapping the supplied data, and using * {@code DEFAULT_SEPARATOR} for separator. * * @param prefix The prefix part of this Name. Optional, i.e. may be null or empty. * @param name The name part of this Name. Mandatory. * @param type The type part of this Name. Mandatory */ public Name(final String prefix, final String name, final String type) { this(prefix, name, type, DEFAULT_SEPARATOR); } /** * Compound constructor creating a new Name object wrapping the supplied data. * * @param prefix The prefix part of this Name. Optional, i.e. may be null or empty. * @param name The name part of this Name. Mandatory. * @param type The type part of this Name. Mandatory. * @param separator The separator part of this Name. Mandatory. */ public Name(final String prefix, final String name, final String type, final String separator) { this.prefix = prefix; this.name = name; this.type = type; this.separator = separator; } /** * @return The prefix part of this Name. Optional, i.e. may be null or empty. */ public String getPrefix() { return prefix; } /** * @return The non-empty name part of this Name. */ public String getName() { return name; } /** * @return The non-empty type of this Name. */ public String getType() { return type; } /** * @return The non-empty separator of this Name. */ public String getSeparator() { return separator; } /** * {@inheritDoc} */ @Override public int compareTo(final Name that) { int toReturn = getStandardNullComparisonValue(this, that); if (toReturn == UNKNOWN) { // Do a detailed comparison. final String thisPrefix = this.getPrefix() == null ? "" : this.getPrefix(); final String thatPrefix = that.getPrefix() == null ? "" : that.getPrefix(); toReturn = thisPrefix.compareTo(thatPrefix); if (toReturn == 0) { toReturn = this.getName().compareTo(that.getName()); } if (toReturn == 0) { toReturn = this.getType().compareTo(that.getType()); } if (toReturn == 0) { toReturn = this.getSeparator().compareTo(that.getSeparator()); } } // All done return toReturn; } /** * {@inheritDoc} */ @Override public boolean equals(final Object obj) { // Check sanity if (!(obj instanceof Name)) { return false; } // All done. return hashCode() == obj.hashCode(); } /** * Joins the parts of this Name to a String, on the form {@code prefix-name-type}, * where the "-" represents the separator. * {@inheritDoc} */ @Override public String toString() { final String prefixPart = prefix == null ? "" : prefix + getSeparator(); return prefixPart + getName() + getSeparator() + getType(); } /** * {@inheritDoc} */ @Override public int hashCode() { final int prefixHash = prefix == null ? 0 : prefix.hashCode(); return prefixHash + name.hashCode() + type.hashCode() + getSeparator().hashCode(); } /** * Compares the two supplied objects, managing null and identity checks. * The return value is found as follows: * <ol> * <li><strong>If both objects are null</strong>: 0</li> * <li><strong>If both objects are identical (o1 == o2)</strong>: 0</li> * <li><strong>If o1 == null and o2 != null</strong>: -1</li> * <li><strong>If o1 != null and o2 == null</strong>: 1</li> * <li><strong>If neither o1 nor o2 is null</strong>: {@code UNKNOWN}</li> * </ol> * {@code 0} if both objects are {@code null} or identical. {@code 1} if o1 is not null and o2 is * {@code null}, and {@code -1} if o1 is null and o2 is not null. If both objects are non-null, * {@code UNKNOWN} is returned. * * @param o1 The left hand object. * @param o2 The right hand object. * @return {@code 0} if both objects are {@code null} or identical. {@code 1} if o1 is not null and o2 is * {@code null}, and {@code -1} if o1 is null and o2 is not null. If both objects are non-null, * {@code UNKNOWN} is returned. */ public static int getStandardNullComparisonValue(final Object o1, final Object o2) { int toReturn = UNKNOWN; // Handle null and identity checks. if (o1 == null) { toReturn = o2 == null ? 0 : -1; } else if (o2 == null) { toReturn = 1; } else if (o1 == o2) { toReturn = 0; } return toReturn; } /** * Standard parsing method which creates a Name from the supplied toParse string, using DEFAULT_SEPARATOR * to identify the Name parts. * * @param toParse A non-empty string which should be parsed into a Name. * @return A Name created from the parts of the toParse string. */ public static Name parse(final String toParse) { return parse(toParse, DEFAULT_SEPARATOR); } /** * Standard parsing method which creates a Name from the supplied toParse string, using the given * separator to identify the Name parts. * * @param toParse A non-empty string which should be parsed into a Name. * @param separator A non-empty separator used to separate the parts of the toParse String. * @return A Name created from the parts of the toParse string. */ public static Name parse(@NotNull @Size(min = 1) final String toParse, @NotNull @Size(min = 1) final String separator) { // Check sanity Validate.notEmpty(toParse, "toParse"); Validate.notEmpty(separator, "separator"); // The name should be on the form [prefix][separator][name][separator][type], // where the [prefix][separator] part is optional. final StringTokenizer tok = new StringTokenizer(toParse, separator, false); final int numTokens = tok.countTokens(); Validate.isTrue(numTokens >= 2, "A Name must contain at least 2 parts (name and type). Found [" + numTokens + "] parts in [" + toParse + "] using separator [" + separator + "]."); String prefix = null; if (numTokens > 2) { // In this case, we have a prefix. prefix = tok.nextToken(); } final String name = tok.nextToken(); // In case we have more separators, simply join the rest of the tokens // with the separator to re-create the type. final StringBuilder typeBuilder = new StringBuilder(); while (tok.hasMoreTokens()) { typeBuilder.append(tok.nextToken()); if (tok.hasMoreTokens()) { typeBuilder.append(separator); } } // All done. return new Name(prefix, name, typeBuilder.toString(), separator); } /** * {@inheritDoc} */ @Override protected void validateEntityState() throws InternalStateValidationException { InternalStateValidationException.create() .notNullOrEmpty(name, "name") .notNullOrEmpty(type, "type") .notNullOrEmpty(separator, "separator") .endExpressionAndValidate(); } }