# QualifiedName.java

    /*
     * Copyright (c) 2009, Rickard Öberg. All Rights Reserved.
     *
     * 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.qi4j.api.common;

    import java.io.Serializable;
    import java.lang.reflect.AccessibleObject;
    import java.lang.reflect.Member;
    import org.qi4j.api.util.NullArgumentException;

    /**
     * QualifiedName is a representation of Property names to their full declaration.
     * <p>
     * A QualifiedName is created by combining the name of a method and the name of the type that declares the method.
     * This class also contains many static utility methods to manage QualifiedName instances.
     * </p>
     * <p>
     * <strong>NOTE: Unless you do very generic libraries, entity stores and other extensions that is deeply coupled into
     * the Zest runtime, it is very unlikely you will need to use this class directly.</strong>
     * </p>
     * <p>
     * It is also important to notice that the QualifiedName needs to be long-term stable, as the names are written
     * to persistent storage. So any changes in the formatting <strong>must be made in a backward-compatible manner
     * </strong>.
     * </p>
     * <p>
     * The QualifiedName has two intrinsic parts, one being the {@code type} and the other the {@code name}. The
     * {@code type} comes from the class where the QualifiedName originates from and internally kept as a {@link TypeName}
     * instance. The name is the name from the method name. When the QualifiedName instance is converted to an external
     * string representation, via the offical and formal {@link #toString()} method, the {@code type} is normalized, i.e.
     * any dollar characters ($) in the name are replaced by dashes (-), to make them URI friendly.
     * </p>
     * <p>
     * QualifiedName instances are immutable, implements {@link #hashCode()} and {@link #equals(Object)} as a value
     * object and can safely be used as keys in {@link java.util.Map}.
     */
    public final class QualifiedName
        implements Comparable<QualifiedName>, Serializable
    {
        private final TypeName typeName;
        private final String name;

        /**
         * Creates a QualifiedName from a method.
         * <p>
         * This factory method will create a QualifiedName from the Method itself.
         *
         * </p>
         *
         * @param method Type method that returns a Property, for which the QualifiedName will be representing.
         *
         * @return A QualifiedName representing this method.
         *
         * @throws NullArgumentException If the {@code method} argument passed is null.
         */
        public static QualifiedName fromAccessor( AccessibleObject method )
        {
            NullArgumentException.validateNotNull( "method", method );
            return fromClass( ( (Member) method ).getDeclaringClass(), ( (Member) method ).getName() );
        }

        /**
         * Creates a QualifiedName instance from the Class and a given name.
         * <p>
         * This factory method converts the {@code type} to a {@link TypeName} and appends the given {@code name}.
         *
         * @param type The Class that is the base of the QualifiedName.
         * @param name The qualifier name which will be appended to the base name derived from the {@code type} argument.
         *
         * @return A QualifiedName instance representing the {@code type} and {@code name} arguments.
         *
         * @throws NullArgumentException if any of the two arguments are {@code null}, or if the name string is empty.
         */
        public static QualifiedName fromClass( Class type, String name )
        {
            return new QualifiedName( TypeName.nameOf( type ), name );
        }

        /**
         * Creates a Qualified name from a type as string and a name qualifier.
         *
         * @param type The type name as a a string, which must be properly formatted. No checks for correctly formatted
         *             type name is performed.
         * @param name The qualifier name which will be appended to the base name derived from the {@code type} argument.
         *
         * @return A QualifiedName instance representing the {@code type} and {@code name} arguments.
         *
         * @throws NullArgumentException if any of the two arguments are {@code null} or either string is empty.
         */
        public static QualifiedName fromName( String type, String name )
        {
            return new QualifiedName( TypeName.nameOf( type ), name );
        }

        /**
         * Creates a QualifiedName from the external string format of QualifiedName.
         * <p>
         * This factory method is the reverse of {@link QualifiedName#toString() }  method, and creates a new QualifiedName
         * instance from the string representation of the QualifiedName.
         * </p>
         *
         * @param fullQualifiedName The QualifiedName external string representation to be converted back into a QualifiedName
         *                      instance.
         *
         * @return The QualifiedName instance represented by the {@code qualifiedName} argument.
         *
         * @throws IllegalArgumentException If the {@code qualifiedName} argument has wrong format.
         */
        public static QualifiedName fromFQN( String fullQualifiedName )
        {
            NullArgumentException.validateNotEmpty( "qualifiedName", fullQualifiedName );
            int idx = fullQualifiedName.lastIndexOf( ":" );
            if( idx == -1 )
            {
                throw new IllegalArgumentException( "Name '" + fullQualifiedName + "' is not a qualified name" );
            }
            final String type = fullQualifiedName.substring( 0, idx );
            final String name = fullQualifiedName.substring( idx + 1 );
            return new QualifiedName( TypeName.nameOf( type ), name );
        }

        QualifiedName( TypeName typeName, String name )
        {
            NullArgumentException.validateNotNull( "typeName", typeName );
            NullArgumentException.validateNotEmpty( "name", name );
            this.typeName = typeName;
            this.name = name;
        }

        /**
         * Returns the normalized string of the type part of the QualifiedName.
         *
         * <p>
         * The normalized type name means that all dollar ($) characters have been replaced by dashes (-).
         * </p>
         *
         * @return the normalized string of the type part of the QualifiedName.
         */
        public String type()
        {
            return typeName.normalized();
        }

        /**
         * Returns the name component of the QualifiedName.
         *
         * @return the name component of the QualifiedName.
         */
        public String name()
        {
            return name;
        }

        /**
         * Returns the URI of the QualifiedName.
         *
         * <p>
         * The URI is the {@link #toNamespace()} followed by the {@code name} component.
         * <p>
         *
         * @return the URI of the QualifiedName.
         *
         * @see #toNamespace()
         */
        public String toURI()
        {
            return toNamespace() + name;
        }

        /**
         * Return the URI of the {@link TypeName} component of the QualifiedName.
         * <p>
         * The URI of the {@link TypeName} component is in the form of;
         * </p>
         * <pre>
         * "urn:qi4j:type:" normalizedClassName
         * </pre>
         * <p>
         * where {@code normalizedClassName} is the fully-qualified class name having had any dollar ($) characters replaced
         * by URI friendly dashes (-), with a trailing hash (#). Examples;
         * </p>
         * <pre>
         * urn:qi4j:type:org.qi4j.api.common.QualifiedName#
         * urn:qi4j:type:org.qi4j.samples.MyClass-MyInnerClass#
         * </pre>
         *
         * @return the URI of the {@link TypeName} component of the QualifiedName.
         */
        public String toNamespace()
        {
            return typeName.toURI() + "#";
        }

        /**
         * Return the formal and official, long-term stable, external string representation of a QualifiedName.
         * <p>
         * This returns the {@link org.qi4j.api.common.TypeName#toString()} followed by the {@code name} component.
         * </p>
         *
         * @return the formal and official, long-term stable, external string representation of a QualifiedName.
         */
        @Override
        public String toString()
        {
            return typeName + ":" + name;
        }

        @Override
        public boolean equals( Object o )
        {
            if( this == o )
            {
                return true;
            }
            if( o == null || getClass() != o.getClass() )
            {
                return false;
            }

            QualifiedName that = (QualifiedName) o;

            return name.equals( that.name ) && typeName.equals( that.typeName );
        }

        @Override
        public int hashCode()
        {
            return 31 * typeName.hashCode() + name.hashCode();
        }

        @Override
        public int compareTo( QualifiedName other )
        {
            final int result = typeName.compareTo( other.typeName );
            if( result != 0 )
            {
                return result;
            }
            return name.compareTo( other.name );
        }
    }