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