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