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 }