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-2011 Pentaho and others
009// All Rights Reserved.
010*/
011package mondrian.olap;
012
013import mondrian.resource.MondrianResource;
014import mondrian.spi.MemberFormatter;
015import mondrian.util.Bug;
016
017import java.util.ArrayList;
018import java.util.List;
019
020/**
021 * <code>MemberBase</code> is a partial implementation of {@link Member}.
022 *
023 * @author jhyde
024 * @since 6 August, 2001
025 */
026public abstract class MemberBase
027    extends OlapElementBase
028    implements Member
029{
030
031    protected Member parentMember;
032    protected final Level level;
033    protected String uniqueName;
034
035    /**
036     * Combines member type and other properties, such as whether the member
037     * is the 'all' or 'null' member of its hierarchy and whether it is a
038     * measure or is calculated, into an integer field.
039     *
040     * <p>The fields are:<ul>
041     * <li>bits 0, 1, 2 ({@link #FLAG_TYPE_MASK}) are member type;
042     * <li>bit 3 ({@link #FLAG_HIDDEN}) is set if the member is hidden;
043     * <li>bit 4 ({@link #FLAG_ALL}) is set if this is the all member of its
044     *     hierarchy;
045     * <li>bit 5 ({@link #FLAG_NULL}) is set if this is the null member of its
046     *     hierarchy;
047     * <li>bit 6 ({@link #FLAG_CALCULATED}) is set if this is a calculated
048     *     member.
049     * <li>bit 7 ({@link #FLAG_MEASURE}) is set if this is a measure.
050     * </ul>
051     *
052     * NOTE: jhyde, 2007/8/10. It is necessary to cache whether the member is
053     * 'all', 'calculated' or 'null' in the member's state, because these
054     * properties are used so often. If we used a virtual method call - say we
055     * made each subclass implement 'boolean isNull()' - it would be slower.
056     * We use one flags field rather than 4 boolean fields to save space.
057     */
058    protected final int flags;
059
060    private static final int FLAG_TYPE_MASK = 0x07;
061    private static final int FLAG_HIDDEN = 0x08;
062    private static final int FLAG_ALL = 0x10;
063    private static final int FLAG_NULL = 0x20;
064    private static final int FLAG_CALCULATED = 0x40;
065    private static final int FLAG_MEASURE = 0x80;
066
067    /**
068     * Cached values of {@link mondrian.olap.Member.MemberType} enumeration.
069     * Without caching, get excessive calls to {@link Object#clone}.
070     */
071    private static final MemberType[] MEMBER_TYPE_VALUES = MemberType.values();
072
073    protected MemberBase(
074        Member parentMember,
075        Level level,
076        MemberType memberType)
077    {
078        this.parentMember = parentMember;
079        this.level = level;
080        this.flags = memberType.ordinal()
081            | (memberType == MemberType.ALL ? FLAG_ALL : 0)
082            | (memberType == MemberType.NULL ? FLAG_NULL : 0)
083            | (computeCalculated(memberType) ? FLAG_CALCULATED : 0)
084            | (level.getHierarchy().getDimension().isMeasures()
085               ? FLAG_MEASURE
086               : 0);
087    }
088
089    protected MemberBase() {
090        this.flags = 0;
091        this.level = null;
092    }
093
094    public String getQualifiedName() {
095        return MondrianResource.instance().MdxMemberName.str(getUniqueName());
096    }
097
098    public abstract String getName();
099
100    public String getUniqueName() {
101        return uniqueName;
102    }
103
104    public String getCaption() {
105        // if there is a member formatter for the members level,
106        //  we will call this interface to provide the display string
107        MemberFormatter mf = getLevel().getMemberFormatter();
108        if (mf != null) {
109            return mf.formatMember(this);
110        }
111        final String caption = super.getCaption();
112        return (caption != null)
113            ? caption
114            : getName();
115    }
116
117    public String getParentUniqueName() {
118        return parentMember == null
119            ? null
120            : parentMember.getUniqueName();
121    }
122
123    public Dimension getDimension() {
124        return level.getDimension();
125    }
126
127    public Hierarchy getHierarchy() {
128        return level.getHierarchy();
129    }
130
131    public Level getLevel() {
132        return level;
133    }
134
135    public MemberType getMemberType() {
136        return MEMBER_TYPE_VALUES[flags & FLAG_TYPE_MASK];
137    }
138
139    public String getDescription() {
140        return (String) getPropertyValue(Property.DESCRIPTION.name);
141    }
142
143    public boolean isMeasure() {
144        return (flags & FLAG_MEASURE) != 0;
145    }
146
147    public boolean isAll() {
148        return (flags & FLAG_ALL) != 0;
149    }
150
151    public boolean isNull() {
152        return (flags & FLAG_NULL) != 0;
153    }
154
155    public boolean isCalculated() {
156        return (flags & FLAG_CALCULATED) != 0;
157    }
158
159    public boolean isEvaluated() {
160        // should just call isCalculated(), but called in tight loops
161        // and too many subclass implementations for jit to inline properly?
162        return (flags & FLAG_CALCULATED) != 0;
163    }
164
165    public OlapElement lookupChild(
166        SchemaReader schemaReader,
167        Id.Segment childName,
168        MatchType matchType)
169    {
170        return schemaReader.lookupMemberChildByName(
171            this, childName, matchType);
172    }
173
174    // implement Member
175    public Member getParentMember() {
176        return parentMember;
177    }
178
179    // implement Member
180    public boolean isChildOrEqualTo(Member member) {
181        // REVIEW: Using uniqueName to calculate ancestry seems inefficient,
182        //   because we can't afford to store every member's unique name, so
183        //   we want to compute it on the fly
184        assert !Bug.BugSegregateRolapCubeMemberFixed;
185        return (member != null) && isChildOrEqualTo(member.getUniqueName());
186    }
187
188   /**
189    * Returns whether this <code>Member</code>'s unique name is equal to, a
190    * child of, or a descendent of a member whose unique name is
191    * <code>uniqueName</code>.
192    */
193    public boolean isChildOrEqualTo(String uniqueName) {
194        if (uniqueName == null) {
195            return false;
196        }
197
198        return isChildOrEqualTo(this, uniqueName);
199    }
200
201    private static boolean isChildOrEqualTo(Member member, String uniqueName) {
202        while (true) {
203            String thisUniqueName = member.getUniqueName();
204            if (thisUniqueName.equals(uniqueName)) {
205                // found a match
206                return true;
207            }
208            // try candidate's parentMember
209            member = member.getParentMember();
210            if (member == null) {
211                // have reached root
212                return false;
213            }
214        }
215    }
216
217    /**
218     * Computes the value to be returned by {@link #isCalculated()}, so it can
219     * be cached in a variable.
220     *
221     * @param memberType Member type
222     * @return Whether this member is calculated
223     */
224    protected boolean computeCalculated(final MemberType memberType) {
225        // If the member is not created from the "with member ..." MDX, the
226        // calculated will be null. But it may be still a calculated measure
227        // stored in the cube.
228        return isCalculatedInQuery() || memberType == MemberType.FORMULA;
229    }
230
231    public int getSolveOrder() {
232        return -1;
233    }
234
235    /**
236     * Returns the expression by which this member is calculated. The expression
237     * is not null if and only if the member is not calculated.
238     *
239     * @post (return != null) == (isCalculated())
240     */
241    public Exp getExpression() {
242        return null;
243    }
244
245    // implement Member
246    public List<Member> getAncestorMembers() {
247        final SchemaReader schemaReader =
248            getDimension().getSchema().getSchemaReader();
249        final ArrayList<Member> ancestorList = new ArrayList<Member>();
250        schemaReader.getMemberAncestors(this, ancestorList);
251        return ancestorList;
252    }
253
254    /**
255     * Returns the ordinal of this member within its hierarchy.
256     * The default implementation returns -1.
257     */
258    public int getOrdinal() {
259        return -1;
260    }
261
262    /**
263     * Returns the order key of this member among its siblings.
264     * The default implementation returns null.
265     */
266    public Comparable getOrderKey() {
267        return null;
268    }
269
270    public boolean isHidden() {
271        return false;
272    }
273
274    public Member getDataMember() {
275        return null;
276    }
277
278    public String getPropertyFormattedValue(String propertyName) {
279        return getPropertyValue(propertyName).toString();
280    }
281
282    public boolean isParentChildLeaf() {
283        return false;
284    }
285}
286
287// End MemberBase.java