001/*
002// This software is subject to the terms of the Eclipse Public License v1.0
003// Agreement, available at the following URL:
004// http://www.eclipse.org/legal/epl-v10.html.
005// You must accept the terms of that agreement to use this software.
006//
007// Copyright (C) 2011-2011 Pentaho
008// All Rights Reserved.
009*/
010package mondrian.olap;
011
012import org.olap4j.mdx.IdentifierNode;
013import org.olap4j.mdx.IdentifierSegment;
014
015import java.util.List;
016
017/**
018 * Resolves a list of segments (a parsed identifier) to an OLAP element.
019 */
020public final class NameResolver {
021
022    /**
023     * Creates a NameResolver.
024     */
025    public NameResolver() {
026    }
027
028    /**
029     * Resolves a list of segments (a parsed identifier) to an OLAP element.
030     *
031     * @param parent Parent element to search in, usually a cube
032     * @param segments Exploded compound name, such as {"Products",
033     *   "Product Department", "Produce"}
034     * @param failIfNotFound If the element is not found, determines whether
035     *   to return null or throw an error
036     * @param category Type of returned element, a {@link Category} value;
037     *   {@link Category#Unknown} if it doesn't matter.
038     * @param matchType Match type
039     * @param namespaces Namespaces wherein to find child element at each step
040     * @return OLAP element with given name, or null if not found
041     */
042    public OlapElement resolve(
043        OlapElement parent,
044        List<IdentifierSegment> segments,
045        boolean failIfNotFound,
046        int category,
047        MatchType matchType,
048        List<Namespace> namespaces)
049    {
050        OlapElement element;
051        if (matchType == MatchType.EXACT) {
052            element = resolveExact(
053                parent,
054                segments,
055                namespaces);
056        } else {
057            element = resolveInexact(
058                parent,
059                segments,
060                matchType,
061                namespaces);
062        }
063        if (element != null) {
064            element = nullify(category, element);
065        }
066        if (element == null && failIfNotFound) {
067            throw Util.newElementNotFoundException(
068                category,
069                new IdentifierNode(segments));
070        }
071        return element;
072    }
073
074    private OlapElement resolveInexact(
075        OlapElement parent,
076        List<IdentifierSegment> segments,
077        MatchType matchType,
078        List<Namespace> namespaces)
079    {
080        OlapElement element = parent;
081        for (final IdentifierSegment segment : segments) {
082            assert element != null;
083            OlapElement child = null;
084            for (Namespace namespace : namespaces) {
085                child = namespace.lookupChild(element, segment, matchType);
086                if (child != null) {
087                    switch (matchType) {
088                    case EXACT:
089                    case EXACT_SCHEMA:
090                        break;
091                    case BEFORE:
092                        if (!Util.matches(segment, child.getName())) {
093                            matchType = MatchType.LAST;
094                        }
095                        break;
096                    case AFTER:
097                        if (!Util.matches(segment, child.getName())) {
098                            matchType = MatchType.FIRST;
099                        }
100                        break;
101                    }
102                    break;
103                }
104            }
105            if (child == null) {
106                return null;
107            }
108            element = child;
109        }
110        return element;
111    }
112
113    // same logic as resolveInexact, pared down for common case
114    // matchType == EXACT
115    private OlapElement resolveExact(
116        OlapElement parent,
117        List<IdentifierSegment> segments,
118        List<Namespace> namespaces)
119    {
120        OlapElement element = parent;
121        for (final IdentifierSegment segment : segments) {
122            assert element != null;
123            OlapElement child = null;
124            for (Namespace namespace : namespaces) {
125                child = namespace.lookupChild(element, segment);
126                if (child != null) {
127                    break;
128                }
129            }
130            if (child == null) {
131                return null;
132            }
133            element = child;
134        }
135        return element;
136    }
137
138    /**
139     * Converts an element to the required type, converting if possible,
140     * returning null if it is not of the required type and cannot be converted.
141     *
142     * @param category Desired category of element
143     * @param element Element
144     * @return Element of the desired category, or null
145     */
146    private OlapElement nullify(int category, OlapElement element) {
147        switch (category) {
148        case Category.Unknown:
149            return element;
150        case Category.Member:
151            return element instanceof Member ? element : null;
152        case Category.Level:
153            return element instanceof Level ? element : null;
154        case Category.Hierarchy:
155            if (element instanceof Hierarchy) {
156                return element;
157            } else if (element instanceof Dimension) {
158                final Dimension dimension = (Dimension) element;
159                final Hierarchy[] hierarchies = dimension.getHierarchies();
160                if (hierarchies.length == 1) {
161                    return hierarchies[0];
162                }
163                return null;
164            } else {
165                return null;
166            }
167        case Category.Dimension:
168            return element instanceof Dimension ? element : null;
169        case Category.Set:
170            return element instanceof NamedSet ? element : null;
171        default:
172            throw Util.newInternal("unexpected: " + category);
173        }
174    }
175
176    /**
177     * Returns whether a formula (representing a calculated member or named
178     * set) matches a given parent and name segment.
179     *
180     * @param formula Formula
181     * @param parent Parent element
182     * @param segment Name segment
183     * @return Whether formula matches
184     */
185    public static boolean matches(
186        Formula formula,
187        OlapElement parent,
188        IdentifierSegment segment)
189    {
190        if (!Util.matches(segment, formula.getName())) {
191            return false;
192        }
193        if (formula.isMember()) {
194            final Member formulaMember = formula.getMdxMember();
195            if (formulaMember.getParentMember() != null) {
196                if (parent instanceof Member) {
197                    // SSAS matches calc members very loosely. For example,
198                    // [Foo].[Z] will match calc member [Foo].[X].[Y].[Z].
199                    return formulaMember.getParentMember().isChildOrEqualTo(
200                        (Member) parent);
201                } else if (parent instanceof Hierarchy) {
202                    return formulaMember.getParentMember().getHierarchy()
203                        .equals(parent);
204                } else {
205                    return parent.getUniqueName().equals(
206                        formulaMember.getParentMember().getUniqueName());
207                }
208            } else {
209                // If parent is not a member, member must be a root member.
210                return parent.equals(formulaMember.getHierarchy())
211                    || parent.equals(formulaMember.getDimension());
212            }
213        } else {
214            return parent instanceof Cube;
215        }
216    }
217
218    /**
219     * Naming context within which elements are defined.
220     *
221     * <p>Elements' names are hierarchical, so elements are resolved one
222     * name segment at a time. It is possible for an element to be defined
223     * in a different namespace than its parent: for example, stored member
224     * [Dim].[Hier].[X].[Y] might have a child [Dim].[Hier].[X].[Y].[Z] which
225     * is a calculated member defined using a WITH MEMBER clause.</p>
226     */
227    public interface Namespace {
228        /**
229         * Looks up a child element, using a match type for inexact matching.
230         *
231         * <p>If {@code matchType} is {@link MatchType#EXACT}, effect is
232         * identical to calling
233         * {@link #lookupChild(OlapElement, org.olap4j.mdx.IdentifierSegment)}.</p>
234         *
235         * <p>Match type is ignored except when searching for members.</p>
236         *
237         * @param parent Parent element
238         * @param segment Name segment
239         * @param matchType Match type
240         * @return Olap element, or null
241         */
242        OlapElement lookupChild(
243            OlapElement parent,
244            IdentifierSegment segment,
245            MatchType matchType);
246
247        /**
248         * Looks up a child element.
249         *
250         * @param parent Parent element
251         * @param segment Name segment
252         * @return Olap element, or null
253         */
254        OlapElement lookupChild(
255            OlapElement parent,
256            IdentifierSegment segment);
257    }
258}
259
260// End NameResolver.java