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