001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions copyright [year] [name of copyright owner]".
013 *
014 * Copyright 2014 ForgeRock AS.
015 */
016
017package org.forgerock.openig.heap;
018
019import static java.lang.String.*;
020import static org.forgerock.util.Reject.*;
021
022import org.forgerock.util.Reject;
023
024/**
025 * A Name uniquely identify an object within a hierarchy.
026 * It is composed of a (possible {@code null} parent Name) and a leaf name (never {@code null}).
027 * <p>
028 * Consumers of that API are free to do their own Name rendering (they have access to the whole Name's chain
029 * with {@link #getParent()} method) or use the pre-defined {@link #getFullyQualifiedName()}
030 * and {@link #getScopedName()} methods. Theses methods use the plus ({@literal +}) character as separator.
031 * <p>
032 * The Name instances are immutable.
033 */
034public final class Name {
035
036    /**
037     * Builds a new Name using the given name parts.
038     * They are ordered in descending order (ancestors first, leaf last)
039     *
040     * @param parts
041     *         ordered fragments of the name
042     * @return a new Name using the given name parts.
043     */
044    public static Name of(final String... parts) {
045        Reject.ifTrue(parts.length == 0);
046        Name name = null;
047        for (String part : parts) {
048            name = new Name(name, part);
049        }
050        return name;
051    }
052
053    /**
054     * Builds a new Name for the given type.
055     * The generated name will use the given type's short name  as leaf and will have no parent.
056     *
057     * @param type
058     *         typ used to generate a name
059     * @return a new Name for the given type
060     */
061    public static Name of(final Class<?> type) {
062        return Name.of(type.getSimpleName());
063    }
064
065    private final Name parent;
066    private final String leaf;
067
068    /**
069     * Builds a new hierarchical Name with the given {@code parent} Name and the given {@code leaf} leaf name. Notice
070     * that the parent name can be {@code null} while the leaf part cannot be {@code null} (this is verified and a
071     * NullPointerException is thrown if it is).
072     *
073     * @param parent
074     *         parent Name
075     * @param leaf
076     *         leaf name (cannot be {@code null})
077     */
078    private Name(final Name parent, final String leaf) {
079        this.parent = parent;
080        this.leaf = checkNotNull(leaf);
081    }
082
083    /**
084     * Returns the parent Name (can be {@code null}).
085     *
086     * @return the parent Name (can be {@code null}).
087     */
088    public Name getParent() {
089        return parent;
090    }
091
092    /**
093     * Returns the leaf name (cannot be {@code null}).
094     *
095     * @return the leaf name.
096     */
097    public String getLeaf() {
098        return leaf;
099    }
100
101    /**
102     * Creates a new Name, relative to this Name with the given leaf name.
103     *
104     * @param name
105     *         relative leaf name
106     * @return a new Name, relative to this Name.
107     */
108    public Name child(final String name) {
109        return new Name(this, name);
110    }
111
112    /**
113     * Returns this name with the last segment adapted to include the decorator name.
114     * The last segment is changed to follow this pattern: {@literal @decorator[last-segment]}.
115     *
116     * @param decorator
117     *         decorator name.
118     * @return a new decorated name based on this name
119     */
120    public Name decorated(final String decorator) {
121        return new Name(parent, format("@%s[%s]", decorator, leaf));
122    }
123
124    /**
125     * Returns a String representation of this Name that includes the full Name hierarchy.
126     * <p>
127     * The following format has to be expected:
128     * <pre>
129     *     {@code
130     *     (parent '+')* leaf
131     *     }
132     * </pre>
133     * <p>
134     * Examples:
135     * <ul>
136     *     <li>{@code LocalNameOnly}</li>
137     *     <li>{@code /openig/config/config.json+_Router}</li>
138     *     <li>{@code /openig/config/config.json+_Router+/openig/config/routes/openid-connect.json+OAuth2Filter}</li>
139     * </ul>
140     *
141     * @return a String representation of this Name that includes the full Name hierarchy.
142     */
143    public String getFullyQualifiedName() {
144        StringBuilder sb = new StringBuilder();
145        if (parent != null) {
146            sb.append(parent.getFullyQualifiedName());
147            sb.append("+");
148        }
149        sb.append(leaf);
150        return sb.toString();
151    }
152
153    /**
154     * Returns a String representation of this Name that includes only the first parent and the leaf name.
155     * <p>
156     * The following format has to be expected:
157     * <pre>
158     *     {@code
159     *     (parent '+')? leaf
160     *     }
161     * </pre>
162     * <p>
163     * Examples:
164     * <ul>
165     *     <li>{@code LocalNameOnly}</li>
166     *     <li>{@code /openig/config/config.json+_Router}</li>
167     * </ul>
168     *
169     * @return a String representation of this Name that includes only the first parent and the leaf name.
170     */
171    public String getScopedName() {
172        StringBuilder sb = new StringBuilder();
173        if (parent != null) {
174            sb.append(parent.getLeaf());
175            sb.append("+");
176        }
177        sb.append(leaf);
178        return sb.toString();
179    }
180
181    @Override
182    public boolean equals(final Object o) {
183        if (this == o) {
184            return true;
185        }
186        if (!(o instanceof Name)) {
187            return false;
188        }
189
190        Name name = (Name) o;
191
192        if (!leaf.equals(name.leaf)) {
193            return false;
194        }
195        if (parent == null) {
196            return name.parent == null;
197        }
198        return parent.equals(name.parent);
199    }
200
201    @Override
202    public int hashCode() {
203        int result = parent != null ? parent.hashCode() : 0;
204        result = 31 * result + leaf.hashCode();
205        return result;
206    }
207
208    /**
209     * Returns the fully qualified name of this Name (format: {@literal (parent '+')* leaf}).
210     *
211     * @return the fully qualified name of this Name.
212     * @see #getFullyQualifiedName()
213     */
214    @Override
215    public String toString() {
216        return getFullyQualifiedName();
217    }
218}