View Javadoc
1   package rydeen;
2   
3   import java.util.ArrayList;
4   import java.util.HashMap;
5   import java.util.List;
6   import java.util.Map;
7   
8   import rydeen.spi.ProcessorService;
9   
10  /**
11   * <p>
12   * Rydeenの1セッションの実行に関する設定を保持するクラスです. 具体的には,以下の情報を保持します.
13   * </p>
14   * <ul>
15   * <li>出力先</li>
16   * <li>処理対象となるファイル一覧</li>
17   * <li>処理を行う処理器の名前一覧</li>
18   * <li>処理器に渡すパラメータ</li>
19   * </ul>
20   * <p>
21   * このオブジェクトは1セッションの情報を保持します. セッションでは,処理器の名前一覧で指定された順で処理器が実行されます.
22   * その際,各処理器は処理器名とそのセッション中に現れる順番を3桁の数字で表した数字をハイフン(-) で繋げた文字列をidとして設定されます.
23   * すなわち,以下の3つの処理器が4つ指定された場合,次のようなidが設定されます.
24   * </p>
25   * <blockquote>processorA, processorB, processorC, processorA</blockquote>
26   * <blockquote>processorA-001, processorB-002, processorC-003, processorA-004</blockquote>
27   * <p>
28   * 処理器に渡すパラメータは実行時オプションで指定できます. パラメータは処理器名とその処理器のパラメータをドット(.)で区切った文字列,もしくは,
29   * 処理器のidとその処理器のパラメータをドット(.)で区切った文字列を, パラメータとして認識します.
30   * すなわち,上記の4つの処理器が指定された場合,processorBのparamB1を指定するには
31   * processorB.paramB1,もしくは,processorB-002.paramB1という名称を用います.
32   * </p>
33   * <p>
34   * また,processorAのparamA1を指定する場合,processorA.paramA1という名称を用いると, processorA-001,
35   * processorA-004の両方のparamA1を同時に指定することになります.
36   * 一方,processorA-001.paramA1という名称を用いれば,processorA-001のparamA1のみを設定できます.
37   * なお,簡略化のため,001のように,処理器名を省略することができます.すなわち,001はprocessorA-001と同一に扱われます.
38   * </p>
39   * <p>
40   * {@link #getProcessors <code>getProcessors</code>}メソッドを呼び出すと,
41   * {@link #getProcessorNames <code>getProcessorNames</code>}が返す全ての処理器を構築し,
42   * 同じ順序の配列で返します.返された処理器はid, パラメータが設定されています. ただし,指定された名前の処理器が見つからない場合は
43   * {@link UnknownProcessorException <code>UnknownProcessorException</code>}
44   * が,処理器のパラメータの値が衝突した場合は {@link ArgumentsConflictException
45   * <code>ArgumentsConflictException</code>}が投げられます.
46   * </p>
47   * <p>
48   * パラメータの衝突とは,同一の処理器の同一のパラメータに異なる値が指定されることを表します. すなわち,processorA-001.paramA1 =
49   * value1, processorA.paramA1 = value2の2つの方法で
50   * パラメータが指定された場合,processorA-001のparamA1はvalue1か,value2
51   * のどちらの値を採用すれば良いのか不明です.そのため,ArgumentsConflictException が投げられます.
52   * </p>
53   * 
54   * @author Haruaki Tamada
55   * @see Environment
56   */
57  public class Context{
58      private Environment env;
59      private List<String> targets = new ArrayList<String>();
60      private String destination;
61      private List<String> processorNames = new ArrayList<String>();
62      private Map<String, String> arguments = new HashMap<String, String>();
63  
64      /**
65       * 引数に与えられたEnvironment上で動く設定オブジェクトを構築します.
66       */
67      public Context(Environment env){
68          this.env = env;
69      }
70  
71      /**
72       * 引数に与えられたEnvironment上で動き,引数で与えられた contextの項目をデフォルト値として持つオブジェクトを構築します.
73       */
74      public Context(Context context){
75          this(context.getEnvironment());
76          destination = context.getDestination();
77          arguments.putAll(context.arguments);
78          processorNames.addAll(context.processorNames);
79          targets.addAll(context.targets);
80      }
81  
82      /**
83       * このオブジェクトが動いているEnvironmentを返します.
84       */
85      public Environment getEnvironment(){
86          return env;
87      }
88  
89      /**
90       * <p>
91       * 実行する処理器を構築して配列で返します.
92       * 作成された各処理器はパラメータが設定済みです.
93       * </p><p>
94       * 処理機は{@link #addProcessorName <code>addProcessorName</code>}
95       * 追加された順({@link #getProcessorNames <code>getProcessorNames</code>}が返す順番)
96       * で構築されます.
97       * </p>
98       * 
99       * @see #addProcessorName(String)
100      * @see #getProcessorNames()
101      * @throws UnknownProcessorException 指定された名前のProcessorが存在しなかった場合.
102      * @throws ArgumentsConflictException 同じ名前で違う値の引数が指定されていた場合.
103      */
104     public Processor[] getProcessors() throws ProcessorException{
105         String[] processorNames = getProcessorNames();
106         Processor[] processors = new Processor[processorNames.length];
107         Environment env = getEnvironment();
108         for(int i = 0; i < processors.length; i++){
109             ProcessorService service = env.getService(processorNames[i]);
110             if(service == null){
111                 throw new UnknownProcessorException(processorNames[i] + " is not found");
112             }
113             processors[i] = service.getProcessor();
114             processors[i].setId(String.format("%s-%03d", processorNames[i], i + 1));
115         }
116         updateArguments(processors);
117         return processors;
118     }
119 
120     /**
121      * 処理対象となるファイルを追加します.
122      * 
123      * @see #getTargets
124      */
125     public void addTarget(String arg){
126         targets.add(arg);
127     }
128 
129     /**
130      * 処理を行う処理器を処理機名で指定して追加します.
131      * 同じ処理機名を複数回追加した場合,その処理機が追加された回数だけ実行されることになります.
132      * 
133      * @see #getProcessorNames
134      */
135     public void addProcessorName(String processorName){
136         processorNames.add(processorName);
137     }
138 
139     /**
140      * 処理対象のファイル一覧を返します.
141      * 
142      * @see #addTarget
143      */
144     public String[] getTargets(){
145         return targets.toArray(new String[targets.size()]);
146     }
147 
148     /**
149      * 処理結果の出力先を返します. 設定されていない場合は,現在のディレクトリを表す"."を返します.
150      * 
151      * @see #setDestination
152      */
153     public String getDestination(){
154         if(destination == null){
155             destination = ".";
156         }
157         return destination;
158     }
159 
160     /**
161      * 現在設定されている処理器の数を返します.
162      */
163     public int getProcessorCount(){
164         return processorNames.size();
165     }
166 
167     /**
168      * 現在設定されている処理器の名前一覧を配列で返します.
169      * 返されるString配列には同じ文字列が含まれている場合があります.
170      * 
171      * @see #addProcessorName
172      * @see #getProcessors
173      */
174     public String[] getProcessorNames(){
175         return processorNames.toArray(new String[processorNames.size()]);
176     }
177 
178     /**
179      * <p>
180      * 引数に指定された名前で始まるArgumentの一覧を返します.
181      * 返されるArgumentはRydeenの実行時オプションで指定されたものです.
182      * </p>
183      * <p>
184      * 返されたArgumentに対して操作を行っても何も影響を与えません.
185      * 設定された値を変更したい場合は{@link #putArgument <code>putArgument</code>}
186      * メソッドを呼び出します. また,返されるArgumentオブジェクトの
187      * {@link Argument#getDescription <code>getDescription</code>}
188      * メソッドは常にnullを返します.
189      * </p>
190      * <p>
191      * 引数がnullの場合,もしくは,引数に指定された名前で始まる
192      * Argumentが存在しない場合は長さ0の配列を返します.
193      * </p>
194      */
195     public Argument[] findArguments(String keyPrefix){
196         List<Argument> list = new ArrayList<Argument>();
197         for(Map.Entry<String, String> entry : arguments.entrySet()){
198             if(entry.getKey().startsWith(keyPrefix)){
199                 list.add(new ArgumentImpl(entry.getKey(), entry.getValue()));
200             }
201         }
202         return list.toArray(new Argument[list.size()]);
203     }
204 
205     /**
206      * 指定された名前を持つArgumentの値を返します.
207      * 指定された名前のArgumentが存在しない場合はnullを返します.
208      */
209     public String getArgumentValue(String key){
210         if(key == null){
211             throw new NullPointerException();
212         }
213         return arguments.get(key);
214     }
215 
216     /**
217      * 指定された名前を持つArgumentが存在すればtrueを返し, 存在しなければfalseを返します.
218      * 引数がnullの場合,NullPointerExceptionが投げられます.
219      */
220     public boolean hasArgument(String key){
221         if(key == null){
222             throw new NullPointerException();
223         }
224         return arguments.containsKey(key);
225     }
226 
227     /**
228      * 指定された名前のArgumentの値を更新します.
229      * keyがnullの場合はNullPointerExceptionが投げられます.
230      * valueがnullの場合は値がnullに設定されます.
231      */
232     public void putArgument(String key, String value){
233         if(key == null){
234             throw new NullPointerException();
235         }
236         else{
237             arguments.put(key, value);
238         }
239     }
240 
241     /**
242      * 処理結果の出力先を設定します.
243      * 
244      * @see #getDestination
245      */
246     public void setDestination(String dest){
247         this.destination = dest;
248     }
249 
250     private void updateArguments(Processor[] processors) throws ArgumentsConflictException{
251         Map<String, Map<String, Argument>> argmap = new HashMap<String, Map<String, Argument>>();
252         List<String> conflictList = new ArrayList<String>();
253         // idから引数を更新.
254         for(Processor processor: processors){
255             String id = processor.getId();
256             String number = id.substring(processor.getProcessorName().length() + 1);
257 
258             updateArguments(id, id, argmap, findArguments(id + "."), conflictList);
259             updateArguments(number, id, argmap, findArguments(number + "."), conflictList);
260         }
261 
262         // Processor名から引数を更新.
263         for(Processor processor : processors){
264             String name = processor.getProcessorName();
265             updateArguments(name, processor.getId(), argmap,
266                     findArguments(name + "."), conflictList);
267         }
268         if(conflictList.size() == 0){
269             for(Processor processor : processors){
270                 Map<String, Argument> submap = argmap.get(processor.getId());
271                 Arguments args = processor.getArguments();
272                 if(submap != null){
273                     for(Argument arg : submap.values()){
274                         args.putValue(arg);
275                     }
276                 }
277             }
278         }
279         else{
280             throw new ArgumentsConflictException(
281                     conflictList.toArray(new String[conflictList.size()]));
282         }
283     }
284 
285     private void updateArguments(String prefix, String id,
286             Map<String, Map<String, Argument>> argmap, Argument[] args,
287             List<String> conflictList){
288         for(Argument arg : args){
289             Map<String, Argument> submap = argmap.get(id);
290             String key = arg.getName().substring(prefix.length() + 1);
291             if(submap == null){
292                 submap = new HashMap<String, Argument>();
293                 argmap.put(id, submap);
294             }
295             if(submap.get(key) != null){
296                 Argument argument = submap.get(key);
297                 String value = argument.getValue();
298                 if((value != null && !value.equals(arg.getValue())) ||
299                         (value == null && arg.getValue() != null)){
300                     conflictList.add(id + "." + key);
301                 }
302             }
303             else{
304                 submap.put(key, new ArgumentImpl(key, arg.getValue()));
305             }
306         }
307     }
308 }