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 2008-2009 Sun Microsystems, Inc.
015 * Portions Copyright 2011-2016 ForgeRock AS.
016 */
017
018package org.forgerock.opendj.config;
019
020import java.util.Collections;
021import java.util.LinkedList;
022import java.util.List;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import org.forgerock.opendj.server.config.client.RootCfgClient;
027import org.forgerock.opendj.server.config.meta.RootCfgDefn;
028import org.forgerock.opendj.server.config.server.RootCfg;
029import org.forgerock.opendj.ldap.DN;
030import org.forgerock.opendj.ldap.RDN;
031import org.forgerock.opendj.ldap.schema.AttributeType;
032import org.forgerock.opendj.ldap.schema.Schema;
033
034/**
035 * A path which can be used to determine the location of a managed object
036 * instance.
037 * <p>
038 * A path is made up of zero or more elements each of which represents a managed
039 * object. Managed objects are arranged hierarchically with the root
040 * configuration being the top-most managed object. Elements are ordered such
041 * that the root configuration managed object is the first element and
042 * subsequent elements representing managed objects further down the hierarchy.
043 * <p>
044 * A path can be encoded into a string representation using the
045 * {@link #toString()} and {@link #toString(StringBuilder)} methods. Conversely,
046 * this string representation can be parsed using the {@link #valueOf(String)}
047 * method.
048 * <p>
049 * The string representation of a managed object path is similar in principle to
050 * a UNIX file-system path and is defined as follows:
051 * <ul>
052 * <li>the root element is represented by the string <code>/</code>
053 * <li>subordinate elements are arranged in big-endian order separated by a
054 * forward slash <code>/</code> character
055 * <li>an element representing a managed object associated with a one-to-one
056 * (singleton) or one-to-zero-or-one (optional) relation has the form
057 * <code>relation=</code><i>relation</i> <code>[+type=</code><i>definition</i>
058 * <code>]</code>, where <i>relation</i> is the name of the relation and
059 * <i>definition</i> is the name of the referenced managed object's definition
060 * if required (usually this is implied by the relation itself)
061 * <li>an element representing a managed object associated with a one-to-many
062 * (instantiable) relation has the form <code>relation=</code><i>relation</i>
063 * <code>[+type=</code> <i>definition</i><code>]</code><code>+name=</code>
064 * <i>name</i>, where <i>relation</i> is the name of the relation and
065 * <i>definition</i> is the name of the referenced managed object's definition
066 * if required (usually this is implied by the relation itself), and <i>name</i>
067 * is the name of the managed object instance
068 * <li>an element representing a managed object associated with a one-to-many
069 * (set) relation has the form <code>relation=</code><i>relation</i>
070 * <code>[+type=</code> <i>definition</i><code>]</code>, where <i>relation</i>
071 * is the name of the relation and <i>definition</i> is the name of the
072 * referenced managed object's definition.
073 * </ul>
074 * The following path string representation identifies a connection handler
075 * instance (note that the <code>type</code> is not specified indicating that
076 * the path identifies a connection handler called <i>my handler</i> which can
077 * be any type of connection handler):
078 *
079 * <pre>
080 *  /relation=connection-handler+name=my handler
081 * </pre>
082 *
083 * If the identified connection handler must be an LDAP connection handler then
084 * the above path should include the <code>type</code>:
085 *
086 * <pre>
087 *  /relation=connection-handler+type=ldap-connection-handler+name=my handler
088 * </pre>
089 *
090 * The final example identifies the global configuration:
091 *
092 * <pre>
093 *  /relation=global-configuration
094 * </pre>
095 *
096 * @param <C>
097 *            The type of client managed object configuration that this path
098 *            references.
099 * @param <S>
100 *            The type of server managed object configuration that this path
101 *            references.
102 */
103public final class ManagedObjectPath<C extends ConfigurationClient, S extends Configuration> {
104
105    /** A serialize which is used to generate the toDN representation. */
106    private static final class DNSerializer implements ManagedObjectPathSerializer {
107
108        /** The current DN. */
109        private DN dn;
110
111        /** The LDAP profile. */
112        private final LDAPProfile profile;
113
114        /** Create a new DN builder. */
115        private DNSerializer() {
116            this.dn = DN.rootDN();
117            this.profile = LDAPProfile.getInstance();
118        }
119
120        @Override
121        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
122            InstantiableRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d,
123            String name) {
124            // Add the RDN sequence representing the relation.
125            appendManagedObjectPathElement(r);
126
127            // Now add the single RDN representing the named instance.
128            String type = profile.getRelationChildRDNType(r);
129            AttributeType attrType = Schema.getDefaultSchema().getAttributeType(type);
130            dn = dn.child(new RDN(attrType, name));
131        }
132
133        @Override
134        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
135            SetRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
136            // Add the RDN sequence representing the relation.
137            appendManagedObjectPathElement(r);
138
139            // Now add the single RDN representing the instance.
140            String type = profile.getRelationChildRDNType(r);
141            AttributeType attrType = Schema.getDefaultSchema().getAttributeType(type);
142            dn = dn.child(new RDN(attrType, d.getName()));
143        }
144
145        @Override
146        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
147            OptionalRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
148            // Add the RDN sequence representing the relation.
149            appendManagedObjectPathElement(r);
150        }
151
152        @Override
153        public <C extends ConfigurationClient, S extends Configuration> void appendManagedObjectPathElement(
154            SingletonRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
155            // Add the RDN sequence representing the relation.
156            appendManagedObjectPathElement(r);
157        }
158
159        /** Appends the RDN sequence representing the provided relation. */
160        private void appendManagedObjectPathElement(RelationDefinition<?, ?> r) {
161            DN localName = DN.valueOf(profile.getRelationRDNSequence(r));
162            dn = dn.child(localName);
163        }
164
165        /** Gets the serialized DN value. */
166        private DN toDN() {
167            return dn;
168        }
169    }
170
171    /** Abstract path element. */
172    private static abstract class Element<C extends ConfigurationClient, S extends Configuration> {
173
174        /** The type of managed object referenced by this element. */
175        private final AbstractManagedObjectDefinition<C, S> definition;
176
177        /**
178         * Protected constructor.
179         *
180         * @param definition
181         *            The type of managed object referenced by this element.
182         */
183        protected Element(AbstractManagedObjectDefinition<C, S> definition) {
184            this.definition = definition;
185        }
186
187        /**
188         * Get the managed object definition associated with this element.
189         *
190         * @return Returns the managed object definition associated with this
191         *         element.
192         */
193        public final AbstractManagedObjectDefinition<C, S> getManagedObjectDefinition() {
194            return definition;
195        }
196
197        /**
198         * Get the name associated with this element if applicable.
199         *
200         * @return Returns the name associated with this element if applicable.
201         */
202        public String getName() {
203            return null;
204        }
205
206        /**
207         * Get the relation definition associated with this element.
208         *
209         * @return Returns the relation definition associated with this element.
210         */
211        public abstract RelationDefinition<? super C, ? super S> getRelationDefinition();
212
213        /**
214         * Serialize this path element using the provided serialization
215         * strategy.
216         *
217         * @param serializer
218         *            The managed object path serialization strategy.
219         */
220        public abstract void serialize(ManagedObjectPathSerializer serializer);
221    }
222
223    /** A path element representing an instantiable managed object. */
224    private static final class InstantiableElement<C extends ConfigurationClient, S extends Configuration> extends
225        Element<C, S> {
226
227        /** Factory method. */
228        private static <C extends ConfigurationClient, S extends Configuration> InstantiableElement<C, S> create(
229            InstantiableRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d,
230            String name) {
231            return new InstantiableElement<>(r, d, name);
232        }
233
234        /** The name of the managed object. */
235        private final String name;
236
237        /** The instantiable relation. */
238        private final InstantiableRelationDefinition<? super C, ? super S> r;
239
240        /** Private constructor. */
241        private InstantiableElement(InstantiableRelationDefinition<? super C, ? super S> r,
242            AbstractManagedObjectDefinition<C, S> d, String name) {
243            super(d);
244            this.r = r;
245            this.name = name;
246        }
247
248        @Override
249        public String getName() {
250            return name;
251        }
252
253        @Override
254        public InstantiableRelationDefinition<? super C, ? super S> getRelationDefinition() {
255            return r;
256        }
257
258        @Override
259        public void serialize(ManagedObjectPathSerializer serializer) {
260            serializer.appendManagedObjectPathElement(r, getManagedObjectDefinition(), name);
261        }
262    }
263
264    /** A path element representing an optional managed object. */
265    private static final class OptionalElement<C extends ConfigurationClient, S extends Configuration> extends
266        Element<C, S> {
267
268        /** Factory method. */
269        private static <C extends ConfigurationClient, S extends Configuration> OptionalElement<C, S> create(
270            OptionalRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
271            return new OptionalElement<>(r, d);
272        }
273
274        /** The optional relation. */
275        private final OptionalRelationDefinition<? super C, ? super S> r;
276
277        /** Private constructor. */
278        private OptionalElement(OptionalRelationDefinition<? super C, ? super S> r,
279            AbstractManagedObjectDefinition<C, S> d) {
280            super(d);
281            this.r = r;
282        }
283
284        @Override
285        public OptionalRelationDefinition<? super C, ? super S> getRelationDefinition() {
286            return r;
287        }
288
289        @Override
290        public void serialize(ManagedObjectPathSerializer serializer) {
291            serializer.appendManagedObjectPathElement(r, getManagedObjectDefinition());
292        }
293    }
294
295    /** A path element representing an set managed object. */
296    private static final class SetElement<C extends ConfigurationClient, S extends Configuration> extends
297        Element<C, S> {
298
299        /** Factory method. */
300        private static <C extends ConfigurationClient, S extends Configuration> SetElement<C, S> create(
301            SetRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
302            return new SetElement<>(r, d);
303        }
304
305        /** The set relation. */
306        private final SetRelationDefinition<? super C, ? super S> r;
307
308        /** Private constructor. */
309        private SetElement(SetRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
310            super(d);
311            this.r = r;
312        }
313
314        @Override
315        public SetRelationDefinition<? super C, ? super S> getRelationDefinition() {
316            return r;
317        }
318
319        @Override
320        public void serialize(ManagedObjectPathSerializer serializer) {
321            serializer.appendManagedObjectPathElement(r, getManagedObjectDefinition());
322        }
323    }
324
325    /** A path element representing a singleton managed object. */
326    private static final class SingletonElement<C extends ConfigurationClient, S extends Configuration> extends
327        Element<C, S> {
328
329        /** Factory method. */
330        private static <C extends ConfigurationClient, S extends Configuration> SingletonElement<C, S> create(
331            SingletonRelationDefinition<? super C, ? super S> r, AbstractManagedObjectDefinition<C, S> d) {
332            return new SingletonElement<>(r, d);
333        }
334
335        /** The singleton relation. */
336        private final SingletonRelationDefinition<? super C, ? super S> r;
337
338        /** Private constructor. */
339        private SingletonElement(SingletonRelationDefinition<? super C, ? super S> r,
340            AbstractManagedObjectDefinition<C, S> d) {
341            super(d);
342            this.r = r;
343        }
344
345        @Override
346        public SingletonRelationDefinition<? super C, ? super S> getRelationDefinition() {
347            return r;
348        }
349
350        @Override
351        public void serialize(ManagedObjectPathSerializer serializer) {
352            serializer.appendManagedObjectPathElement(r, getManagedObjectDefinition());
353        }
354    }
355
356    /** A serialize which is used to generate the toString representation. */
357    private static final class StringSerializer implements ManagedObjectPathSerializer {
358
359        /** Serialize to this string builder. */
360        private final StringBuilder builder;
361
362        /** Private constructor. */
363        private StringSerializer(StringBuilder builder) {
364            this.builder = builder;
365        }
366
367        @Override
368        public <M extends ConfigurationClient, N extends Configuration> void appendManagedObjectPathElement(
369            InstantiableRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d,
370            String name) {
371            serializeElement(r, d);
372
373            // Be careful to escape any forward slashes in the name.
374            builder.append("+name=");
375            builder.append(name.replace("/", "//"));
376        }
377
378        @Override
379        public <M extends ConfigurationClient, N extends Configuration> void appendManagedObjectPathElement(
380            OptionalRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
381            serializeElement(r, d);
382        }
383
384        @Override
385        public <M extends ConfigurationClient, N extends Configuration> void appendManagedObjectPathElement(
386            SingletonRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
387            serializeElement(r, d);
388        }
389
390        @Override
391        public <M extends ConfigurationClient, N extends Configuration> void appendManagedObjectPathElement(
392            SetRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
393            serializeElement(r, d);
394        }
395
396        /** Common element serialization. */
397        private <M, N> void serializeElement(RelationDefinition<?, ?> r, AbstractManagedObjectDefinition<?, ?> d) {
398            // Always specify the relation name.
399            builder.append("/relation=");
400            builder.append(r.getName());
401
402            // Only specify the type if it is a sub-type of the relation's
403            // type.
404            if (r.getChildDefinition() != d) {
405                builder.append("+type=");
406                builder.append(d.getName());
407            }
408        }
409    }
410
411    /** Single instance of a root path. */
412    private static final ManagedObjectPath<RootCfgClient, RootCfg> EMPTY_PATH =
413        new ManagedObjectPath<>(new LinkedList<Element<?, ?>>(), null, RootCfgDefn.getInstance());
414
415    /** A regular expression used to parse path elements. */
416    private static final Pattern PE_REGEXP = Pattern.compile("^\\s*relation=\\s*([^+]+)\\s*"
417        + "(\\+\\s*type=\\s*([^+]+)\\s*)?" + "(\\+\\s*name=\\s*([^+]+)\\s*)?$");
418
419    /**
420     * Creates a new managed object path representing the configuration root.
421     *
422     * @return Returns a new managed object path representing the configuration
423     *         root.
424     */
425    public static ManagedObjectPath<RootCfgClient, RootCfg> emptyPath() {
426        return EMPTY_PATH;
427    }
428
429    /**
430     * Returns a managed object path holding the value of the specified string.
431     *
432     * @param s
433     *            The string to be parsed.
434     * @return Returns a managed object path holding the value of the specified
435     *         string.
436     * @throws IllegalArgumentException
437     *             If the string could not be parsed.
438     */
439    public static ManagedObjectPath<?, ?> valueOf(String s) {
440        String ns = s.trim();
441
442        // Check for root special case.
443        if (ns.equals("/")) {
444            return EMPTY_PATH;
445        }
446
447        // Parse the elements.
448        LinkedList<Element<?, ?>> elements = new LinkedList<>();
449        Element<?, ?> lastElement = null;
450        AbstractManagedObjectDefinition<?, ?> definition = RootCfgDefn.getInstance();
451
452        if (!ns.startsWith("/")) {
453            throw new IllegalArgumentException("Invalid path \"" + ns + "\": must begin with a \"/\"");
454        }
455
456        int start = 1;
457        while (true) {
458            // Get the next path element.
459            int end;
460            for (end = start; end < ns.length(); end++) {
461                char c = ns.charAt(end);
462                if (c == '/') {
463                    if (end == (ns.length() - 1)) {
464                        throw new IllegalArgumentException("Invalid path \"" + ns
465                            + "\": must not end with a trailing \"/\"");
466                    }
467
468                    if (ns.charAt(end + 1) == '/') {
469                        // Found an escaped forward slash.
470                        end++;
471                    } else {
472                        // Found the end of this path element.
473                        break;
474                    }
475                }
476            }
477
478            // Get the next element.
479            String es = ns.substring(start, end);
480
481            Matcher m = PE_REGEXP.matcher(es);
482            if (!m.matches()) {
483                throw new IllegalArgumentException("Invalid path element \"" + es + "\" in path \"" + ns + "\"");
484            }
485
486            // Mandatory.
487            String relation = m.group(1);
488
489            // Optional.
490            String type = m.group(3);
491
492            // Mandatory if relation is instantiable.
493            String name = m.group(5);
494
495            // Get the relation definition.
496            RelationDefinition<?, ?> r;
497            try {
498                r = definition.getRelationDefinition(relation);
499            } catch (IllegalArgumentException e) {
500                throw new IllegalArgumentException("Invalid path element \"" + es + "\" in path \"" + ns
501                    + "\": unknown relation \"" + relation + "\"");
502            }
503
504            // Append the next element.
505            lastElement = createElement(r, ns, es, type, name);
506            elements.add(lastElement);
507            definition = lastElement.getManagedObjectDefinition();
508
509            // Update start to point to the beginning of the next element.
510            if (end < ns.length()) {
511                // Skip to the beginning of the next element
512                start = end + 1;
513            } else {
514                // We reached the end of the string.
515                break;
516            }
517        }
518
519        // Construct the new path.
520        return create(elements, lastElement);
521    }
522
523    /** Factory method required in order to allow generic wild-card construction of new paths. */
524    private static <C extends ConfigurationClient, S extends Configuration> ManagedObjectPath<C, S> create(
525        LinkedList<Element<?, ?>> elements, Element<C, S> lastElement) {
526        return new ManagedObjectPath<>(elements, lastElement.getRelationDefinition(),
527            lastElement.getManagedObjectDefinition());
528    }
529
530    // @Checkstyle:ignore
531    /** Decode an element. */
532    private static <C extends ConfigurationClient, S extends Configuration> Element<? extends C, ? extends S>
533    createElement(RelationDefinition<C, S> r, String path, String element, String type, String name) {
534        // First determine the managed object definition.
535        AbstractManagedObjectDefinition<? extends C, ? extends S> d = null;
536
537        if (type != null) {
538            for (AbstractManagedObjectDefinition<? extends C, ? extends S> child : r.getChildDefinition()
539                .getAllChildren()) {
540                if (child.getName().equals(type)) {
541                    d = child;
542                    break;
543                }
544            }
545
546            if (d == null) {
547                throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
548                    + "\": unknown sub-type \"" + type + "\"");
549            }
550        } else {
551            d = r.getChildDefinition();
552        }
553
554        if (r instanceof InstantiableRelationDefinition) {
555            InstantiableRelationDefinition<C, S> ir = (InstantiableRelationDefinition<C, S>) r;
556
557            if (name == null) {
558                throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
559                    + "\": no instance name for instantiable relation");
560            }
561
562            return InstantiableElement.create(ir, d, name);
563        } else if (r instanceof SetRelationDefinition) {
564            SetRelationDefinition<C, S> ir = (SetRelationDefinition<C, S>) r;
565
566            if (name != null) {
567                throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
568                    + "\": instance name specified for set relation");
569            }
570
571            return SetElement.create(ir, d);
572        } else if (r instanceof OptionalRelationDefinition) {
573            OptionalRelationDefinition<C, S> or = (OptionalRelationDefinition<C, S>) r;
574
575            if (name != null) {
576                throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
577                    + "\": instance name specified for optional relation");
578            }
579
580            return OptionalElement.create(or, d);
581        } else if (r instanceof SingletonRelationDefinition) {
582            SingletonRelationDefinition<C, S> sr = (SingletonRelationDefinition<C, S>) r;
583
584            if (name != null) {
585                throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
586                    + "\": instance name specified for singleton relation");
587            }
588
589            return SingletonElement.create(sr, d);
590        } else {
591            throw new IllegalArgumentException("Invalid path element \"" + element + "\" in path \"" + path
592                + "\": unsupported relation type");
593        }
594    }
595
596    /** The managed object definition in this path. */
597    private final AbstractManagedObjectDefinition<C, S> d;
598
599    /** The list of path elements in this path. */
600    private final List<Element<?, ?>> elements;
601
602    /** The last relation definition in this path. */
603    private final RelationDefinition<? super C, ? super S> r;
604
605    /** Private constructor. */
606    private ManagedObjectPath(LinkedList<Element<?, ?>> elements, RelationDefinition<? super C, ? super S> r,
607        AbstractManagedObjectDefinition<C, S> d) {
608        this.elements = Collections.unmodifiableList(elements);
609        this.r = r;
610        this.d = d;
611    }
612
613    /**
614     * Creates a new managed object path which has the same structure as this
615     * path except that the final path element is associated with the specified
616     * managed object definition.
617     *
618     * @param <C1>
619     *            The type of client managed object configuration that this path
620     *            will reference.
621     * @param <S1>
622     *            The type of server managed object configuration that this path
623     *            will reference.
624     * @param nd
625     *            The new managed object definition.
626     * @return Returns a new managed object path which has the same structure as
627     *         this path except that the final path element is associated with
628     *         the specified managed object definition.
629     */
630    // @Checkstyle:ignore
631    public <C1 extends C, S1 extends S> ManagedObjectPath<C1, S1> asSubType(AbstractManagedObjectDefinition<C1, S1> nd) {
632        if (r instanceof InstantiableRelationDefinition) {
633            InstantiableRelationDefinition<? super C, ? super S> ir =
634                (InstantiableRelationDefinition<? super C, ? super S>) r;
635            if (elements.size() == 0) {
636                return parent().child(ir, nd, "null");
637            } else {
638                return parent().child(ir, nd, elements.get(elements.size() - 1).getName());
639            }
640        } else if (r instanceof SetRelationDefinition) {
641            SetRelationDefinition<? super C, ? super S> sr = (SetRelationDefinition<? super C, ? super S>) r;
642            return parent().child(sr, nd);
643        } else if (r instanceof OptionalRelationDefinition) {
644            OptionalRelationDefinition<? super C, ? super S> or =
645                (OptionalRelationDefinition<? super C, ? super S>) r;
646            return parent().child(or, nd);
647        } else {
648            SingletonRelationDefinition<? super C, ? super S> sr =
649                (SingletonRelationDefinition<? super C, ? super S>) r;
650            return parent().child(sr, nd);
651        }
652    }
653
654    /**
655     * Creates a new child managed object path beneath the provided parent path
656     * having the specified managed object definition.
657     *
658     * @param <M>
659     *            The type of client managed object configuration that the child
660     *            path references.
661     * @param <N>
662     *            The type of server managed object configuration that the child
663     *            path references.
664     * @param r
665     *            The instantiable relation referencing the child.
666     * @param d
667     *            The managed object definition associated with the child (must
668     *            be a sub-type of the relation).
669     * @param name
670     *            The relative name of the child managed object.
671     * @return Returns a new child managed object path beneath the provided
672     *         parent path.
673     * @throws IllegalArgumentException
674     *             If the provided name is empty or blank.
675     */
676    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
677        InstantiableRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d, String name) {
678        if (name.trim().length() == 0) {
679            throw new IllegalArgumentException("Empty or blank managed object names are not allowed");
680        }
681        LinkedList<Element<?, ?>> celements = new LinkedList<>(elements);
682        celements.add(new InstantiableElement<M, N>(r, d, name));
683        return new ManagedObjectPath<>(celements, r, d);
684    }
685
686    /**
687     * Creates a new child managed object path beneath the provided parent path
688     * using the relation's child managed object definition.
689     *
690     * @param <M>
691     *            The type of client managed object configuration that the child
692     *            path references.
693     * @param <N>
694     *            The type of server managed object configuration that the child
695     *            path references.
696     * @param r
697     *            The instantiable relation referencing the child.
698     * @param name
699     *            The relative name of the child managed object.
700     * @return Returns a new child managed object path beneath the provided
701     *         parent path.
702     * @throws IllegalArgumentException
703     *             If the provided name is empty or blank.
704     */
705    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
706        InstantiableRelationDefinition<M, N> r, String name) {
707        return child(r, r.getChildDefinition(), name);
708    }
709
710    /**
711     * Creates a new child managed object path beneath the provided parent path
712     * having the specified managed object definition.
713     *
714     * @param <M>
715     *            The type of client managed object configuration that the child
716     *            path references.
717     * @param <N>
718     *            The type of server managed object configuration that the child
719     *            path references.
720     * @param r
721     *            The optional relation referencing the child.
722     * @param d
723     *            The managed object definition associated with the child (must
724     *            be a sub-type of the relation).
725     * @return Returns a new child managed object path beneath the provided
726     *         parent path.
727     */
728    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
729        OptionalRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
730        LinkedList<Element<?, ?>> celements = new LinkedList<>(elements);
731        celements.add(new OptionalElement<M, N>(r, d));
732        return new ManagedObjectPath<>(celements, r, d);
733    }
734
735    /**
736     * Creates a new child managed object path beneath the provided parent path
737     * using the relation's child managed object definition.
738     *
739     * @param <M>
740     *            The type of client managed object configuration that the child
741     *            path references.
742     * @param <N>
743     *            The type of server managed object configuration that the child
744     *            path references.
745     * @param r
746     *            The optional relation referencing the child.
747     * @return Returns a new child managed object path beneath the provided
748     *         parent path.
749     */
750    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
751        OptionalRelationDefinition<M, N> r) {
752        return child(r, r.getChildDefinition());
753    }
754
755    /**
756     * Creates a new child managed object path beneath the provided parent path
757     * having the specified managed object definition.
758     *
759     * @param <M>
760     *            The type of client managed object configuration that the child
761     *            path references.
762     * @param <N>
763     *            The type of server managed object configuration that the child
764     *            path references.
765     * @param r
766     *            The singleton relation referencing the child.
767     * @param d
768     *            The managed object definition associated with the child (must
769     *            be a sub-type of the relation).
770     * @return Returns a new child managed object path beneath the provided
771     *         parent path.
772     */
773    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
774        SingletonRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
775        LinkedList<Element<?, ?>> celements = new LinkedList<>(elements);
776        celements.add(new SingletonElement<M, N>(r, d));
777        return new ManagedObjectPath<>(celements, r, d);
778    }
779
780    /**
781     * Creates a new child managed object path beneath the provided parent path
782     * using the relation's child managed object definition.
783     *
784     * @param <M>
785     *            The type of client managed object configuration that the child
786     *            path references.
787     * @param <N>
788     *            The type of server managed object configuration that the child
789     *            path references.
790     * @param r
791     *            The singleton relation referencing the child.
792     * @return Returns a new child managed object path beneath the provided
793     *         parent path.
794     */
795    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
796        SingletonRelationDefinition<M, N> r) {
797        return child(r, r.getChildDefinition());
798    }
799
800    /**
801     * Creates a new child managed object path beneath the provided parent path
802     * having the specified managed object definition.
803     *
804     * @param <M>
805     *            The type of client managed object configuration that the child
806     *            path references.
807     * @param <N>
808     *            The type of server managed object configuration that the child
809     *            path references.
810     * @param r
811     *            The set relation referencing the child.
812     * @param d
813     *            The managed object definition associated with the child (must
814     *            be a sub-type of the relation).
815     * @return Returns a new child managed object path beneath the provided
816     *         parent path.
817     * @throws IllegalArgumentException
818     *             If the provided name is empty or blank.
819     */
820    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
821        SetRelationDefinition<? super M, ? super N> r, AbstractManagedObjectDefinition<M, N> d) {
822        LinkedList<Element<?, ?>> celements = new LinkedList<>(elements);
823        celements.add(new SetElement<M, N>(r, d));
824        return new ManagedObjectPath<>(celements, r, d);
825    }
826
827    /**
828     * Creates a new child managed object path beneath the provided parent path
829     * having the managed object definition indicated by <code>name</code>.
830     *
831     * @param <M>
832     *            The type of client managed object configuration that the path
833     *            references.
834     * @param <N>
835     *            The type of server managed object configuration that the path
836     *            references.
837     * @param r
838     *            The set relation referencing the child.
839     * @param name
840     *            The name of the managed object definition associated with the
841     *            child (must be a sub-type of the relation).
842     * @return Returns a new child managed object path beneath the provided
843     *         parent path.
844     * @throws IllegalArgumentException
845     *             If the provided name is empty or blank or specifies a managed
846     *             object definition which is not a sub-type of the relation's
847     *             child definition.
848     */
849    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<? extends M, ? extends N> child(
850        SetRelationDefinition<M, N> r, String name) {
851        AbstractManagedObjectDefinition<M, N> d = r.getChildDefinition();
852        return child(r, d.getChild(name));
853    }
854
855    /**
856     * Creates a new child managed object path beneath the provided parent path
857     * using the relation's child managed object definition.
858     *
859     * @param <M>
860     *            The type of client managed object configuration that the child
861     *            path references.
862     * @param <N>
863     *            The type of server managed object configuration that the child
864     *            path references.
865     * @param r
866     *            The set relation referencing the child.
867     * @return Returns a new child managed object path beneath the provided
868     *         parent path.
869     * @throws IllegalArgumentException
870     *             If the provided name is empty or blank.
871     */
872    public <M extends ConfigurationClient, N extends Configuration> ManagedObjectPath<M, N> child(
873        SetRelationDefinition<M, N> r) {
874        return child(r, r.getChildDefinition());
875    }
876
877    @Override
878    public boolean equals(Object obj) {
879        if (obj == this) {
880            return true;
881        } else if (obj instanceof ManagedObjectPath) {
882            ManagedObjectPath<?, ?> other = (ManagedObjectPath<?, ?>) obj;
883            return toString().equals(other.toString());
884        } else {
885            return false;
886        }
887    }
888
889    /**
890     * Get the definition of the managed object referred to by this path.
891     * <p>
892     * When the path is empty, the {@link RootCfgDefn} is returned.
893     *
894     * @return Returns the definition of the managed object referred to by this
895     *         path, or the {@link RootCfgDefn} if the path is empty.
896     */
897    public AbstractManagedObjectDefinition<C, S> getManagedObjectDefinition() {
898        return d;
899    }
900
901    /**
902     * Get the name of the managed object referred to by this path if
903     * applicable.
904     * <p>
905     * If there path does not refer to an instantiable managed object
906     * <code>null</code> is returned.
907     *
908     * @return Returns the name of the managed object referred to by this path,
909     *         or <code>null</code> if the managed object does not have a name.
910     */
911    public String getName() {
912        if (elements.isEmpty()) {
913            return null;
914        } else {
915            return elements.get(elements.size() - 1).getName();
916        }
917    }
918
919    /**
920     * Get the relation definition of the managed object referred to by this
921     * path.
922     * <p>
923     * When the path is empty, the <code>null</code> is returned.
924     *
925     * @return Returns the relation definition of the managed object referred to
926     *         by this path, or the <code>null</code> if the path is empty.
927     */
928    public RelationDefinition<? super C, ? super S> getRelationDefinition() {
929        return r;
930    }
931
932    @Override
933    public int hashCode() {
934        return toString().hashCode();
935    }
936
937    /**
938     * Determine whether this path contains any path elements.
939     *
940     * @return Returns <code>true</code> if this path does not contain any path
941     *         elements.
942     */
943    public boolean isEmpty() {
944        return elements.isEmpty();
945    }
946
947    /**
948     * Determines whether this managed object path references the same location
949     * as the provided managed object path.
950     * <p>
951     * This method differs from <code>equals</code> in that it ignores sub-type
952     * definitions.
953     *
954     * @param other
955     *            The managed object path to be compared.
956     * @return Returns <code>true</code> if this managed object path references
957     *         the same location as the provided managed object path.
958     */
959    public boolean matches(ManagedObjectPath<?, ?> other) {
960        DN thisDN = toDN();
961        DN otherDN = other.toDN();
962        return thisDN.equals(otherDN);
963    }
964
965    /**
966     * Creates a new parent managed object path representing the immediate
967     * parent of this path. This method is a short-hand for
968     * <code>parent(1)</code>.
969     *
970     * @return Returns a new parent managed object path representing the
971     *         immediate parent of this path.
972     * @throws IllegalArgumentException
973     *             If this path does not have a parent (i.e. it is the empty
974     *             path).
975     */
976    public ManagedObjectPath<?, ?> parent() {
977        return parent(1);
978    }
979
980    /**
981     * Creates a new parent managed object path the specified number of path
982     * elements above this path.
983     *
984     * @param offset
985     *            The number of path elements (0 - means no offset, 1 means the
986     *            parent, and 2 means the grand-parent).
987     * @return Returns a new parent managed object path the specified number of
988     *         path elements above this path.
989     * @throws IllegalArgumentException
990     *             If the offset is less than 0, or greater than the number of
991     *             path elements in this path.
992     */
993    public ManagedObjectPath<?, ?> parent(int offset) {
994        if (offset < 0) {
995            throw new IllegalArgumentException("Negative offset");
996        }
997
998        if (offset > elements.size()) {
999            throw new IllegalArgumentException("Offset is greater than the number of path elements");
1000        }
1001
1002        // An offset of 0 leaves the path unchanged.
1003        if (offset == 0) {
1004            return this;
1005        }
1006
1007        // Return the empty path if the parent has zero elements.
1008        if (elements.size() == offset) {
1009            return emptyPath();
1010        }
1011
1012        LinkedList<Element<?, ?>> celements = new LinkedList<>(elements.subList(0, elements.size() - offset));
1013        return create(celements, celements.getLast());
1014    }
1015
1016    /**
1017     * Creates a new managed object path which has the same structure as this
1018     * path except that the final path element is renamed. The final path
1019     * element must comprise of an instantiable relation.
1020     *
1021     * @param newName
1022     *            The new name of the final path element.
1023     * @return Returns a new managed object path which has the same structure as
1024     *         this path except that the final path element is renamed.
1025     * @throws IllegalStateException
1026     *             If this managed object path is empty or if its final path
1027     *             element does not comprise of an instantiable relation.
1028     */
1029    public ManagedObjectPath<C, S> rename(String newName) {
1030        if (elements.size() == 0) {
1031            throw new IllegalStateException("Cannot rename an empty path");
1032        }
1033
1034        if (r instanceof InstantiableRelationDefinition) {
1035            InstantiableRelationDefinition<? super C, ? super S> ir =
1036                (InstantiableRelationDefinition<? super C, ? super S>) r;
1037            return parent().child(ir, d, newName);
1038        } else {
1039            throw new IllegalStateException("Not an instantiable relation");
1040        }
1041    }
1042
1043    /**
1044     * Serialize this managed object path using the provided serialization
1045     * strategy.
1046     * <p>
1047     * The path elements will be passed to the serializer in big-endian order:
1048     * starting from the root element and proceeding down to the leaf.
1049     *
1050     * @param serializer
1051     *            The managed object path serialization strategy.
1052     */
1053    public void serialize(ManagedObjectPathSerializer serializer) {
1054        for (Element<?, ?> element : elements) {
1055            element.serialize(serializer);
1056        }
1057    }
1058
1059    /**
1060     * Get the number of path elements in this managed object path.
1061     *
1062     * @return Returns the number of path elements (0 - means no offset, 1 means
1063     *         the parent, and 2 means the grand-parent).
1064     */
1065    public int size() {
1066        return elements.size();
1067    }
1068
1069    /**
1070     * Creates a DN representation of this managed object path.
1071     *
1072     * @return Returns a DN representation of this managed object path.
1073     */
1074    public DN toDN() {
1075        // Use a simple serializer to create the contents.
1076        DNSerializer serializer = new DNSerializer();
1077        serialize(serializer);
1078        return serializer.toDN();
1079    }
1080
1081    @Override
1082    public String toString() {
1083        StringBuilder builder = new StringBuilder();
1084        toString(builder);
1085        return builder.toString();
1086    }
1087
1088    /**
1089     * Appends a string representation of this managed object path to the
1090     * provided string builder.
1091     *
1092     * @param builder
1093     *            Append the string representation to this builder.
1094     * @see #toString()
1095     */
1096    public void toString(final StringBuilder builder) {
1097        if (isEmpty()) {
1098            // Special treatment of root configuration paths.
1099            builder.append('/');
1100        } else {
1101            // Use a simple serializer to create the contents.
1102            ManagedObjectPathSerializer serializer = new StringSerializer(builder);
1103            serialize(serializer);
1104        }
1105    }
1106
1107}