public void addJoinInfo(ForeignInfo joinInfo)
 {
     if (_joinInfoList == null)
     {
         _joinInfoList = new ArrayList <ForeignInfo>();
     }
     _joinInfoList.add(joinInfo);
 }
        protected String resolveFixedConditionOverRelation(String fixedCondition)
        {
            String relationBeginMark = getRelationBeginMark();
            String relationEndMark   = getRelationEndMark();
            String remainder         = fixedCondition;

            while (true)
            {
                IndexOfInfo relationBeginIndex = Srl.indexOfFirst(remainder, relationBeginMark);
                if (relationBeginIndex == null)
                {
                    break;
                }
                remainder = relationBeginIndex.substringRear();
                IndexOfInfo relationEndIndex = Srl.indexOfFirst(remainder, relationEndMark);
                if (relationEndIndex == null)
                {
                    break;
                }

                // analyze:
                // - $$over($$localTable$$.memberStatus)$$
                // - $$over($$foreignTable$$.memberStatus, DISPLAY_ORDER)$$
                // - $$over(PURCHASE.product.productStatus)$$
                String relationExp = relationEndIndex.substringFront();
                String pointTable;
                String targetRelation;
                String secondArg;
                {
                    IndexOfInfo separatorIndex = Srl.indexOfFirst(relationExp, ".");
                    if (separatorIndex != null)
                    {
                        pointTable = separatorIndex.substringFrontTrimmed();
                        IndexOfInfo argIndex = Srl.indexOfFirst(separatorIndex.substringRearTrimmed(), ",");
                        targetRelation = argIndex != null?argIndex.substringFrontTrimmed() : separatorIndex
                                             .substringRearTrimmed();

                        secondArg = argIndex != null?argIndex.substringRearTrimmed() : null;
                    }
                    else
                    {
                        IndexOfInfo argIndex = Srl.indexOfFirst(relationExp, ",");
                        pointTable = argIndex != null?argIndex.substringFrontTrimmed() : Srl.trim(relationExp);

                        targetRelation = null;
                        secondArg      = argIndex != null?argIndex.substringRearTrimmed() : null;
                    }
                }

                ConditionQuery relationPointCQ;
                ConditionQuery columnTargetCQ;
                if (Srl.equalsPlain(pointTable, getLocalTableMark()))
                {
                    relationPointCQ = _localCQ;
                    if (targetRelation != null)
                    {
                        columnTargetCQ = invokeColumnTargetCQ(relationPointCQ, targetRelation);
                    }
                    else
                    {
                        String notice = "The relation on fixed condition is required if the table is not referrer.";
                        throwIllegalFixedConditionOverRelationException(notice, pointTable, null, fixedCondition);
                        return(null); // unreachable
                    }
                }
                else if (Srl.equalsPlain(pointTable, getForeignTableMark()))
                {
                    relationPointCQ = _foreignCQ;
                    columnTargetCQ  = relationPointCQ;
                    if (targetRelation == null)
                    {
                        String notice = "The relation on fixed condition is required if the table is not referrer.";
                        throwIllegalFixedConditionOverRelationException(notice, pointTable, null, fixedCondition);
                        return(null); // unreachable
                    }
                    // prepare fixed InlineView
                    if (_inlineViewResourceMap == null)
                    {
                        _inlineViewResourceMap = new LinkedHashMap <String, InlineViewResource>();
                    }
                    InlineViewResource resource;
                    if (_inlineViewResourceMap.containsKey(targetRelation))
                    {
                        resource = _inlineViewResourceMap.get(targetRelation);
                    }
                    else
                    {
                        resource = new InlineViewResource();
                        _inlineViewResourceMap.put(targetRelation, resource);
                    }
                    String columnName;
                    {
                        IndexOfInfo rearIndex = Srl.indexOfFirst(relationEndIndex.substringRearTrimmed(), ".");
                        if (rearIndex == null || rearIndex.getIndex() > 0)
                        {
                            String notice = "The OverRelation variable should continue to column after the variable.";
                            throwIllegalFixedConditionOverRelationException(notice, pointTable, targetRelation,
                                                                            fixedCondition);
                            return(null); // unreachable
                        }
                        String      columnStart = rearIndex.substringRear();
                        IndexOfInfo indexInfo   = Srl.indexOfFirst(columnStart, " ", ",", ")", "\n", "\t");
                        columnName = indexInfo != null?indexInfo.substringFront() : columnStart;
                    }
                    // the secondArg should be a column DB name, and then rear column is alias name
                    String resolvedColumn = secondArg != null ? secondArg + " as " + columnName : columnName;
                    resource.addAdditionalColumn(resolvedColumn);
                    List <String> splitList     = Srl.splitList(targetRelation, ".");
                    DBMeta        currentDBMeta = _dbmetaProvider.provideDBMeta(_foreignCQ.getTableDbName());
                    foreach (String element in splitList)
                    {
                        ForeignInfo foreignInfo = currentDBMeta.FindForeignInfo(element);
                        resource.addJoinInfo(foreignInfo);
                        currentDBMeta = foreignInfo.ForeignDBMeta;
                    }
                }
                else
                {
                    DBMeta pointDBMeta;
                    try {
                        pointDBMeta = _dbmetaProvider.provideDBMeta(pointTable);
                    } catch (DBMetaNotFoundException e) {
                        String notice = "The table for relation on fixed condition does not exist.";
                        throwIllegalFixedConditionOverRelationException(notice, pointTable, targetRelation, fixedCondition,
                                                                        e);
                        return(null); // unreachable
                    }
                    ConditionQuery referrerQuery = _localCQ.xgetReferrerQuery();
                    while (true)
                    {
                        if (referrerQuery == null) // means not found
                        {
                            break;
                        }
                        if (Srl.equalsPlain(pointDBMeta.TableDbName, referrerQuery.getTableDbName()))
                        {
                            break;
                        }
                        referrerQuery = referrerQuery.xgetReferrerQuery();
                    }
                    relationPointCQ = referrerQuery;
                    if (relationPointCQ == null)
                    {
                        String notice = "The table for relation on fixed condition was not found in the scope.";
                        throwIllegalFixedConditionOverRelationException(notice, pointTable, targetRelation, fixedCondition);
                        return(null); // unreachable
                    }
                    if (targetRelation != null)
                    {
                        columnTargetCQ = invokeColumnTargetCQ(relationPointCQ, targetRelation);
                    }
                    else
                    {
                        columnTargetCQ = relationPointCQ;
                    }
                }

                String relationVariable = relationBeginMark + relationExp + relationEndMark;
                String relationAlias    = columnTargetCQ.xgetAliasName();
                fixedCondition = replaceString(fixedCondition, relationVariable, relationAlias);

                // after case for loop
                remainder = relationEndIndex.substringRear();

                // for prevent from processing same one
                remainder = replaceString(remainder, relationVariable, relationAlias);
            }
            return(fixedCondition);
        }