Apexで連動項目の選択肢を取得する方法（改善版）

この記事は https://qiita.com/hrk623/items/483550948d2f7ed3bd58 を元に改善したものです。

元の方法は問題が二つあります。
１．連動先項目は450ぐらいを超えると、「System.LimitException: Apex CPU time limit exceeded」が発生され、うまく行けません。
２．連動元項目は32を超えると、33番目以上の連動元項目は取りません。バグの原因は元記事下から5行目のデータ型キャストだと分かりました。
　　下から5行目　⇒　Integer tBitVal = (`(Integer)`(Math.Pow(2, shiftBits)) ...

元記事のソースの元はSalesforce公開したJava版のサンプルです。JavaサンプルがJava Bitsetクラスを利用して、うまく行きました。ApexにJavaのようなBitsetクラスはないので、元作者さんがApex用のBitsetを作ってくれましたが、ちょっと雑だなあと感じました。

新方法はBitsetが要らなくて、Apexらしく処理で挑戦しました。
連動元項目が150個ぐらい、連動先項目が1000ぐらいのデータに対して、1秒未満の結果が出ました。

以下、新方法のソースになります。

```java:Example.apxc
public class Example {
    private static String INITIAL_VALUE = '';
    // 選択リストで選択された値が格納される変数
    public SomeObject objectInstance {get; set;}

    public List<SelectOption> DependentPickListOptions{
        get{
            List<SelectOption> options = new List<SelectOption>();
           
            // 選択リストの一番上にデフォルトの選択値を設定
            options.add(new SelectOption(INITIAL_VALUE, '-- なし --'));

            // 制御項目ctrlPicklistが選択されている時に連動項目depPicklistで選べる選択肢を取得する。
            if( this.objectInstance.ctrlPicklist != null || this.objectInstance.ctrlPicklist != INITIAL_VALUE ){
                List<DepPickListCtrl.TPicklistEntry> tPicklistEntries = 
                    DepPickListCtrl.GetDependentOptions('SomeObject','ctrlPicklist','depPicklist').get(this.objectInstance.ctrlPicklist);

                for( DepPickListCtrl.TPicklistEntry e : tPicklistEntries ){
                    options.add(new SelectOption(e.value, e.label));
                }
            }
            return options;
        }
    }

    // コンストラクタ
    public Example(){
        this.objectInstance = new SomeObject();
    }     
}
```




下のApexクラスを同じ組織に一つ追加します。

```java:DepPickListCtrl.apxc
/* Reference
 * Great thanks to Neel
 * http://titancronus.com/blog/2014/05/01/salesforce-acquiring-dependent-picklists-in-apex/
 */

public class DepPickListCtrl {
    public static Map<String,List<DepPickListCtrl.TPicklistEntry>> GetDependentOptions(String pObjName, String pControllingFieldName, String pDependentFieldName){
        Map<String,List<DepPickListCtrl.TPicklistEntry>> objResults = new Map<String,List<DepPickListCtrl.TPicklistEntry>>();
        //get the string to sobject global map
        Map<String,Schema.SObjectType> objGlobalMap = Schema.getGlobalDescribe();
        if (!objGlobalMap.containsKey(pObjName))
            return objResults;
        //get the type being dealt with
        Schema.SObjectType pType = objGlobalMap.get(pObjName);
        Map<String, Schema.SObjectField> objFieldMap = pType.getDescribe().fields.getMap();
        //verify field names
        if (!objFieldMap.containsKey(pControllingFieldName) || !objFieldMap.containsKey(pDependentFieldName))
            return objResults;     
        //get the control values   
        List<Schema.PicklistEntry> ctrl_ple = objFieldMap.get(pControllingFieldName).getDescribe().getPicklistValues();
        //get the dependent values
        List<Schema.PicklistEntry> dep_ple = objFieldMap.get(pDependentFieldName).getDescribe().getPicklistValues();
        //iterate through the values and get the ones valid for the controlling field name
        DepPickListCtrl.DependentValidFor dvf=new DepPickListCtrl.DependentValidFor();

        //set up the results
        for(Integer pControllingIndex=0; pControllingIndex<ctrl_ple.size(); pControllingIndex++){          
            //get the pointer to the entry
            Schema.PicklistEntry ctrl_entry = ctrl_ple[pControllingIndex];
            //get the label
            String pControllingLabel = ctrl_entry.getLabel();
            //create the entry with the label
            objResults.put(pControllingLabel,new List<DepPickListCtrl.TPicklistEntry>());
        }
        //cater for null and empty
        objResults.put('',new List<DepPickListCtrl.TPicklistEntry>());
        objResults.put(null,new List<DepPickListCtrl.TPicklistEntry>());
        //check the dependent values
        for(Integer pDependentIndex=0; pDependentIndex<dep_ple.size(); pDependentIndex++){          
            //get the pointer to the dependent index
            Schema.PicklistEntry dep_entry = dep_ple[pDependentIndex];
            //get the valid for
            String pEntryStructure = JSON.serialize(dep_entry);                
            DepPickListCtrl.TPicklistEntry objDepPLE = (DepPickListCtrl.TPicklistEntry)JSON.deserialize(pEntryStructure, DepPickListCtrl.TPicklistEntry.class);
            //if valid for is empty, skip
            if (objDepPLE.validFor==null || objDepPLE.validFor==''){
                continue;
            }
            //iterate through the controlling values
            for(Integer idx : dvf.getControlIndexes(objDepPLE.validFor)){
                //get the label
                String pControllingValue = ctrl_ple[idx].getValue();
                objResults.get(pControllingValue).add(objDepPLE);
            }
        } 
        return objResults;
    }
    public class TPicklistEntry{
        public string active {get;set;}
        public string defaultValue {get;set;}
        public string label {get;set;}
        public string value {get;set;}
        public string validFor {get;set;}
        public TPicklistEntry(){
        }
    }
    private class DependentValidFor {
        private Map<String,Set<Integer>> mapValidForIndexes=new Map<String,Set<Integer>>();
        public Set<Integer> getControlIndexes(String validFor){
            if(mapValidForIndexes.containsKey(validFor))
                return mapValidForIndexes.get(validFor);
            String hex=EncodingUtil.convertToHex(EncodingUtil.base64Decode(validFor));
            Set<Integer> result=new Set<Integer>();
            for(Integer i=0; i<hex.length(); i++){
                String b=hex.substring(i, i+1);
                Integer off=i*4;
                switch on b {   //0123(index)
                    when '0'{   //0000
                    }
                    when '1'{   //0001
                        result.add(off+3);
                    }
                    when '2'{   //0010
                        result.add(off+2);
                    }
                    when '3'{   //0011
                        result.add(off+2);
                        result.add(off+3);
                    }
                    when '4'{   //0100
                        result.add(off+1);
                    }
                    when '5'{   //0101
                        result.add(off+1);
                        result.add(off+3);
                    }
                    when '6'{   //0110
                        result.add(off+1);
                        result.add(off+2);
                    }
                    when '7'{   //0111
                        result.add(off+1);
                        result.add(off+2);
                        result.add(off+3);
                    }
                    when '8'{   //1000
                        result.add(off+0);
                    }
                    when '9'{   //1001
                        result.add(off+0);
                        result.add(off+3);
                    }
                    when 'a'{   //1010
                        result.add(off+0);
                        result.add(off+2);
                    }
                    when 'b'{   //1011
                        result.add(off+0);
                        result.add(off+2);
                        result.add(off+3);
                    }
                    when 'c'{   //1100
                        result.add(off+0);
                        result.add(off+1);
                    }
                    when 'd'{   //1101
                        result.add(off+0);
                        result.add(off+1);
                        result.add(off+3);
                    }
                    when 'e'{   //1110
                        result.add(off+0);
                        result.add(off+1);
                        result.add(off+2);
                    }
                    when 'f'{   //1111
                        result.add(off+0);
                        result.add(off+1);
                        result.add(off+2);
                        result.add(off+3);
                    }
                }
            }
            mapValidForIndexes.put(validFor,result);
            return result;
        }
    }
}
```

## 動作確認
V47