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