gt:         if (nullMember == null) {
context: /*
  // This software is subject to the terms of the Eclipse Public License v1.0
  // Agreement, available at the following URL:
  // http://www.eclipse.org/legal/epl-v10.html.
  // You must accept the terms of that agreement to use this software.
  //
  // Copyright (C) 2001-2005 Julian Hyde
  // Copyright (C) 2005-2012 Pentaho and others
  // All Rights Reserved.
  */
  package mondrian.rolap;
  
  import mondrian.calc.*;
  import mondrian.calc.impl.*;
  import mondrian.mdx.*;
  import mondrian.olap.*;
  import mondrian.olap.DimensionType;
  import mondrian.olap.LevelType;
  import mondrian.olap.Role.HierarchyAccess;
  import mondrian.olap.fun.*;
  import mondrian.olap.type.*;
  import mondrian.resource.MondrianResource;
  import mondrian.rolap.RestrictedMemberReader.MultiCardinalityDefaultMember;
  import mondrian.rolap.sql.MemberChildrenConstraint;
  import mondrian.rolap.sql.SqlQuery;
  import mondrian.spi.CellFormatter;
  import mondrian.spi.impl.Scripts;
  import mondrian.util.UnionIterator;
  
  import org.apache.log4j.Logger;
  
  import java.io.PrintWriter;
  import java.util.*;
  
  /**
   * <code>RolapHierarchy</code> implements {@link Hierarchy} for a ROLAP database.
   *
   * <p>The ordinal of a hierarchy <em>within a particular cube</em> is found by
   * calling {@link #getOrdinalInCube()}. Ordinals are contiguous and zero-based.
   * Zero is always the <code>[Measures]</code> dimension.
   *
   * <p>NOTE: It is only valid to call that method on the measures hierarchy, and
   * on members of the {@link RolapCubeHierarchy} subclass. When the measures
   * hierarchy is of that class, we will move the method down.)
   *
   * @author jhyde
   * @since 10 August, 2001
    */
  public class RolapHierarchy extends HierarchyBase {
  
      private static final Logger LOGGER = Logger.getLogger(RolapHierarchy.class);
  
      /**
       * The raw member reader. For a member reader which incorporates access
       * control and deals with hidden members (if the hierarchy is ragged), use
       * {@link #createMemberReader(Role)}.
       */
      private MemberReader memberReader;
      protected MondrianDef.Hierarchy xmlHierarchy;
      private String memberReaderClass;
      protected MondrianDef.RelationOrJoin relation;
      private Member defaultMember;
      private String defaultMemberName;
      private RolapNullMember nullMember;
  
      private String sharedHierarchyName;
      private String uniqueKeyLevelName;
  
      private Exp aggregateChildrenExpression;
  
      /**
       * The level that the null member belongs too.
       */
      protected final RolapLevel nullLevel;
  
      /**
       * The 'all' member of this hierarchy. This exists even if the hierarchy
       * does not officially have an 'all' member.
       */
      private RolapMemberBase allMember;
      private static final String ALL_LEVEL_CARDINALITY = "1";
      private final Map<String, Annotation> annotationMap;
      final RolapHierarchy closureFor;
  
      /**
       * Creates a hierarchy.
       *
       * @param dimension Dimension
       * @param subName Name of this hierarchy
       * @param hasAll Whether hierarchy has an 'all' member
       * @param closureFor Hierarchy for which the new hierarchy is a closure;
       *     null for regular hierarchies
       */
      RolapHierarchy(
          RolapDimension dimension,
          String subName,
          String caption,
          boolean visible,
          String description,
          boolean hasAll,
          RolapHierarchy closureFor,
          Map<String, Annotation> annotationMap)
      {
          super(dimension, subName, caption, visible, description, hasAll);
          this.annotationMap = annotationMap;
          this.allLevelName = "(All)";
          this.allMemberName =
              subName != null
              && (MondrianProperties.instance().SsasCompatibleNaming.get()
                  || name.equals(subName + "." + subName))
                  ? "All " + subName + "s"
                  : "All " + name + "s";
          this.closureFor = closureFor;
          if (hasAll) {
              this.levels = new RolapLevel[1];
              this.levels[0] =
                  new RolapLevel(
                      this,
                      this.allLevelName,
                      null,
                      true,
                      null,
                      0,
                      null,
                      null,
                      null,
                      null,
                      null,
                      null,
                      null,
                      RolapProperty.emptyArray,
                      RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
                      null,
                      null,
                      RolapLevel.HideMemberCondition.Never,
                      LevelType.Regular,
                      "",
                      Collections.<String, Annotation>emptyMap());
          } else {
              this.levels = new RolapLevel[0];
          }
  
          // The null member belongs to a level with very similar properties to
          // the 'all' level.
          this.nullLevel =
              new RolapLevel(
                  this,
                  this.allLevelName,
                  null,
                  true,
                  null,
                  0,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  RolapProperty.emptyArray,
                  RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
                  null,
                  null,
                  RolapLevel.HideMemberCondition.Never,
                  LevelType.Null,
                  "",
                  Collections.<String, Annotation>emptyMap());
      }
  
      /**
       * Creates a <code>RolapHierarchy</code>.
       *
       * @param dimension the dimension this hierarchy belongs to
       * @param xmlHierarchy the xml object defining this hierarchy
       * @param xmlCubeDimension the xml object defining the cube
       *   dimension for this object
       */
      RolapHierarchy(
          RolapDimension dimension,
          MondrianDef.Hierarchy xmlHierarchy,
          MondrianDef.CubeDimension xmlCubeDimension)
      {
          this(
              dimension,
              xmlHierarchy.name,
              xmlHierarchy.caption,
              xmlHierarchy.visible,
              xmlHierarchy.description,
              xmlHierarchy.hasAll,
              null,
              createAnnotationMap(xmlHierarchy.annotations));
  
          assert !(this instanceof RolapCubeHierarchy);
  
          this.xmlHierarchy = xmlHierarchy;
          this.relation = xmlHierarchy.relation;
          if (xmlHierarchy.relation instanceof MondrianDef.InlineTable) {
              this.relation =
                  RolapUtil.convertInlineTableToRelation(
                      (MondrianDef.InlineTable) xmlHierarchy.relation,
                      getRolapSchema().getDialect());
          }
          this.memberReaderClass = xmlHierarchy.memberReaderClass;
          this.uniqueKeyLevelName = xmlHierarchy.uniqueKeyLevelName;
  
          // Create an 'all' level even if the hierarchy does not officially
          // have one.
          if (xmlHierarchy.allMemberName != null) {
              this.allMemberName = xmlHierarchy.allMemberName;
          }
          if (xmlHierarchy.allLevelName != null) {
              this.allLevelName = xmlHierarchy.allLevelName;
          }
          RolapLevel allLevel =
              new RolapLevel(
                  this,
                  this.allLevelName,
                  null,
                  true,
                  null,
                  0,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  RolapProperty.emptyArray,
                  RolapLevel.FLAG_ALL | RolapLevel.FLAG_UNIQUE,
                  null,
                  null,
                  RolapLevel.HideMemberCondition.Never,
                  LevelType.Regular, ALL_LEVEL_CARDINALITY,
                  Collections.<String, Annotation>emptyMap());
          allLevel.init(xmlCubeDimension);
          this.allMember = new RolapMemberBase(
              null, allLevel, RolapUtil.sqlNullValue,
              allMemberName, Member.MemberType.ALL);
          // assign "all member" caption
          if (xmlHierarchy.allMemberCaption != null
              && xmlHierarchy.allMemberCaption.length() > 0)
          {
              this.allMember.setCaption(xmlHierarchy.allMemberCaption);
          }
          this.allMember.setOrdinal(0);
  
          if (xmlHierarchy.levels.length == 0) {
              throw MondrianResource.instance().HierarchyHasNoLevels.ex(
                  getUniqueName());
          }
  
          Set<String> levelNameSet = new HashSet<String>();
          for (MondrianDef.Level level : xmlHierarchy.levels) {
              if (!levelNameSet.add(level.name)) {
                  throw MondrianResource.instance().HierarchyLevelNamesNotUnique
                      .ex(
                          getUniqueName(), level.name);
              }
          }
  
          // If the hierarchy has an 'all' member, the 'all' level is level 0.
          if (hasAll) {
              this.levels = new RolapLevel[xmlHierarchy.levels.length + 1];
              this.levels[0] = allLevel;
              for (int i = 0; i < xmlHierarchy.levels.length; i++) {
                  final MondrianDef.Level xmlLevel = xmlHierarchy.levels[i];
                  if (xmlLevel.getKeyExp() == null
                      && xmlHierarchy.memberReaderClass == null)
                  {
                      throw MondrianResource.instance()
                          .LevelMustHaveNameExpression.ex(xmlLevel.name);
                  }
                  levels[i + 1] = new RolapLevel(this, i + 1, xmlLevel);
              }
          } else {
              this.levels = new RolapLevel[xmlHierarchy.levels.length];
              for (int i = 0; i < xmlHierarchy.levels.length; i++) {
                  levels[i] = new RolapLevel(this, i, xmlHierarchy.levels[i]);
              }
          }
  
          if (xmlCubeDimension instanceof MondrianDef.DimensionUsage) {
              String sharedDimensionName =
                  ((MondrianDef.DimensionUsage) xmlCubeDimension).source;
              this.sharedHierarchyName = sharedDimensionName;
              if (subName != null) {
                  this.sharedHierarchyName += "." + subName; // e.g. "Time.Weekly"
              }
          } else {
              this.sharedHierarchyName = null;
          }
          if (xmlHierarchy.relation != null
              && xmlHierarchy.memberReaderClass != null)
          {
              throw MondrianResource.instance()
                  .HierarchyMustNotHaveMoreThanOneSource.ex(getUniqueName());
          }
          if (!Util.isEmpty(xmlHierarchy.caption)) {
              setCaption(xmlHierarchy.caption);
          }
          defaultMemberName = xmlHierarchy.defaultMember;
      }
  
      public static Map<String, Annotation> createAnnotationMap(
          MondrianDef.Annotations annotations)
      {
          if (annotations == null
              || annotations.array == null
              || annotations.array.length == 0)
          {
              return Collections.emptyMap();
          }
          // Use linked hash map because it retains order.
          final Map<String, Annotation> map =
              new LinkedHashMap<String, Annotation>();
          for (MondrianDef.Annotation annotation : annotations.array) {
              final String name = annotation.name;
              final String value = annotation.cdata;
              map.put(
                  annotation.name,
                  new Annotation() {
                      public String getName() {
                          return name;
                      }
  
                      public Object getValue() {
                          return value;
                      }
                  });
          }
          return map;
      }
  
      protected Logger getLogger() {
          return LOGGER;
      }
  
      public boolean equals(Object o) {
          if (this == o) {
              return true;
          }
          if (!(o instanceof RolapHierarchy)) {
              return false;
          }
  
          RolapHierarchy that = (RolapHierarchy)o;
          if (sharedHierarchyName == null || that.sharedHierarchyName == null) {
              return false;
          } else {
              return sharedHierarchyName.equals(that.sharedHierarchyName)
                  && getUniqueName().equals(that.getUniqueName());
          }
      }
  
      protected int computeHashCode() {
          return super.computeHashCode()
              ^ (sharedHierarchyName == null
                  ? 0
                  : sharedHierarchyName.hashCode());
      }
  
      /**
       * Initializes a hierarchy within the context of a cube.
       */
      void init(MondrianDef.CubeDimension xmlDimension) {
          // first create memberReader
          if (this.memberReader == null) {
              this.memberReader = getRolapSchema().createMemberReader(
                  sharedHierarchyName, this, memberReaderClass);
          }
          for (Level level : levels) {
              ((RolapLevel) level).init(xmlDimension);
          }
          if (defaultMemberName != null) {
              List<Id.Segment> uniqueNameParts;
              if (defaultMemberName.contains("[")) {
                  uniqueNameParts = Util.parseIdentifier(defaultMemberName);
              } else {
                  uniqueNameParts =
                      Collections.<Id.Segment>singletonList(
                          new Id.NameSegment(
                              defaultMemberName,
                              Id.Quoting.UNQUOTED));
              }
  
              // First look up from within this hierarchy. Works for unqualified
              // names, e.g. [USA].[CA].
              defaultMember = (Member) Util.lookupCompound(
                  getRolapSchema().getSchemaReader(),
                  this,
                  uniqueNameParts,
                  false,
                  Category.Member,
                  MatchType.EXACT);
  
              // Next look up within global context. Works for qualified names,
              // e.g. [Store].[USA].[CA] or [Time].[Weekly].[1997].[Q2].
              if (defaultMember == null) {
                  defaultMember = (Member) Util.lookupCompound(
                      getRolapSchema().getSchemaReader(),
                      new DummyElement(),
                      uniqueNameParts,
                      false,
                      Category.Member,
                      MatchType.EXACT);
              }
              if (defaultMember == null) {
                  throw Util.newInternal(
                      "Can not find Default Member with name \""
                      + defaultMemberName + "\" in Hierarchy \""
                      + getName() + "\"");
              }
          }
      }
  
      void setMemberReader(MemberReader memberReader) {
          this.memberReader = memberReader;
      }
  
      MemberReader getMemberReader() {
          return memberReader;
      }
  
      public Map<String, Annotation> getAnnotationMap() {
          return annotationMap;
      }
  
      RolapLevel newMeasuresLevel() {
          RolapLevel level =
              new RolapLevel(
                  this,
                  "MeasuresLevel",
                  null,
                  true,
                  null,
                  this.levels.length,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  null,
                  RolapProperty.emptyArray,
                  0,
                  null,
                  null,
                  RolapLevel.HideMemberCondition.Never,
                  LevelType.Regular,
                  "",
                  Collections.<String, Annotation>emptyMap());
          this.levels = Util.append(this.levels, level);
          return level;
      }
  
      /**
       * If this hierarchy has precisely one table, returns that table;
       * if this hierarchy has no table, return the cube's fact-table;
       * otherwise, returns null.
       */
      MondrianDef.Relation getUniqueTable() {
          if (relation instanceof MondrianDef.Relation) {
              return (MondrianDef.Relation) relation;
          } else if (relation instanceof MondrianDef.Join) {
              return null;
          } else {
              throw Util.newInternal(
                  "hierarchy's relation is a " + relation.getClass());
          }
      }
  
      boolean tableExists(String tableName) {
          return (relation != null) && getTable(tableName, relation) != null;
      }
  
      MondrianDef.Relation getTable(String tableName) {
          return relation == null ? null : getTable(tableName, relation);
      }
  
      private static MondrianDef.Relation getTable(
          String tableName,
          MondrianDef.RelationOrJoin relationOrJoin)
      {
          if (relationOrJoin instanceof MondrianDef.Relation) {
              MondrianDef.Relation relation =
                  (MondrianDef.Relation) relationOrJoin;
              if (relation.getAlias().equals(tableName)) {
                  return relation;
              } else {
                  return null;
              }
          } else {
              MondrianDef.Join join = (MondrianDef.Join) relationOrJoin;
              MondrianDef.Relation rel = getTable(tableName, join.left);
              if (rel != null) {
                  return rel;
              }
              return getTable(tableName, join.right);
          }
      }
  
      public RolapSchema getRolapSchema() {
          return (RolapSchema) dimension.getSchema();
      }
  
      public MondrianDef.RelationOrJoin getRelation() {
          return relation;
      }
  
      public MondrianDef.Hierarchy getXmlHierarchy() {
          return xmlHierarchy;
      }
  
      public Member getDefaultMember() {
          // use lazy initialization to get around bootstrap issues
          if (defaultMember == null) {
              List<RolapMember> rootMembers = memberReader.getRootMembers();
              final SchemaReader schemaReader =
                  getRolapSchema().getSchemaReader();
              List<RolapMember> calcMemberList =
                  Util.cast(schemaReader.getCalculatedMembers(getLevels()[0]));
              for (RolapMember rootMember
                  : UnionIterator.over(rootMembers, calcMemberList))
              {
                  if (rootMember.isHidden()) {
                      continue;
                  }
                  // Note: We require that the root member is not a hidden member
                  // of a ragged hierarchy, but we do not require that it is
                  // visible. In particular, if a cube contains no explicit
                  // measures, the default measure will be the implicitly defined
                  // [Fact Count] measure, which happens to be non-visible.
                  defaultMember = rootMember;
                  break;
              }
              if (defaultMember == null) {
                  throw MondrianResource.instance().InvalidHierarchyCondition.ex(
                      this.getUniqueName());
              }
          }
          return defaultMember;
      }
  
      public Member getNullMember() {
          // use lazy initialization to get around bootstrap issues
  