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 2010-2011 ApexIdentity Inc.
015 * Portions Copyright 2011-2015 ForgeRock AS.
016 */
017
018package org.forgerock.openig.resolver;
019
020import java.util.ArrayList;
021import java.util.Arrays;
022import java.util.Collections;
023import java.util.Iterator;
024import java.util.List;
025import java.util.Map;
026
027import org.forgerock.http.util.Loader;
028
029/**
030 * Performs object resolution by object type. A given object may have more than
031 * one resolver, depending on what class it extends and/or interfaces it
032 * implements, or what its superclasses and interfaces are.
033 */
034public final class Resolvers {
035
036    /**
037     * Resolver that handles native arrays (not handled like the service-based
038     * resolvers).
039     */
040    private static final List<Resolver> ARRAY_RESOLVER = Collections.unmodifiableList(Arrays
041            .asList((Resolver) new ArrayResolver()));
042
043    /** Mapping of supported classes to associated resolvers. */
044    @SuppressWarnings("rawtypes")
045    public static final Map<Class, Resolver> SERVICES = Collections.unmodifiableMap(Loader.loadMap(
046            Class.class, Resolver.class));
047
048    /** Static methods only. */
049    private Resolvers() {
050    }
051
052    /**
053     * Provides an iterable object over the resolvers that are appropriate for a
054     * particular object. Resolvers are provided ordered from most specific to
055     * class/interface to least. Resolvers are provided through an iterator
056     * interface to avoid the overhead of determining all resolvers in advance.
057     *
058     * @param object the object for which a set of resolvers is being sought.
059     * @return an object that returns an iterator over the set of resolvers for
060     * the object.
061     */
062    public static Iterable<Resolver> resolvers(final Object object) {
063        return new Iterable<Resolver>() {
064            public Iterator<Resolver> iterator() {
065                return (object.getClass().isArray() ? ARRAY_RESOLVER.iterator() : new Iterator<Resolver>() {
066                    Class<?> class1 = object.getClass();
067                    Class<?> class2 = class1;
068                    Iterator<Class<?>> interfaces = null;
069                    int n = 0;
070
071                    public boolean hasNext() {
072                        // interface hierarchy not yet exhausted
073                        return (class2 != null);
074                    }
075
076                    public Resolver next() {
077                        while (class1 != null) {
078                            // class hierarchy
079                            Resolver resolver = SERVICES.get(class1);
080                            class1 = class1.getSuperclass();
081                            if (resolver != null) {
082                                return resolver;
083                            }
084                        }
085                        // exhausted class hierarchy
086                        class1 = null;
087                        while (class2 != null && class2 != Object.class) {
088                            // interface hierarchy
089                            if (interfaces != null && interfaces.hasNext()) {
090                                Resolver resolver = SERVICES.get(interfaces.next());
091                                if (resolver != null) {
092                                    return resolver;
093                                }
094                            } else {
095                                List<Class<?>> list = getInterfaces(class2, n++);
096                                if (list.size() > 0) {
097                                    interfaces = list.iterator();
098                                } else {
099                                    class2 = class2.getSuperclass();
100                                    n = 0;
101                                }
102                            }
103                        }
104                        // exhausted interface hierarchy
105                        class2 = null;
106                        return new Unresolver();
107                    }
108
109                    public void remove() {
110                        throw new UnsupportedOperationException();
111                    }
112                });
113            }
114        };
115    }
116
117    /**
118     * Attempts to resolve an element of an object.
119     *
120     * @param object the object in which to resolve the specified element.
121     * @param element the element to resolve within the specified object.
122     * @return the value of the resolved element, or {@link Resolver#UNRESOLVED
123     * UNRESOLVED} if it cannot be resolved.
124     * @see Resolver#get(Object, Object)
125     */
126    public static Object get(Object object, Object element) {
127        for (Resolver resolver : resolvers(object)) {
128            Object value = resolver.get(object, element);
129            if (value != Resolver.UNRESOLVED) {
130                // first hit wins
131                return value;
132            }
133        }
134        return Resolver.UNRESOLVED;
135    }
136
137    /**
138     * Attempts to set the value of an element of an object.
139     *
140     * @param object the object in which to resolve the specified element.
141     * @param element the element within the specified object whose value is to be
142     * set.
143     * @param value the value to set the element to.
144     * @return the previous value of the element, {@code null} if no previous
145     * value, or {@link Resolver#UNRESOLVED UNRESOLVED} if it cannot be
146     * resolved.
147     * @see Resolver#put(Object, Object, Object)
148     */
149    public static Object put(Object object, Object element, Object value) {
150        for (Resolver resolver : resolvers(object)) {
151            Object resolved = resolver.put(object, element, value);
152            if (resolved != Resolver.UNRESOLVED) {
153                // first hit wins
154                return resolved;
155            }
156        }
157        return Resolver.UNRESOLVED;
158    }
159
160    private static List<Class<?>> getInterfaces(Class<?> c, int level) {
161        List<Class<?>> interfaces;
162        if (level == 0) {
163            interfaces = Arrays.asList(c.getInterfaces());
164        } else {
165            interfaces = new ArrayList<>();
166            for (Class<?> iface : c.getInterfaces()) {
167                // recursion
168                interfaces.addAll(getInterfaces(iface, level - 1));
169            }
170        }
171        return interfaces;
172    }
173}