001    /*
002    // $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/SmartMemberReader.java#1 $
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    // Copyright (C) 2004-2005 TONBELLER AG
009    // All Rights Reserved.
010    // You must accept the terms of that agreement to use this software.
011    //
012    // jhyde, 21 December, 2001
013    */
014    
015    package mondrian.rolap;
016    import java.util.ArrayList;
017    import java.util.Collections;
018    import java.util.HashMap;
019    import java.util.List;
020    import java.util.Map;
021    
022    import mondrian.olap.Id;
023    import mondrian.olap.Util;
024    import mondrian.rolap.TupleReader.MemberBuilder;
025    import mondrian.rolap.sql.MemberChildrenConstraint;
026    import mondrian.rolap.sql.TupleConstraint;
027    import mondrian.util.ConcatenableList;
028    
029    /**
030     * <code>SmartMemberReader</code> implements {@link MemberReader} by keeping a
031     * cache of members and their children. If a member is 'in cache', there is a
032     * list of its children. It also caches the members of levels.
033     *
034     * <p>Synchronization: the MemberReader <code>source</code> must be called
035     * from synchronized(this) context - it does not synchronize itself (probably
036     * it should).</p>
037     *
038     * <p>Constraints: Member.Children and Level.Members may be constrained by a
039     * SqlConstraint object. In this case a subset of all members is returned.
040     * These subsets are cached too and the SqlConstraint is part of the cache key.
041     * This is used in NON EMPTY context.</p>
042     *
043     * <p>Uniqueness. We need to ensure that there is never more than one {@link
044     * RolapMember} object representing the same member.</p>
045     *
046     * @author jhyde
047     * @since 21 December, 2001
048     * @version $Id: //open/mondrian-release/3.2/src/main/mondrian/rolap/SmartMemberReader.java#1 $
049     */
050    public class SmartMemberReader implements MemberReader {
051        private final SqlConstraintFactory sqlConstraintFactory =
052                SqlConstraintFactory.instance();
053    
054        /** access to <code>source</code> must be synchronized(this) */
055        protected final MemberReader source;
056    
057        protected final MemberCacheHelper cacheHelper;
058    
059        protected List<RolapMember> rootMembers;
060    
061        SmartMemberReader(MemberReader source) {
062            this.source = source;
063            this.cacheHelper = new MemberCacheHelper(source.getHierarchy());
064            if (!source.setCache(cacheHelper)) {
065                throw Util.newInternal(
066                    "MemberSource (" + source + ", " + source.getClass()
067                    + ") does not support cache-writeback");
068            }
069        }
070    
071        // implement MemberReader
072        public RolapHierarchy getHierarchy() {
073            return source.getHierarchy();
074        }
075    
076        public MemberCache getMemberCache() {
077            return cacheHelper;
078        }
079    
080        // implement MemberSource
081        public boolean setCache(MemberCache cache) {
082            // we do not support cache writeback -- we must be masters of our
083            // own cache
084            return false;
085        }
086    
087        public RolapMember substitute(RolapMember member) {
088            return member;
089        }
090    
091        public RolapMember desubstitute(RolapMember member) {
092            return member;
093        }
094    
095        // implement MemberReader
096        public List<RolapMember> getMembers() {
097            List<RolapMember> v = new ConcatenableList<RolapMember>();
098            RolapLevel[] levels = (RolapLevel[]) getHierarchy().getLevels();
099            // todo: optimize by walking to children for members we know about
100            for (RolapLevel level : levels) {
101                List<RolapMember> membersInLevel = getMembersInLevel(
102                    level,
103                    0,
104                    Integer.MAX_VALUE);
105                v.addAll(membersInLevel);
106            }
107            return v;
108        }
109    
110        public List<RolapMember> getRootMembers() {
111            if (rootMembers == null) {
112                rootMembers = source.getRootMembers();
113            }
114            return rootMembers;
115        }
116    
117        public List<RolapMember> getMembersInLevel(
118            RolapLevel level,
119            int startOrdinal,
120            int endOrdinal)
121        {
122            TupleConstraint constraint =
123                sqlConstraintFactory.getLevelMembersConstraint(null);
124            return getMembersInLevel(level, startOrdinal, endOrdinal, constraint);
125        }
126    
127        protected void checkCacheStatus() {
128            cacheHelper.checkCacheStatus();
129        }
130    
131        public List<RolapMember> getMembersInLevel(
132            RolapLevel level,
133            int startOrdinal,
134            int endOrdinal,
135            TupleConstraint constraint)
136        {
137            synchronized (cacheHelper) {
138                checkCacheStatus();
139    
140                List<RolapMember> members =
141                    cacheHelper.getLevelMembersFromCache(level, constraint);
142                if (members != null) {
143                    return members;
144                }
145    
146                members =
147                    source.getMembersInLevel(
148                        level, startOrdinal, endOrdinal, constraint);
149                cacheHelper.putLevelMembersInCache(level, constraint, members);
150                return members;
151            }
152        }
153    
154        public int getLevelMemberCount(RolapLevel level) {
155            // No need to cache the result: the caller saves the result by calling
156            // RolapLevel.setApproxRowCount
157            return source.getLevelMemberCount(level);
158        }
159    
160        public void getMemberChildren(
161            RolapMember parentMember,
162            List<RolapMember> children)
163        {
164            MemberChildrenConstraint constraint =
165                    sqlConstraintFactory.getMemberChildrenConstraint(null);
166            getMemberChildren(parentMember, children, constraint);
167        }
168    
169        public void getMemberChildren(
170            RolapMember parentMember,
171            List<RolapMember> children,
172            MemberChildrenConstraint constraint)
173        {
174            List<RolapMember> parentMembers =
175                Collections.singletonList(parentMember);
176            getMemberChildren(parentMembers, children, constraint);
177        }
178    
179        public void getMemberChildren(
180            List<RolapMember> parentMembers,
181            List<RolapMember> children)
182        {
183            MemberChildrenConstraint constraint =
184                    sqlConstraintFactory.getMemberChildrenConstraint(null);
185            getMemberChildren(parentMembers, children, constraint);
186        }
187    
188        public void getMemberChildren(
189            List<RolapMember> parentMembers,
190            List<RolapMember> children,
191            MemberChildrenConstraint constraint)
192        {
193            synchronized (cacheHelper) {
194                checkCacheStatus();
195    
196                List<RolapMember> missed = new ArrayList<RolapMember>();
197                for (RolapMember parentMember : parentMembers) {
198                    List<RolapMember> list =
199                        cacheHelper.getChildrenFromCache(parentMember, constraint);
200                    if (list == null) {
201                        // the null member has no children
202                        if (!parentMember.isNull()) {
203                            missed.add(parentMember);
204                        }
205                    } else {
206                        children.addAll(list);
207                    }
208                }
209                if (missed.size() > 0) {
210                    readMemberChildren(missed, children, constraint);
211                }
212            }
213        }
214    
215        public RolapMember lookupMember(
216            List<Id.Segment> uniqueNameParts,
217            boolean failIfNotFound)
218        {
219            return RolapUtil.lookupMember(this, uniqueNameParts, failIfNotFound);
220        }
221    
222        /**
223         * Reads the children of <code>member</code> into cache, and also into
224         * <code>result</code>.
225         *
226         * @param result Children are written here, in order
227         * @param members Members whose children to read
228         * @param constraint restricts the returned members if possible (optional
229         *             optimization)
230         */
231        protected void readMemberChildren(
232            List<RolapMember> members,
233            List<RolapMember> result,
234            MemberChildrenConstraint constraint)
235        {
236            if (false) {
237                // Pre-condition disabled. It makes sense to have the pre-
238                // condition, because lists of parent members are typically
239                // sorted by construction, and we should be able to exploit this
240                // when constructing the (significantly larger) set of children.
241                // But currently BasicQueryTest.testBasketAnalysis() fails this
242                // assert, and I haven't had time to figure out why.
243                //   -- jhyde, 2004/6/10.
244                Util.assertPrecondition(isSorted(members), "isSorted(members)");
245            }
246            List<RolapMember> children = new ConcatenableList<RolapMember>();
247            source.getMemberChildren(members, children, constraint);
248            // Put them in a temporary hash table first. Register them later, when
249            // we know their size (hence their 'cost' to the cache pool).
250            Map<RolapMember, List<RolapMember>> tempMap =
251                new HashMap<RolapMember, List<RolapMember>>();
252            for (RolapMember member1 : members) {
253                tempMap.put(member1, Collections.EMPTY_LIST);
254            }
255            for (final RolapMember child : children) {
256                // todo: We could optimize here. If members.length is small, it's
257                // more efficient to drive from members, rather than hashing
258                // children.length times. We could also exploit the fact that the
259                // result is sorted by ordinal and therefore, unless the "members"
260                // contains members from different levels, children of the same
261                // member will be contiguous.
262                assert child != null : "child";
263                assert tempMap != null : "tempMap";
264                final RolapMember parentMember = child.getParentMember();
265                List<RolapMember> list = tempMap.get(parentMember);
266                if (list == null) {
267                    // The list is null if, due to dropped constraints, we now
268                    // have a children list of a member we didn't explicitly
269                    // ask for it. Adding it to the cache would be viable, but
270                    // let's ignore it.
271                    continue;
272                } else if (list == Collections.EMPTY_LIST) {
273                    list = new ArrayList<RolapMember>();
274                    tempMap.put(parentMember, list);
275                }
276                ((List)list).add(child);
277                ((List)result).add(child);
278            }
279            synchronized (cacheHelper) {
280                for (Map.Entry<RolapMember, List<RolapMember>> entry
281                    : tempMap.entrySet())
282                {
283                    final RolapMember member = entry.getKey();
284                    if (cacheHelper.getChildrenFromCache(member, constraint)
285                        == null)
286                    {
287                        final List<RolapMember> list = entry.getValue();
288                        cacheHelper.putChildren(member, constraint, list);
289                    }
290                }
291            }
292        }
293    
294        /**
295         * Returns true if every element of <code>members</code> is not null and is
296         * strictly less than the following element; false otherwise.
297         */
298        public boolean isSorted(List<RolapMember> members) {
299            final int count = members.size();
300            if (count == 0) {
301                return true;
302            }
303            RolapMember m1 = members.get(0);
304            if (m1 == null) {
305                // Special case check for 0th element, just in case length == 1.
306                return false;
307            }
308            for (int i = 1; i < count; i++) {
309                RolapMember m0 = m1;
310                m1 = members.get(i);
311                if (m1 == null || compare(m0, m1, false) >= 0) {
312                    return false;
313                }
314            }
315            return true;
316        }
317    
318        public RolapMember getLeadMember(RolapMember member, int n) {
319            // uncertain if this method needs to be synchronized
320            synchronized (cacheHelper) {
321                if (n == 0 || member.isNull()) {
322                    return member;
323                } else {
324                    SiblingIterator iter = new SiblingIterator(this, member);
325                    if (n > 0) {
326                        RolapMember sibling = null;
327                        while (n-- > 0) {
328                            if (!iter.hasNext()) {
329                                return (RolapMember)
330                                    member.getHierarchy().getNullMember();
331                            }
332                            sibling = iter.nextMember();
333                        }
334                        return sibling;
335                    } else {
336                        n = -n;
337                        RolapMember sibling = null;
338                        while (n-- > 0) {
339                            if (!iter.hasPrevious()) {
340                                return (RolapMember)
341                                    member.getHierarchy().getNullMember();
342                            }
343                            sibling = iter.previousMember();
344                        }
345                        return sibling;
346                    }
347                }
348            }
349        }
350    
351        public void getMemberRange(
352            RolapLevel level,
353            RolapMember startMember,
354            RolapMember endMember,
355            List<RolapMember> list)
356        {
357            assert startMember != null;
358            assert endMember != null;
359            assert startMember.getLevel() == endMember.getLevel();
360    
361            if (compare(startMember, endMember, false) > 0) {
362                return;
363            }
364            list.add(startMember);
365            if (startMember.equals(endMember)) {
366                return;
367            }
368            SiblingIterator siblings = new SiblingIterator(this, startMember);
369            while (siblings.hasNext()) {
370                final RolapMember member = siblings.nextMember();
371                list.add(member);
372                if (member.equals(endMember)) {
373                    return;
374                }
375            }
376            throw Util.newInternal(
377                "sibling iterator did not hit end point, start="
378                + startMember + ", end=" + endMember);
379        }
380    
381        public int getMemberCount() {
382            return source.getMemberCount();
383        }
384    
385        public int compare(
386            RolapMember m1,
387            RolapMember m2,
388            boolean siblingsAreEqual)
389        {
390            if (m1.equals(m2)) {
391                return 0;
392            }
393            if (Util.equals(m1.getParentMember(), m2.getParentMember())) {
394                // including case where both parents are null
395                if (siblingsAreEqual) {
396                    return 0;
397                } else if (m1.getParentMember() == null) {
398                    // at this point we know that both parent members are null.
399                    int pos1 = -1, pos2 = -1;
400                    List<RolapMember> siblingList = getRootMembers();
401                    for (int i = 0, n = siblingList.size(); i < n; i++) {
402                        RolapMember child = siblingList.get(i);
403                        if (child.equals(m1)) {
404                            pos1 = i;
405                        }
406                        if (child.equals(m2)) {
407                            pos2 = i;
408                        }
409                    }
410                    if (pos1 == -1) {
411                        throw Util.newInternal(m1 + " not found among siblings");
412                    }
413                    if (pos2 == -1) {
414                        throw Util.newInternal(m2 + " not found among siblings");
415                    }
416                    Util.assertTrue(pos1 != pos2);
417                    return pos1 < pos2 ? -1 : 1;
418                } else {
419                    List<RolapMember> children = new ArrayList<RolapMember>();
420                    getMemberChildren(m1.getParentMember(), children);
421                    int pos1 = -1, pos2 = -1;
422                    for (int i = 0, n = children.size(); i < n; i++) {
423                        RolapMember child = children.get(i);
424                        if (child.equals(m1)) {
425                            pos1 = i;
426                        }
427                        if (child.equals(m2)) {
428                            pos2 = i;
429                        }
430                    }
431                    if (pos1 == -1) {
432                        throw Util.newInternal(m1 + " not found among siblings");
433                    }
434                    if (pos2 == -1) {
435                        throw Util.newInternal(m2 + " not found among siblings");
436                    }
437                    Util.assertTrue(pos1 != pos2);
438                    return pos1 < pos2 ? -1 : 1;
439                }
440            }
441            int levelDepth1 = m1.getLevel().getDepth();
442            int levelDepth2 = m2.getLevel().getDepth();
443            if (levelDepth1 < levelDepth2) {
444                final int c = compare(m1, m2.getParentMember(), false);
445                return (c == 0) ? -1 : c;
446    
447            } else if (levelDepth1 > levelDepth2) {
448                final int c = compare(m1.getParentMember(), m2, false);
449                return (c == 0) ? 1 : c;
450    
451            } else {
452                return compare(m1.getParentMember(), m2.getParentMember(), false);
453            }
454        }
455    
456        /**
457         * <code>SiblingIterator</code> helps traverse a hierarchy of members, by
458         * remembering the position at each level. Each SiblingIterator has a
459         * parent, to which it defers when the last child of the current member is
460         * reached.
461         */
462        class SiblingIterator {
463            private final MemberReader reader;
464            private final SiblingIterator parentIterator;
465            private List<RolapMember> siblings;
466            private int position;
467    
468            SiblingIterator(MemberReader reader, RolapMember member) {
469                this.reader = reader;
470                RolapMember parent = member.getParentMember();
471                List<RolapMember> siblingList;
472                if (parent == null) {
473                    siblingList = reader.getRootMembers();
474                    this.parentIterator = null;
475                } else {
476                    siblingList = new ArrayList<RolapMember>();
477                    reader.getMemberChildren(parent, siblingList);
478                    this.parentIterator = new SiblingIterator(reader, parent);
479                }
480                this.siblings = siblingList;
481                this.position = -1;
482                for (int i = 0; i < this.siblings.size(); i++) {
483                    if (siblings.get(i).equals(member)) {
484                        this.position = i;
485                        break;
486                    }
487                }
488                if (this.position == -1) {
489                    throw Util.newInternal(
490                        "member " + member + " not found among its siblings");
491                }
492            }
493    
494            boolean hasNext() {
495                return (this.position < this.siblings.size() - 1)
496                    || (parentIterator != null)
497                    && parentIterator.hasNext();
498            }
499    
500            Object next() {
501                return nextMember();
502            }
503    
504            RolapMember nextMember() {
505                if (++this.position >= this.siblings.size()) {
506                    if (parentIterator == null) {
507                        throw Util.newInternal("there is no next member");
508                    }
509                    RolapMember parent = parentIterator.nextMember();
510                    List<RolapMember> siblingList = new ArrayList<RolapMember>();
511                    reader.getMemberChildren(parent, siblingList);
512                    this.siblings = siblingList;
513                    this.position = 0;
514                }
515                return this.siblings.get(this.position);
516            }
517    
518            boolean hasPrevious() {
519                return (this.position > 0)
520                    || (parentIterator != null)
521                    && parentIterator.hasPrevious();
522            }
523    
524            Object previous() {
525                return previousMember();
526            }
527    
528            RolapMember previousMember() {
529                if (--this.position < 0) {
530                    if (parentIterator == null) {
531                        throw Util.newInternal("there is no next member");
532                    }
533                    RolapMember parent = parentIterator.previousMember();
534                    List<RolapMember> siblingList = new ArrayList<RolapMember>();
535                    reader.getMemberChildren(parent, siblingList);
536                    this.siblings = siblingList;
537                    this.position = this.siblings.size() - 1;
538                }
539                return this.siblings.get(this.position);
540            }
541        }
542    
543        public MemberBuilder getMemberBuilder() {
544            return source.getMemberBuilder();
545        }
546    
547        public RolapMember getDefaultMember() {
548            RolapMember defaultMember =
549                (RolapMember) getHierarchy().getDefaultMember();
550            if (defaultMember != null) {
551                return defaultMember;
552            }
553            return getRootMembers().get(0);
554        }
555    
556        public RolapMember getMemberParent(RolapMember member) {
557            // This method deals with ragged hierarchies but not access-controlled
558            // hierarchies - assume these have RestrictedMemberReader possibly
559            // wrapped in a SubstitutingMemberReader.
560            RolapMember parentMember = member.getParentMember();
561            // Skip over hidden parents.
562            while (parentMember != null && parentMember.isHidden()) {
563                parentMember = parentMember.getParentMember();
564            }
565            return parentMember;
566        }
567    }
568    
569    // End SmartMemberReader.java