/*
* This software is subject to the terms of the Eclipse Public License v1.0
* Agreement, available at the following URL:
* http://www.eclipse.org/legal/epl-v10.html.
* You must accept the terms of that agreement to use this software.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package mondrian.olap;
import org.olap4j.mdx.IdentifierNode;
import org.olap4j.mdx.IdentifierSegment;
import java.util.List;
/**
* Resolves a list of segments (a parsed identifier) to an OLAP element.
*/
public final class NameResolver {
/**
* Creates a NameResolver.
*/
public NameResolver() {
}
/**
* Resolves a list of segments (a parsed identifier) to an OLAP element.
*
* @param parent Parent element to search in, usually a cube
* @param segments Exploded compound name, such as {"Products",
* "Product Department", "Produce"}
* @param failIfNotFound If the element is not found, determines whether
* to return null or throw an error
* @param category Type of returned element, a {@link Category} value;
* {@link Category#Unknown} if it doesn't matter.
* @param matchType Match type
* @param namespaces Namespaces wherein to find child element at each step
* @return OLAP element with given name, or null if not found
*/
public OlapElement resolve(
OlapElement parent,
List<IdentifierSegment> segments,
boolean failIfNotFound,
int category,
MatchType matchType,
List<Namespace> namespaces)
{
OlapElement element;
if (matchType == MatchType.EXACT) {
element = resolveExact(
parent,
segments,
namespaces);
} else {
element = resolveInexact(
parent,
segments,
matchType,
namespaces);
}
if (element != null) {
element = nullify(category, element);
}
if (element == null && failIfNotFound) {
throw Util.newElementNotFoundException(
category,
new IdentifierNode(segments));
}
return element;
}
private OlapElement resolveInexact(
OlapElement parent,
List<IdentifierSegment> segments,
MatchType matchType,
List<Namespace> namespaces)
{
OlapElement element = parent;
for (final IdentifierSegment segment : segments) {
assert element != null;
OlapElement child = null;
for (Namespace namespace : namespaces) {
child = namespace.lookupChild(element, segment, matchType);
if (child != null) {
switch (matchType) {
case EXACT:
case EXACT_SCHEMA:
break;
case BEFORE:
if (!Util.matches(segment, child.getName())) {
matchType = MatchType.LAST;
}
break;
case AFTER:
if (!Util.matches(segment, child.getName())) {
matchType = MatchType.FIRST;
}
break;
}
break;
}
}
if (child == null) {
return null;
}
element = child;
}
return element;
}
// same logic as resolveInexact, pared down for common case
// matchType == EXACT
private OlapElement resolveExact(
OlapElement parent,
List<IdentifierSegment> segments,
List<Namespace> namespaces)
{
OlapElement element = parent;
for (final IdentifierSegment segment : segments) {
assert element != null;
OlapElement child = null;
for (Namespace namespace : namespaces) {
child = namespace.lookupChild(element, segment);
if (child != null) {
break;
}
}
if (child == null) {
return null;
}
element = child;
}
return element;
}
/**
* Converts an element to the required type, converting if possible,
* returning null if it is not of the required type and cannot be converted.
*
* @param category Desired category of element
* @param element Element
* @return Element of the desired category, or null
*/
private OlapElement nullify(int category, OlapElement element) {
switch (category) {
case Category.Unknown:
return element;
case Category.Member:
return element instanceof Member ? element : null;
case Category.Level:
return element instanceof Level ? element : null;
case Category.Hierarchy:
if (element instanceof Hierarchy) {
return element;
} else if (element instanceof Dimension) {
final Dimension dimension = (Dimension) element;
final Hierarchy[] hierarchies = dimension.getHierarchies();
if (hierarchies.length == 1) {
return hierarchies[0];
}
return null;
} else {
return null;
}
case Category.Dimension:
return element instanceof Dimension ? element : null;
case Category.Set:
return element instanceof NamedSet ? element : null;
default:
throw Util.newInternal("unexpected: " + category);
}
}
/**
* Returns whether a formula (representing a calculated member or named
* set) matches a given parent and name segment.
*
* @param formula Formula
* @param parent Parent element
* @param segment Name segment
* @return Whether formula matches
*/
public static boolean matches(
Formula formula,
OlapElement parent,
IdentifierSegment segment)
{
if (!Util.matches(segment, formula.getName())) {
return false;
}
if (formula.isMember()) {
final Member formulaMember = formula.getMdxMember();
if (formulaMember.getParentMember() != null) {
if (parent instanceof Member) {
// SSAS matches calc members very loosely. For example,
// [Foo].[Z] will match calc member [Foo].[X].[Y].[Z].
return formulaMember.getParentMember().isChildOrEqualTo(
(Member) parent);
} else if (parent instanceof Hierarchy) {
return formulaMember.getParentMember().getHierarchy()
.equals(parent);
} else {
return parent.getUniqueName().equals(
formulaMember.getParentMember().getUniqueName());
}
} else {
// If parent is not a member, member must be a root member.
return parent.equals(formulaMember.getHierarchy())
|| parent.equals(formulaMember.getDimension());
}
} else {
return parent instanceof Cube;
}
}
/**
* Naming context within which elements are defined.
*
* <p>Elements' names are hierarchical, so elements are resolved one
* name segment at a time. It is possible for an element to be defined
* in a different namespace than its parent: for example, stored member
* [Dim].[Hier].[X].[Y] might have a child [Dim].[Hier].[X].[Y].[Z] which
* is a calculated member defined using a WITH MEMBER clause.</p>
*/
public interface Namespace {
/**
* Looks up a child element, using a match type for inexact matching.
*
* <p>If {@code matchType} is {@link MatchType#EXACT}, effect is
* identical to calling
* {@link #lookupChild(OlapElement, org.olap4j.mdx.IdentifierSegment)}.</p>
*
* <p>Match type is ignored except when searching for members.</p>
*
* @param parent Parent element
* @param segment Name segment
* @param matchType Match type
* @return Olap element, or null
*/
OlapElement lookupChild(
OlapElement parent,
IdentifierSegment segment,
MatchType matchType);
/**
* Looks up a child element.
*
* @param parent Parent element
* @param segment Name segment
* @return Olap element, or null
*/
OlapElement lookupChild(
OlapElement parent,
IdentifierSegment segment);
}
}
// End NameResolver.java