001 /*
002 // $Id: //open/mondrian-release/3.1/src/main/mondrian/rolap/RolapMember.java#5 $
003 // This software is subject to the terms of the Eclipse Public License v1.0
004 // Agreement, available at the following URL:
005 // http://www.eclipse.org/legal/epl-v10.html.
006 // Copyright (C) 2001-2002 Kana Software, Inc.
007 // Copyright (C) 2001-2009 Julian Hyde and others
008 // All Rights Reserved.
009 // You must accept the terms of that agreement to use this software.
010 //
011 // jhyde, 10 August, 2001
012 */
013
014 package mondrian.rolap;
015
016 import mondrian.olap.*;
017 import mondrian.util.ObjectFactory;
018 import mondrian.util.CreationException;
019
020 import org.apache.commons.collections.map.Flat3Map;
021 import org.apache.log4j.Logger;
022 import org.eigenbase.util.property.StringProperty;
023
024 import java.util.*;
025
026 /**
027 * A <code>RolapMember</code> is a member of a {@link RolapHierarchy}. There are
028 * sub-classes for {@link RolapStoredMeasure}, {@link RolapCalculatedMember}.
029 *
030 * @author jhyde
031 * @since 10 August, 2001
032 * @version $Id: //open/mondrian-release/3.1/src/main/mondrian/rolap/RolapMember.java#5 $
033 */
034 public class RolapMember extends MemberBase {
035
036 private static final Logger LOGGER = Logger.getLogger(RolapMember.class);
037
038 /**
039 * For members of a level with an ordinal expression defined, the
040 * value of that expression for this member as retrieved via JDBC;
041 * otherwise null.
042 */
043 private Comparable orderKey;
044
045 /**
046 * This returns an array of member arrays where the first member
047 * array are the root members while the last member array are the
048 * leaf members.
049 * <p>
050 * If you know that you will need to get all or most of the members of
051 * a hierarchy, then calling this which gets all of the hierarchy's
052 * members all at once is much faster than getting members one at
053 * a time.
054 *
055 * @param schemaReader Schema reader
056 * @param hierarchy Hierarchy
057 * @return List of arrays of members
058 */
059 public static List<List<Member>> getAllMembers(
060 SchemaReader schemaReader,
061 Hierarchy hierarchy)
062 {
063 long start = System.currentTimeMillis();
064
065 try {
066 // Getting the members by Level is the fastest way that I could
067 // find for getting all of a hierarchy's members.
068 List<List<Member>> list = new ArrayList<List<Member>>();
069 Level[] levels = hierarchy.getLevels();
070 for (Level level : levels) {
071 List<Member> members =
072 schemaReader.getLevelMembers(level, true);
073 if (members != null) {
074 list.add(members);
075 }
076 }
077 return list;
078 } finally {
079 if (LOGGER.isDebugEnabled()) {
080 long end = System.currentTimeMillis();
081 LOGGER.debug(
082 "RolapMember.getAllMembers: time=" + (end - start));
083 }
084 }
085 }
086
087 public static int getHierarchyCardinality(
088 SchemaReader schemaReader,
089 Hierarchy hierarchy)
090 {
091 int cardinality = 0;
092 Level[] levels = hierarchy.getLevels();
093 for (Level level1 : levels) {
094 cardinality += schemaReader.getLevelCardinality(level1, true, true);
095 }
096 return cardinality;
097 }
098
099 /**
100 * Sets member ordinal values using a Bottom-up/Top-down algorithm.
101 *
102 * <p>Gets an array of members for each level and traverses
103 * array for the lowest level, setting each member's
104 * parent's parent's etc. member's ordinal if not set working back
105 * down to the leaf member and then going to the next leaf member
106 * and traversing up again.
107 *
108 * <p>The above algorithm only works for a hierarchy that has all of its
109 * leaf members in the same level (that is, a non-ragged hierarchy), which
110 * is the norm. After all member ordinal values have been set, traverses
111 * the array of members, making sure that all members' ordinals have been
112 * set. If one is found that is not set, then one must to a full Top-down
113 * setting of the ordinals.
114 *
115 * <p>The Bottom-up/Top-down algorithm is MUCH faster than the Top-down
116 * algorithm.
117 *
118 * @param schemaReader Schema reader
119 * @param seedMember Member
120 */
121 public static void setOrdinals(
122 SchemaReader schemaReader,
123 Member seedMember)
124 {
125 /*
126 * The following are times for executing different set ordinals
127 * algorithms for both the FoodMart Sales cube/Store dimension
128 * and a Large Data set with a dimension with about 250,000 members.
129 *
130 * Times:
131 * Original setOrdinals Top-down
132 * Foodmart: 63ms
133 * Large Data set: 651865ms
134 * Calling getAllMembers before calling original setOrdinals Top-down
135 * Foodmart: 32ms
136 * Large Data set: 73880ms
137 * Bottom-up/Top-down
138 * Foodmart: 17ms
139 * Large Data set: 4241ms
140 */
141 long start = System.currentTimeMillis();
142
143 try {
144 Hierarchy hierarchy = seedMember.getHierarchy();
145 int ordinal = hierarchy.hasAll() ? 1 : 0;
146 List<List<Member>> levelMembers =
147 getAllMembers(schemaReader, hierarchy);
148 List<Member> leafMembers =
149 levelMembers.get(levelMembers.size() - 1);
150 levelMembers = levelMembers.subList(0, levelMembers.size() - 1);
151
152 // Set all ordinals
153 for (Member child : leafMembers) {
154 ordinal = bottomUpSetParentOrdinals(ordinal, child);
155 ordinal = setOrdinal(child, ordinal);
156 }
157
158 boolean needsFullTopDown = needsFullTopDown(levelMembers);
159
160 // If we must to a full Top-down, then first reset all ordinal
161 // values to -1, and then call the Top-down
162 if (needsFullTopDown) {
163 for (List<Member> members : levelMembers) {
164 for (Member member : members) {
165 if (member instanceof RolapMember) {
166 ((RolapMember) member).resetOrdinal();
167 }
168 }
169 }
170
171 // call full Top-down
172 setOrdinalsTopDown(schemaReader, seedMember);
173 }
174 } finally {
175 if (LOGGER.isDebugEnabled()) {
176 long end = System.currentTimeMillis();
177 LOGGER.debug("RolapMember.setOrdinals: time=" + (end - start));
178 }
179 }
180 }
181
182 /**
183 * Returns whether the ordinal assignment algorithm needs to perform
184 * the more expensive top-down algorithm. If the hierarchy is 'uneven', not
185 * all leaf members are at the same level, then bottom-up setting of
186 * ordinals will have missed some.
187 *
188 * @param levelMembers Array containing the list of members in each level
189 * except the leaf level
190 * @return whether we need to apply the top-down ordinal assignment
191 */
192 private static boolean needsFullTopDown(List<List<Member>> levelMembers) {
193 for (List<Member> members : levelMembers) {
194 for (Member member : members) {
195 if (member.getOrdinal() == -1) {
196 return true;
197 }
198 }
199 }
200 return false;
201 }
202
203 /**
204 * Walks up the hierarchy, setting the ordinals of ancestors until it
205 * reaches the root or hits an ancestor whose ordinal has already been
206 * assigned.
207 *
208 * <p>Assigns the given ordinal to the ancestor nearest the root which has
209 * not been assigned an ordinal, and increments by one for each descendant.
210 *
211 * @param ordinal Ordinal to assign to deepest ancestor
212 * @param child Member whose ancestors ordinals to set
213 * @return Ordinal, incremented for each time it was used
214 */
215 private static int bottomUpSetParentOrdinals(int ordinal, Member child) {
216 Member parent = child.getParentMember();
217 if ((parent != null) && parent.getOrdinal() == -1) {
218 ordinal = bottomUpSetParentOrdinals(ordinal, parent);
219 ordinal = setOrdinal(parent, ordinal);
220 }
221 return ordinal;
222 }
223
224 private static int setOrdinal(Member member, int ordinal) {
225 if (member instanceof RolapMember) {
226 ((RolapMember) member).setOrdinal(ordinal++);
227 } else {
228 // TODO
229 LOGGER.warn(
230 "RolapMember.setAllChildren: NOT RolapMember "
231 + "member.name=" + member.getName()
232 + ", member.class=" + member.getClass().getName()
233 + ", ordinal=" + ordinal);
234 ordinal++;
235 }
236 return ordinal;
237 }
238
239 /**
240 * Sets ordinals of a complete member hierarchy as required by the
241 * MEMBER_ORDINAL XMLA element using a depth-first algorithm.
242 *
243 * <p>For big hierarchies it takes a bunch of time. SQL Server is
244 * relatively fast in comparison so it might be storing such
245 * information in the DB.
246 *
247 * @param schemaReader Schema reader
248 * @param member Member
249 */
250 private static void setOrdinalsTopDown(
251 SchemaReader schemaReader,
252 Member member)
253 {
254 long start = System.currentTimeMillis();
255
256 try {
257 Member parent = schemaReader.getMemberParent(member);
258
259 if (parent == null) {
260 // top of the world
261 int ordinal = 0;
262
263 List<Member> siblings =
264 schemaReader.getHierarchyRootMembers(member.getHierarchy());
265
266 for (Member sibling : siblings) {
267 ordinal = setAllChildren(ordinal, schemaReader, sibling);
268 }
269
270 } else {
271 setOrdinalsTopDown(schemaReader, parent);
272 }
273 } finally {
274 if (LOGGER.isDebugEnabled()) {
275 long end = System.currentTimeMillis();
276 LOGGER.debug(
277 "RolapMember.setOrdinalsTopDown: time=" + (end - start));
278 }
279 }
280 }
281
282 private static int setAllChildren(
283 int ordinal,
284 SchemaReader schemaReader,
285 Member member)
286 {
287 ordinal = setOrdinal(member, ordinal);
288
289 List<Member> children = schemaReader.getMemberChildren(member);
290 for (Member child : children) {
291 ordinal = setAllChildren(ordinal, schemaReader, child);
292 }
293
294 return ordinal;
295 }
296
297 /**
298 * Sets a member's parent.
299 *
300 * <p>Can screw up the caching structure. Only to be called by
301 * {@link mondrian.olap.CacheControl#createMoveCommand}.
302 *
303 * <p>New parent must be in same level as old parent.
304 *
305 * @param parentMember New parent member
306 *
307 * @see #getParentMember()
308 * @see #getParentUniqueName()
309 */
310 void setParentMember(RolapMember parentMember) {
311 final RolapMember previousParentMember = getParentMember();
312 if (previousParentMember.getLevel() != parentMember.getLevel()) {
313 throw new IllegalArgumentException(
314 "new parent belongs to different level than old");
315 }
316 this.parentMember = parentMember;
317 this.parentUniqueName = parentMember.getUniqueName();
318 }
319
320 /**
321 * Converts a key to a string to be used as part of the member's name
322 * and unique name.
323 *
324 * <p>Usually, it just calls {@link Object#toString}. But if the key is an
325 * integer value represented in a floating-point column, we'd prefer the
326 * integer value. For example, one member of the
327 * <code>[Sales].[Store SQFT]</code> dimension comes out "20319.0" but we'd
328 * like it to be "20319".
329 */
330 protected static String keyToString(Object key) {
331 String name;
332 if (key == null || RolapUtil.sqlNullValue.equals(key)) {
333 name = RolapUtil.mdxNullLiteral;
334 } else if (key instanceof Id.Segment) {
335 name = ((Id.Segment) key).name;
336 } else {
337 name = key.toString();
338 }
339 if ((key instanceof Number) && name.endsWith(".0")) {
340 name = name.substring(0, name.length() - 2);
341 }
342 return name;
343 }
344
345 /** Ordinal of the member within the hierarchy. Some member readers do not
346 * use this property; in which case, they should leave it as its default,
347 * -1. */
348 private int ordinal;
349 private final Object key;
350
351 /**
352 * Maps property name to property value.
353 *
354 * <p> We expect there to be a lot of members, but few of them will
355 * have properties. So to reduce memory usage, when empty, this is set to
356 * an immutable empty set.
357 */
358 private Map<String, Object> mapPropertyNameToValue;
359
360 /**
361 * Creates a RolapMember
362 *
363 * @param parentMember Parent member
364 * @param level Level this member belongs to
365 * @param key Key to this member in the underlying RDBMS
366 * @param name Name of this member
367 * @param memberType Type of member
368 */
369 protected RolapMember(
370 RolapMember parentMember,
371 RolapLevel level,
372 Object key,
373 String name,
374 MemberType memberType)
375 {
376 super(parentMember, level, memberType);
377 if (key instanceof byte[]) {
378 // Some drivers (e.g. Derby) return byte arrays for binary columns
379 // but byte arrays do not implement Comparable
380 this.key = new String((byte[])key);
381 } else {
382 this.key = key;
383 }
384 this.ordinal = -1;
385 this.mapPropertyNameToValue = Collections.emptyMap();
386
387 if (name != null
388 && !(key != null && name.equals(key.toString())))
389 {
390 // Save memory by only saving the name as a property if it's
391 // different from the key.
392 setProperty(Property.NAME.name, name);
393 } else if (key != null) {
394 setUniqueName(key);
395 }
396 }
397
398 RolapMember(RolapMember parentMember, RolapLevel level, Object value) {
399 this(parentMember, level, value, null, MemberType.REGULAR);
400 }
401
402 /**
403 * Used by RolapCubeMember. Can obsolete when RolapMember becomes a
404 * hierarchy.
405 */
406 protected RolapMember() {
407 super();
408 this.key = null;
409 }
410
411 protected Logger getLogger() {
412 return LOGGER;
413 }
414
415 public RolapLevel getLevel() {
416 return (RolapLevel) level;
417 }
418
419 public RolapHierarchy getHierarchy() {
420 return getLevel().getHierarchy();
421 }
422
423 public RolapMember getParentMember() {
424 return (RolapMember) super.getParentMember();
425 }
426
427 // Regular members do not have annotations. Measures and calculated members
428 // do, so they override this method.
429 public Map<String, Annotation> getAnnotationMap() {
430 return Collections.emptyMap();
431 }
432
433 public int hashCode() {
434 return getUniqueName().hashCode();
435 }
436
437 public boolean equals(Object o) {
438 return (o == this)
439 || ((o instanceof RolapMember) && equals((RolapMember) o));
440 }
441
442 public boolean equals(OlapElement o) {
443 return (o instanceof RolapMember)
444 && equals((RolapMember) o);
445 }
446
447 private boolean equals(RolapMember that) {
448 assert that != null; // public method should have checked
449 // Do not use equalsIgnoreCase; unique names should be identical, and
450 // hashCode assumes this.
451 return this.getUniqueName().equals(that.getUniqueName());
452 }
453
454 void makeUniqueName(HierarchyUsage hierarchyUsage) {
455 if (parentMember == null && key != null) {
456 String n = hierarchyUsage.getName();
457 if (n != null) {
458 String name = keyToString(key);
459 n = Util.quoteMdxIdentifier(n);
460 this.uniqueName = Util.makeFqName(n, name);
461 if (getLogger().isDebugEnabled()) {
462 getLogger().debug(
463 "RolapMember.makeUniqueName: uniqueName=" + uniqueName);
464 }
465 }
466 }
467 }
468
469 protected void setUniqueName(Object key) {
470 String name = keyToString(key);
471 if (parentMember == null) {
472 final RolapHierarchy hierarchy = getHierarchy();
473 final Dimension dimension = hierarchy.getDimension();
474 if (/* dimension.getHierarchies().length == 1
475 && */ hierarchy.getName().equals(dimension.getName()))
476 {
477 // Kludge to ensure that calc members are called
478 // [Measures].[Foo] not [Measures].[Measures].[Foo]. We can
479 // remove this code when we revisit the scheme to generate
480 // member unique names.
481 this.uniqueName = Util.makeFqName(dimension, name);
482 } else {
483 this.uniqueName = Util.makeFqName(hierarchy, name);
484 }
485 } else {
486 this.uniqueName = Util.makeFqName(parentMember, name);
487 }
488 }
489
490 public boolean isCalculatedInQuery() {
491 return false;
492 }
493
494 public String getName() {
495 final String name =
496 (String) getPropertyValue(Property.NAME.name);
497 return (name != null)
498 ? name
499 : keyToString(key);
500 }
501
502 public void setName(String name) {
503 throw new Error("unsupported");
504 }
505
506 /**
507 * Sets a property of this member to a given value.
508 *
509 * <p>WARNING: Setting system properties such as "$name" may have nasty
510 * side-effects.
511 */
512 public synchronized void setProperty(String name, Object value) {
513 if (name.equals(Property.CAPTION.name)) {
514 setCaption((String)value);
515 return;
516 }
517
518 if (mapPropertyNameToValue.isEmpty()) {
519 // the empty map is shared and immutable; create our own
520 PropertyValueMapFactory factory =
521 PropertyValueMapFactoryFactory.getPropertyValueMapFactory();
522 mapPropertyNameToValue = factory.create(this);
523 }
524 if (name.equals(Property.NAME.name)) {
525 if (value == null) {
526 value = RolapUtil.mdxNullLiteral;
527 }
528 setUniqueName(value);
529 }
530
531 if (name.equals(Property.MEMBER_ORDINAL.name)) {
532 String ordinal = (String) value;
533 if (ordinal.startsWith("\"") && ordinal.endsWith("\"")) {
534 ordinal = ordinal.substring(1, ordinal.length() - 1);
535 }
536 final double d = Double.parseDouble(ordinal);
537 setOrdinal((int) d);
538 }
539
540 mapPropertyNameToValue.put(name, value);
541 }
542
543 public final Object getPropertyValue(String propertyName) {
544 return getPropertyValue(propertyName, true);
545 }
546
547 public Object getPropertyValue(String propertyName, boolean matchCase) {
548 Property property = Property.lookup(propertyName, matchCase);
549 if (property != null) {
550 Schema schema;
551 Member parentMember;
552 List<RolapMember> list;
553 switch (property.ordinal) {
554 case Property.NAME_ORDINAL:
555 // Do NOT call getName() here. This property is internal,
556 // and must fall through to look in the property list.
557 break;
558
559 case Property.CAPTION_ORDINAL:
560 return getCaption();
561
562 case Property.CONTRIBUTING_CHILDREN_ORDINAL:
563 list = new ArrayList<RolapMember>();
564 getHierarchy().getMemberReader().getMemberChildren(this, list);
565 return list;
566
567 case Property.CATALOG_NAME_ORDINAL:
568 // TODO: can't go from member to connection thence to
569 // Connection.getCatalogName()
570 break;
571
572 case Property.SCHEMA_NAME_ORDINAL:
573 schema = getHierarchy().getDimension().getSchema();
574 return schema.getName();
575
576 case Property.CUBE_NAME_ORDINAL:
577 // TODO: can't go from member to cube cube yet
578 break;
579
580 case Property.DIMENSION_UNIQUE_NAME_ORDINAL:
581 return getHierarchy().getDimension().getUniqueName();
582
583 case Property.HIERARCHY_UNIQUE_NAME_ORDINAL:
584 return getHierarchy().getUniqueName();
585
586 case Property.LEVEL_UNIQUE_NAME_ORDINAL:
587 return getLevel().getUniqueName();
588
589 case Property.LEVEL_NUMBER_ORDINAL:
590 return getLevel().getDepth();
591
592 case Property.MEMBER_UNIQUE_NAME_ORDINAL:
593 return getUniqueName();
594
595 case Property.MEMBER_NAME_ORDINAL:
596 return getName();
597
598 case Property.MEMBER_TYPE_ORDINAL:
599 return getMemberType().ordinal();
600
601 case Property.MEMBER_GUID_ORDINAL:
602 return null;
603
604 case Property.MEMBER_CAPTION_ORDINAL:
605 return getCaption();
606
607 case Property.MEMBER_ORDINAL_ORDINAL:
608 return getOrdinal();
609
610 case Property.CHILDREN_CARDINALITY_ORDINAL:
611 Integer cardinality;
612
613 if (isAll() && childLevelHasApproxRowCount()) {
614 cardinality =
615 getLevel().getChildLevel().getApproxRowCount();
616 } else {
617 list = new ArrayList<RolapMember>();
618 getHierarchy().getMemberReader().getMemberChildren(
619 this, list);
620 cardinality = list.size();
621 }
622 return cardinality;
623
624 case Property.PARENT_LEVEL_ORDINAL:
625 parentMember = getParentMember();
626 return parentMember == null
627 ? 0
628 : parentMember.getLevel().getDepth();
629
630 case Property.PARENT_UNIQUE_NAME_ORDINAL:
631 parentMember = getParentMember();
632 return parentMember == null
633 ? null
634 : parentMember.getUniqueName();
635
636 case Property.PARENT_COUNT_ORDINAL:
637 parentMember = getParentMember();
638 return parentMember == null ? 0 : 1;
639
640 case Property.VISIBLE_ORDINAL:
641 break;
642
643 case Property.MEMBER_KEY_ORDINAL:
644 case Property.KEY_ORDINAL:
645 return this == this.getHierarchy().getAllMember()
646 ? 0
647 : getKey();
648
649 case Property.SCENARIO_ORDINAL:
650 throw new UnsupportedOperationException(
651 "writeback not yet supported");
652
653 default:
654 break;
655 // fall through
656 }
657 }
658 return getPropertyFromMap(propertyName, matchCase);
659 }
660
661 /**
662 * Returns the value of a property by looking it up in the property map.
663 *
664 * @param propertyName Name of property
665 * @param matchCase Whether to match name case-sensitive
666 * @return Property value
667 */
668 protected Object getPropertyFromMap(
669 String propertyName,
670 boolean matchCase)
671 {
672 synchronized (this) {
673 if (matchCase) {
674 return mapPropertyNameToValue.get(propertyName);
675 } else {
676 for (String key : mapPropertyNameToValue.keySet()) {
677 if (key.equalsIgnoreCase(propertyName)) {
678 return mapPropertyNameToValue.get(key);
679 }
680 }
681 return null;
682 }
683 }
684 }
685
686 protected boolean childLevelHasApproxRowCount() {
687 return getLevel().getChildLevel().getApproxRowCount()
688 > Integer.MIN_VALUE;
689 }
690
691 protected boolean isAllMember() {
692 return getLevel().getHierarchy().hasAll()
693 && getLevel().getDepth() == 0;
694 }
695
696 public Property[] getProperties() {
697 return getLevel().getInheritedProperties();
698 }
699
700 public int getOrdinal() {
701 return ordinal;
702 }
703
704 public Comparable getOrderKey() {
705 return orderKey;
706 }
707
708 void setOrdinal(int ordinal) {
709 if (this.ordinal == -1) {
710 this.ordinal = ordinal;
711 }
712 }
713
714 void setOrderKey(Comparable orderKey) {
715 this.orderKey = orderKey;
716 }
717
718 private void resetOrdinal() {
719 this.ordinal = -1;
720 }
721
722 public Object getKey() {
723 return this.key;
724 }
725
726 /**
727 * Compares this member to another {@link RolapMember}.
728 *
729 * <p>The method first compares on keys; null keys always collate last.
730 * If the keys are equal, it compares using unique name.
731 *
732 * <p>This method does not consider {@link #ordinal} field, because
733 * ordinal is only unique within a parent. If you want to compare
734 * members which may be at any position in the hierarchy, use
735 * {@link mondrian.olap.fun.FunUtil#compareHierarchically}.
736 *
737 * @return -1 if this is less, 0 if this is the same, 1 if this is greater
738 */
739 public int compareTo(Object o) {
740 RolapMember other = (RolapMember)o;
741 if (this.key != null && other.key == null) {
742 return 1; // not null is greater than null
743 }
744 if (this.key == null && other.key != null) {
745 return -1; // null is less than not null
746 }
747 // compare by unique name, if both keys are null
748 if (this.key == null && other.key == null) {
749 return this.getUniqueName().compareTo(other.getUniqueName());
750 }
751 // compare by unique name, if one ore both members are null
752 if (this.key == RolapUtil.sqlNullValue
753 || other.key == RolapUtil.sqlNullValue)
754 {
755 return this.getUniqueName().compareTo(other.getUniqueName());
756 }
757 // as both keys are not null, compare by key
758 // String, Double, Integer should be possible
759 // any key object should be "Comparable"
760 // anyway - keys should be of the same class
761 if (this.key.getClass().equals(other.key.getClass())) {
762 if (this.key instanceof String) {
763 // use a special case sensitive compare name which
764 // first compares w/o case, and if 0 compares with case
765 return Util.caseSensitiveCompareName(
766 (String) this.key, (String) other.key);
767 } else {
768 return Util.compareKey(this.key, other.key);
769 }
770 }
771 // Compare by unique name in case of different key classes.
772 // This is possible, if a new calculated member is created
773 // in a dimension with an Integer key. The calculated member
774 // has a key of type String.
775 return this.getUniqueName().compareTo(other.getUniqueName());
776 }
777
778 public boolean isHidden() {
779 final RolapLevel rolapLevel = getLevel();
780 switch (rolapLevel.getHideMemberCondition()) {
781 case Never:
782 return false;
783
784 case IfBlankName:
785 {
786 // If the key value in the database is null, then we use
787 // a special key value whose toString() is "null".
788 final String name = getName();
789 return name.equals(RolapUtil.mdxNullLiteral) || name.equals("");
790 }
791
792 case IfParentsName:
793 {
794 final Member parentMember = getParentMember();
795 if (parentMember == null) {
796 return false;
797 }
798 final String parentName = parentMember.getName();
799 final String name = getName();
800 return (parentName == null ? "" : parentName).equals(
801 name == null ? "" : name);
802 }
803
804 default:
805 throw Util.badValue(rolapLevel.getHideMemberCondition());
806 }
807 }
808
809 public int getDepth() {
810 return getLevel().getDepth();
811 }
812
813 public String getPropertyFormattedValue(String propertyName) {
814 // do we have a formatter ? if yes, use it
815 Property[] props = getLevel().getProperties();
816 Property prop = null;
817 for (Property prop1 : props) {
818 if (prop1.getName().equals(propertyName)) {
819 prop = prop1;
820 break;
821 }
822 }
823 PropertyFormatter pf;
824 if (prop != null && (pf = prop.getFormatter()) != null) {
825 return pf.formatProperty(
826 this, propertyName,
827 getPropertyValue(propertyName));
828 }
829
830 Object val = getPropertyValue(propertyName);
831 return (val == null)
832 ? ""
833 : val.toString();
834 }
835
836 /**
837 * <p>Interface definition for the pluggable factory used to decide
838 * which implementation of {@link java.util.Map} to use to store
839 * property string/value pairs for member properties.</p>
840 *
841 * <p>This permits tuning for performance, memory allocation, etcetera.
842 * For example, if a member belongs to a level which has 10 member
843 * properties a HashMap may be preferred, while if the level has
844 * only two member properties a Flat3Map may make more sense.</p>
845 */
846 public interface PropertyValueMapFactory {
847 /**
848 * <p>Create a new {@link java.util.Map} to be used for storing
849 * property string/value pairs for the specified
850 * {@link mondrian.olap.Member}.</p>
851 * @param member
852 * @return the Map instance to store property/value pairs
853 */
854 Map<String, Object> create(Member member);
855 }
856
857 /**
858 * Default {@link mondrian.rolap.RolapMember.PropertyValueMapFactory}
859 * implementation, used if
860 * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass}
861 * is not set.
862 */
863 public static final class DefaultPropertyValueMapFactory
864 implements PropertyValueMapFactory
865 {
866 /**
867 * {@inheritDoc}
868 * <p>This factory creates an
869 * {@link org.apache.commons.collections.map.Flat3Map} if
870 * it appears that the provided member has less than 3 properties,
871 * and a {@link java.util.HashMap} if it appears
872 * that it has more than 3.</p>
873 *
874 * <p>Guessing the number of properties
875 * can be tricky since some subclasses of
876 * {@link mondrian.olap.Member}</p> have additional properties
877 * that aren't explicitly declared. The most common offenders
878 * are the (@link mondrian.olap.Measure} implementations, which
879 * often have 4 or more undeclared properties, so if the member
880 * is a measure, the factory will create a {@link java.util.HashMap}.
881 * </p>
882 *
883 * @param member {@inheritDoc}
884 * @return {@inheritDoc}
885 */
886 @SuppressWarnings({"unchecked"})
887 public Map<String, Object> create(Member member) {
888 assert member != null;
889 Property[] props = member.getProperties();
890 if ((member instanceof RolapMeasure)
891 || (props == null)
892 || (props.length > 3))
893 {
894 return new HashMap<String, Object>();
895 } else {
896 return new Flat3Map();
897 }
898 }
899 }
900
901 /**
902 * <p>Creates the PropertyValueMapFactory which is in turn used
903 * to create property-value maps for member properties.</p>
904 *
905 * <p>The name of the PropertyValueMapFactory is drawn from
906 * {@link mondrian.olap.MondrianProperties#PropertyValueMapFactoryClass}
907 * in mondrian.properties. If unset, it defaults to
908 * {@link mondrian.rolap.RolapMember.DefaultPropertyValueMapFactory}. </p>
909 */
910 public static final class PropertyValueMapFactoryFactory
911 extends ObjectFactory.Singleton<PropertyValueMapFactory>
912 {
913 /**
914 * Single instance of the <code>PropertyValueMapFactory</code>.
915 */
916 private static final PropertyValueMapFactoryFactory factory;
917 static {
918 factory = new PropertyValueMapFactoryFactory();
919 }
920
921 /**
922 * Access the <code>PropertyValueMapFactory</code> instance.
923 *
924 * @return the <code>Map</code>.
925 */
926 public static PropertyValueMapFactory getPropertyValueMapFactory() {
927 return factory.getObject();
928 }
929
930 /**
931 * The constructor for the <code>PropertyValueMapFactoryFactory</code>.
932 * This passes the <code>PropertyValueMapFactory</code> class to the
933 * <code>ObjectFactory</code> base class.
934 */
935 @SuppressWarnings({"unchecked"})
936 private PropertyValueMapFactoryFactory() {
937 super((Class) PropertyValueMapFactory.class);
938 }
939
940 protected StringProperty getStringProperty() {
941 return MondrianProperties.instance().PropertyValueMapFactoryClass;
942 }
943
944 protected PropertyValueMapFactory getDefault(
945 Class[] parameterTypes,
946 Object[] parameterValues)
947 throws CreationException
948 {
949 return new DefaultPropertyValueMapFactory();
950 }
951 }
952 }
953
954 // End RolapMember.java