Coverage Report - org.seasar.cubby.routing.impl.PathResolverImpl
 
Classes in this File Line Coverage Branch Coverage Complexity
PathResolverImpl
93%
136/147
84%
74/88
0
PathResolverImpl$1
100%
3/3
N/A
0
PathResolverImpl$2
100%
7/7
100%
4/4
0
PathResolverImpl$RoutingComparator
76%
16/21
64%
9/14
0
 
 1  
 /*
 2  
  * Copyright 2004-2008 the Seasar Foundation and the Others.
 3  
  *
 4  
  * Licensed under the Apache License, Version 2.0 (the "License");
 5  
  * you may not use this file except in compliance with the License.
 6  
  * You may obtain a copy of the License at
 7  
  *
 8  
  *     http://www.apache.org/licenses/LICENSE-2.0
 9  
  *
 10  
  * Unless required by applicable law or agreed to in writing, software
 11  
  * distributed under the License is distributed on an "AS IS" BASIS,
 12  
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 13  
  * either express or implied. See the License for the specific language
 14  
  * governing permissions and limitations under the License.
 15  
  */
 16  
 package org.seasar.cubby.routing.impl;
 17  
 
 18  
 import static org.seasar.cubby.CubbyConstants.INTERNAL_FORWARD_DIRECTORY;
 19  
 
 20  
 import java.io.IOException;
 21  
 import java.io.UnsupportedEncodingException;
 22  
 import java.lang.reflect.Method;
 23  
 import java.net.URLDecoder;
 24  
 import java.net.URLEncoder;
 25  
 import java.util.ArrayList;
 26  
 import java.util.Collections;
 27  
 import java.util.Comparator;
 28  
 import java.util.HashMap;
 29  
 import java.util.Iterator;
 30  
 import java.util.List;
 31  
 import java.util.Map;
 32  
 import java.util.TreeMap;
 33  
 import java.util.Map.Entry;
 34  
 import java.util.regex.Matcher;
 35  
 import java.util.regex.Pattern;
 36  
 
 37  
 import org.seasar.cubby.action.Action;
 38  
 import org.seasar.cubby.action.Path;
 39  
 import org.seasar.cubby.action.RequestMethod;
 40  
 import org.seasar.cubby.controller.ClassDetector;
 41  
 import org.seasar.cubby.controller.DetectClassProcessor;
 42  
 import org.seasar.cubby.exception.ActionRuntimeException;
 43  
 import org.seasar.cubby.exception.DuplicateRoutingRuntimeException;
 44  
 import org.seasar.cubby.exception.IllegalRoutingRuntimeException;
 45  
 import org.seasar.cubby.routing.InternalForwardInfo;
 46  
 import org.seasar.cubby.routing.PathResolver;
 47  
 import org.seasar.cubby.routing.PathTemplateParser;
 48  
 import org.seasar.cubby.routing.Routing;
 49  
 import org.seasar.cubby.util.CubbyUtils;
 50  
 import org.seasar.cubby.util.QueryStringBuilder;
 51  
 import org.seasar.framework.convention.NamingConvention;
 52  
 import org.seasar.framework.exception.IORuntimeException;
 53  
 import org.seasar.framework.log.Logger;
 54  
 import org.seasar.framework.util.ClassUtil;
 55  
 import org.seasar.framework.util.Disposable;
 56  
 import org.seasar.framework.util.DisposableUtil;
 57  
 import org.seasar.framework.util.StringUtil;
 58  
 
 59  
 /**
 60  
  * クラスパスから {@link Action} を検索し、クラス名やメソッド名、そのクラスやメソッドに指定された
 61  
  * {@link org.seasar.cubby.action.Path}
 62  
  * の情報からアクションのパスを抽出し、リクエストされたパスをどのメソッドに振り分けるかを決定します。
 63  
  * 
 64  
  * @author baba
 65  
  * @since 1.0.0
 66  
  */
 67  358
 public class PathResolverImpl implements PathResolver, DetectClassProcessor,
 68  
                 Disposable {
 69  
 
 70  
         /** ロガー */
 71  1
         private static final Logger logger = Logger
 72  
                         .getLogger(PathResolverImpl.class);
 73  
 
 74  
         /** インスタンスが初期化済みであることを示します。 */
 75  
         private boolean initialized;
 76  
 
 77  
         /** 命名規約。 */
 78  
         private NamingConvention namingConvention;
 79  
 
 80  
         /** ルーティングのコンパレータ。 */
 81  55
         private final Comparator<Routing> routingComparator = new RoutingComparator();
 82  
 
 83  
         /** 登録されたルーティングのマップ。 */
 84  55
         private final Map<Routing, Routing> routings = new TreeMap<Routing, Routing>(
 85  
                         routingComparator);
 86  
 
 87  
         /** クラスパスを走査してクラスを検出するクラス。 */
 88  
         private ClassDetector classDetector;
 89  
 
 90  
         /** パステンプレートのパーサー。 */
 91  
         private PathTemplateParser pathTemplateParser;
 92  
 
 93  
         /** 手動登録用のプライオリティカウンタ。 */
 94  55
         private int priorityCounter = 0;
 95  
 
 96  
         /**
 97  
          * インスタンス化します。
 98  
          */
 99  55
         public PathResolverImpl() {
 100  55
         }
 101  
 
 102  
         /**
 103  
          * ルーティング情報を取得します。
 104  
          * 
 105  
          * @return ルーティング情報
 106  
          */
 107  
         public List<Routing> getRoutings() {
 108  7
                 initialize();
 109  7
                 return Collections.unmodifiableList(new ArrayList<Routing>(routings
 110  
                                 .values()));
 111  
         }
 112  
 
 113  
         /**
 114  
          * クラスパスを走査してクラスを検出するクラスを設定します。
 115  
          * 
 116  
          * @param classDetector
 117  
          *            クラスパスを走査してクラスを検出するクラス
 118  
          */
 119  
         public void setClassDetector(final ClassDetector classDetector) {
 120  56
                 this.classDetector = classDetector;
 121  56
         }
 122  
 
 123  
         /**
 124  
          * パステンプレートのパーサーを設定します。
 125  
          * 
 126  
          * @param pathTemplateParser
 127  
          *            パステンプレートのパーサー
 128  
          */
 129  
         public void setPathTemplateParser(
 130  
                         final PathTemplateParser pathTemplateParser) {
 131  55
                 this.pathTemplateParser = pathTemplateParser;
 132  55
         }
 133  
 
 134  
         /**
 135  
          * 初期化します。
 136  
          */
 137  
         public void initialize() {
 138  20
                 if (initialized) {
 139  4
                         return;
 140  
                 }
 141  16
                 classDetector.detect();
 142  16
                 DisposableUtil.add(this);
 143  16
                 initialized = true;
 144  16
         }
 145  
 
 146  
         /**
 147  
          * {@inheritDoc}
 148  
          */
 149  
         public void dispose() {
 150  16
                 final List<Routing> removes = new ArrayList<Routing>();
 151  16
                 for (final Routing routing : routings.keySet()) {
 152  362
                         if (routing.isAuto()) {
 153  354
                                 removes.add(routing);
 154  
                         }
 155  
                 }
 156  16
                 for (final Routing routing : removes) {
 157  354
                         routings.remove(routing);
 158  
                 }
 159  16
                 initialized = false;
 160  16
         }
 161  
 
 162  
         /**
 163  
          * ルーティング情報を登録します。
 164  
          * <p>
 165  
          * クラスパスを検索して自動登録されるルーティング情報以外にも、このメソッドによって手動でルーティング情報を登録できます。
 166  
          * </p>
 167  
          * 
 168  
          * @param actionPath
 169  
          *            アクションのパス
 170  
          * @param actionClass
 171  
          *            アクションクラス
 172  
          * @param methodName
 173  
          *            アクションメソッド名
 174  
          */
 175  
         public void add(final String actionPath,
 176  
                         final Class<? extends Action> actionClass, final String methodName) {
 177  88
                 this.add(actionPath, actionClass, methodName, new RequestMethod[0]);
 178  86
         }
 179  
 
 180  
         /**
 181  
          * ルーティング情報を登録します。
 182  
          * <p>
 183  
          * クラスパスを検索して自動登録されるルーティング情報以外にも、このメソッドによって手動でルーティング情報を登録できます。
 184  
          * </p>
 185  
          * 
 186  
          * @param actionPath
 187  
          *            アクションのパス
 188  
          * @param actionClass
 189  
          *            アクションクラス
 190  
          * @param methodName
 191  
          *            アクションメソッド名
 192  
          * @param requestMethods
 193  
          *            リクエストメソッド
 194  
          */
 195  
         public void add(final String actionPath,
 196  
                         final Class<? extends Action> actionClass, final String methodName,
 197  
                         final RequestMethod... requestMethods) {
 198  
 
 199  90
                 final Method method = ClassUtil.getMethod(actionClass, methodName,
 200  
                                 new Class<?>[0]);
 201  90
                 if (requestMethods == null || requestMethods.length == 0) {
 202  260
                         for (final RequestMethod requestMethod : CubbyUtils.DEFAULT_ACCEPT_ANNOTATION
 203  
                                         .value()) {
 204  174
                                 this.add(actionPath, actionClass, method, requestMethod, false);
 205  
                         }
 206  
                 } else {
 207  4
                         for (final RequestMethod requestMethod : requestMethods) {
 208  2
                                 this.add(actionPath, actionClass, method, requestMethod, false);
 209  
                         }
 210  
                 }
 211  88
         }
 212  
 
 213  
         /**
 214  
          * ルーティング情報を登録します。
 215  
          * 
 216  
          * @param actionPath
 217  
          *            アクションのパス
 218  
          * @param actionClass
 219  
          *            アクションクラス
 220  
          * @param method
 221  
          *            アクションメソッド
 222  
          * @param requestMethods
 223  
          *            リクエストメソッド
 224  
          * @param auto
 225  
          *            自動登録かどうか
 226  
          */
 227  
         private void add(final String actionPath,
 228  
                         final Class<? extends Action> actionClass, final Method method,
 229  
                         final RequestMethod requestMethod, final boolean auto) {
 230  
 
 231  538
                 if (!CubbyUtils.isActionClass(actionClass)) {
 232  1
                         throw new IllegalRoutingRuntimeException("ECUB0002",
 233  
                                         new Object[] { actionClass });
 234  537
                 } else if (!CubbyUtils.isActionMethod(method)) {
 235  1
                         throw new IllegalRoutingRuntimeException("ECUB0003",
 236  
                                         new Object[] { method });
 237  
                 }
 238  
 
 239  536
                 final List<String> uriParameterNames = new ArrayList<String>();
 240  536
                 final String uriRegex = pathTemplateParser.parse(actionPath,
 241  536
                                 new PathTemplateParser.Handler() {
 242  
 
 243  
                                         public String handle(final String name, final String regex) {
 244  350
                                                 uriParameterNames.add(name);
 245  350
                                                 return regexGroup(regex);
 246  
                                         }
 247  
 
 248  
                                 });
 249  536
                 final Pattern pattern = Pattern.compile("^" + uriRegex + "$");
 250  
 
 251  536
                 final String onSubmit = CubbyUtils.getOnSubmit(method);
 252  
 
 253  536
                 final int priority = auto ? CubbyUtils.getPriority(method)
 254  
                                 : priorityCounter++;
 255  
 
 256  536
                 final Routing routing = new RoutingImpl(actionClass, method,
 257  
                                 actionPath, uriParameterNames, pattern, requestMethod,
 258  
                                 onSubmit, priority, auto);
 259  
 
 260  536
                 if (routings.containsKey(routing)) {
 261  0
                         final Routing duplication = routings.get(routing);
 262  0
                         if (!routing.getActionClass().equals(duplication.getActionClass())
 263  
                                         || !routing.getMethod().equals(duplication.getMethod())) {
 264  0
                                 throw new DuplicateRoutingRuntimeException("ECUB0001",
 265  
                                                 new Object[] { routing, duplication });
 266  
                         }
 267  0
                 } else {
 268  536
                         routings.put(routing, routing);
 269  536
                         if (logger.isDebugEnabled()) {
 270  536
                                 logger.log("DCUB0007", new Object[] { routing });
 271  
                         }
 272  
                 }
 273  536
         }
 274  
 
 275  
         /**
 276  
          * {@inheritDoc}
 277  
          */
 278  
         public InternalForwardInfo getInternalForwardInfo(final String path,
 279  
                         final String requestMethod, final String characterEncoding) {
 280  13
                 initialize();
 281  
 
 282  13
                 final InternalForwardInfo internalForwardInfo = findInternalForwardInfo(
 283  
                                 decode(path, characterEncoding), requestMethod,
 284  
                                 characterEncoding);
 285  13
                 return internalForwardInfo;
 286  
         }
 287  
 
 288  
         /**
 289  
          * 指定されたパス、メソッドに対応する内部フォワード情報を検索します。
 290  
          * 
 291  
          * @param path
 292  
          *            リクエストのパス
 293  
          * @param requestMethod
 294  
          *            リクエストのメソッド
 295  
          * @return 内部フォワード情報、対応する内部フォワード情報が登録されていない場合は <code>null</code>
 296  
          */
 297  
         private InternalForwardInfo findInternalForwardInfo(final String path,
 298  
                         final String requestMethod, final String characterEncoding) {
 299  13
                 final Iterator<Routing> iterator = routings.values().iterator();
 300  190
                 while (iterator.hasNext()) {
 301  188
                         final Routing routing = iterator.next();
 302  188
                         final Matcher matcher = routing.getPattern().matcher(path);
 303  188
                         if (matcher.find()) {
 304  12
                                 if (routing.isAcceptable(requestMethod)) {
 305  11
                                         final Map<String, Routing> onSubmitRoutings = new HashMap<String, Routing>();
 306  11
                                         onSubmitRoutings.put(routing.getOnSubmit(), routing);
 307  127
                                         while (iterator.hasNext()) {
 308  116
                                                 final Routing anotherRouting = iterator.next();
 309  116
                                                 if (routing.getPattern().pattern().equals(
 310  
                                                                 anotherRouting.getPattern().pattern())
 311  
                                                                 && routing.getRequestMethod().equals(
 312  
                                                                                 anotherRouting.getRequestMethod())) {
 313  0
                                                         onSubmitRoutings.put(anotherRouting.getOnSubmit(),
 314  
                                                                         anotherRouting);
 315  
                                                 }
 316  116
                                         }
 317  
 
 318  11
                                         final Map<String, String[]> uriParameters = new HashMap<String, String[]>();
 319  19
                                         for (int i = 0; i < matcher.groupCount(); i++) {
 320  8
                                                 final String name = routing.getUriParameterNames().get(
 321  
                                                                 i);
 322  8
                                                 final String value = matcher.group(i + 1);
 323  8
                                                 uriParameters.put(name, new String[] { value });
 324  
                                         }
 325  11
                                         final String inernalFowardPath = buildInternalForwardPath(
 326  
                                                         uriParameters, characterEncoding);
 327  
 
 328  11
                                         final InternalForwardInfo internalForwardInfo = new InternalForwardInfoImpl(
 329  
                                                         inernalFowardPath, onSubmitRoutings);
 330  
 
 331  11
                                         return internalForwardInfo;
 332  
                                 }
 333  
                         }
 334  177
                 }
 335  
 
 336  2
                 return null;
 337  
         }
 338  
 
 339  
         /**
 340  
          * {@inheritDoc}
 341  
          */
 342  
         public String buildInternalForwardPath(
 343  
                         final Map<String, String[]> parameters,
 344  
                         final String characterEncoding) {
 345  17
                 final StringBuilder path = new StringBuilder(100);
 346  17
                 path.append(INTERNAL_FORWARD_DIRECTORY);
 347  17
                 if (parameters != null && !parameters.isEmpty()) {
 348  8
                         path.append("?");
 349  8
                         final QueryStringBuilder query = new QueryStringBuilder();
 350  8
                         if (!StringUtil.isEmpty(characterEncoding)) {
 351  8
                                 query.setEncode(characterEncoding);
 352  
                         }
 353  8
                         for (final Entry<String, String[]> entry : parameters.entrySet()) {
 354  24
                                 for (final String parameter : entry.getValue()) {
 355  12
                                         query.addParam(entry.getKey(), parameter);
 356  
                                 }
 357  
                         }
 358  8
                         path.append(query.toString());
 359  
                 }
 360  17
                 return path.toString();
 361  
         }
 362  
 
 363  
         /**
 364  
          * 命名規約を設定します。
 365  
          * 
 366  
          * @param namingConvention
 367  
          *            命名規約
 368  
          */
 369  
         public void setNamingConvention(final NamingConvention namingConvention) {
 370  55
                 this.namingConvention = namingConvention;
 371  55
         }
 372  
 
 373  
         /**
 374  
          * 指定された正規表現を括弧「()」で囲んで正規表現のグループにします。
 375  
          * 
 376  
          * @param regex
 377  
          *            正規表現
 378  
          * @return 正規表現のグループ
 379  
          */
 380  
         private static String regexGroup(final String regex) {
 381  350
                 return "(" + regex + ")";
 382  
         }
 383  
 
 384  
         /**
 385  
          * ルーティングのコンパレータ。
 386  
          * 
 387  
          * @author baba
 388  
          */
 389  4582
         static class RoutingComparator implements Comparator<Routing> {
 390  
 
 391  
                 /**
 392  
                  * routing1 と routing2 を比較します。
 393  
                  * <p>
 394  
                  * 正規表現パターンと HTTP メソッドが同じ場合は同値とみなします。
 395  
                  * </p>
 396  
                  * <p>
 397  
                  * また、大小関係は以下のようになります。
 398  
                  * <ul>
 399  
                  * <li>優先度(@link {@link Path#priority()})が小さい順</li>
 400  
                  * <li>URI 埋め込みパラメータが少ない順</li>
 401  
                  * <li>正規表現の順(@link {@link String#compareTo(String)})</li>
 402  
                  * </ul>
 403  
                  * </p>
 404  
                  * 
 405  
                  * @param routing1
 406  
                  *            比較対象1
 407  
                  * @param routing2
 408  
                  *            比較対象2
 409  
                  * @return 比較結果
 410  
                  */
 411  
                 public int compare(final Routing routing1, final Routing routing2) {
 412  4526
                         int compare = routing1.getPriority() - routing2.getPriority();
 413  4526
                         if (compare != 0) {
 414  846
                                 return compare;
 415  
                         }
 416  3680
                         compare = routing1.getUriParameterNames().size()
 417  
                                         - routing2.getUriParameterNames().size();
 418  3680
                         if (compare != 0) {
 419  978
                                 return compare;
 420  
                         }
 421  2702
                         compare = routing1.getPattern().pattern().compareTo(
 422  
                                         routing2.getPattern().pattern());
 423  2702
                         if (compare != 0) {
 424  1720
                                 return compare;
 425  
                         }
 426  982
                         compare = routing1.getRequestMethod().compareTo(
 427  
                                         routing2.getRequestMethod());
 428  982
                         if (compare != 0) {
 429  626
                                 return compare;
 430  
                         }
 431  356
                         if (routing1.getOnSubmit() == routing2.getOnSubmit()) {
 432  356
                                 compare = 0;
 433  0
                         } else if (routing1.getOnSubmit() == null) {
 434  0
                                 compare = -1;
 435  0
                         } else if (routing2.getOnSubmit() == null) {
 436  0
                                 compare = 1;
 437  
                         } else {
 438  0
                                 compare = routing1.getOnSubmit().compareTo(
 439  
                                                 routing2.getOnSubmit());
 440  
                         }
 441  356
                         return compare;
 442  
                 }
 443  
         }
 444  
 
 445  
         /**
 446  
          * {@inheritDoc}
 447  
          */
 448  
         public String reverseLookup(final Class<? extends Action> actionClass,
 449  
                         final String methodName, final Map<String, String[]> parameters,
 450  
                         final String characterEncoding) {
 451  18
                 final Routing routing = findRouting(actionClass, methodName);
 452  17
                 final String actionPath = routing.getActionPath();
 453  17
                 final Map<String, String[]> copyOfParameters = new HashMap<String, String[]>(
 454  
                                 parameters);
 455  17
                 final StringBuilder path = new StringBuilder(100);
 456  17
                 path.append(pathTemplateParser.parse(actionPath,
 457  17
                                 new PathTemplateParser.Handler() {
 458  
 
 459  
                                         public String handle(final String name, final String regex) {
 460  10
                                                 if (!copyOfParameters.containsKey(name)) {
 461  1
                                                         throw new ActionRuntimeException("ECUB0104",
 462  
                                                                         new Object[] { actionPath, name });
 463  
                                                 }
 464  9
                                                 final String value = copyOfParameters.remove(name)[0];
 465  9
                                                 if (!value.matches(regex)) {
 466  1
                                                         throw new ActionRuntimeException("ECUB0105",
 467  
                                                                         new Object[] { actionPath, name, value,
 468  
                                                                                         regex });
 469  
                                                 }
 470  8
                                                 return encode(value, characterEncoding);
 471  
                                         }
 472  
 
 473  
                                 }));
 474  
 
 475  15
                 if (!copyOfParameters.isEmpty()) {
 476  8
                         final QueryStringBuilder builder = new QueryStringBuilder();
 477  8
                         if (characterEncoding != null) {
 478  8
                                 builder.setEncode(characterEncoding);
 479  
                         }
 480  8
                         for (final Entry<String, String[]> entry : copyOfParameters
 481  
                                         .entrySet()) {
 482  22
                                 for (final String value : entry.getValue()) {
 483  11
                                         builder.addParam(entry.getKey(), value);
 484  
                                 }
 485  
                         }
 486  8
                         path.append('?');
 487  8
                         path.append(builder.toString());
 488  
                 }
 489  
 
 490  15
                 return path.toString();
 491  
         }
 492  
 
 493  
         /**
 494  
          * 指定されたクラス、メソッドに対応するルーティング情報を検索します。
 495  
          * 
 496  
          * @param actionClass
 497  
          *            クラス
 498  
          * @param methodName
 499  
          *            メソッド
 500  
          * @return ルーティング情報
 501  
          * @throws ActionRuntimeException
 502  
          *             ルーティング情報が見つからなかった場合
 503  
          */
 504  
         private Routing findRouting(final Class<? extends Action> actionClass,
 505  
                         final String methodName) {
 506  18
                 for (final Routing routing : routings.values()) {
 507  47
                         if (actionClass.getCanonicalName().equals(
 508  
                                         routing.getActionClass().getCanonicalName())) {
 509  47
                                 if (methodName.equals(routing.getMethod().getName())) {
 510  17
                                         return routing;
 511  
                                 }
 512  
                         }
 513  
                 }
 514  1
                 throw new ActionRuntimeException("ECUB0103", new Object[] {
 515  
                                 actionClass, methodName });
 516  
         }
 517  
 
 518  
         /**
 519  
          * {@inheritDoc}
 520  
          * <p>
 521  
          * 指定されたパッケージ名、クラス名から導出されるクラスがアクションクラスだった場合はルーティングを登録します。
 522  
          * </p>
 523  
          */
 524  
         public void processClass(final String packageName,
 525  
                         final String shortClassName) {
 526  5797
                 if (shortClassName.indexOf('$') != -1) {
 527  1887
                         return;
 528  
                 }
 529  3910
                 final String className = ClassUtil.concatName(packageName,
 530  
                                 shortClassName);
 531  3910
                 if (!namingConvention.isTargetClassName(className)) {
 532  3659
                         return;
 533  
                 }
 534  251
                 if (!className.endsWith(namingConvention.getActionSuffix())) {
 535  178
                         return;
 536  
                 }
 537  73
                 final Class<?> clazz = ClassUtil.forName(className);
 538  73
                 if (namingConvention.isSkipClass(clazz)) {
 539  0
                         return;
 540  
                 }
 541  73
                 if (!CubbyUtils.isActionClass(clazz)) {
 542  14
                         return;
 543  
                 }
 544  59
                 final Class<? extends Action> actionClass = cast(clazz);
 545  
 
 546  1205
                 for (final Method method : clazz.getMethods()) {
 547  1146
                         if (CubbyUtils.isActionMethod(method)) {
 548  202
                                 final String actionPath = CubbyUtils.getActionPath(actionClass,
 549  
                                                 method);
 550  202
                                 final RequestMethod[] acceptableRequestMethods = CubbyUtils
 551  
                                                 .getAcceptableRequestMethods(clazz, method);
 552  564
                                 for (final RequestMethod requestMethod : acceptableRequestMethods) {
 553  362
                                         add(actionPath, actionClass, method, requestMethod, true);
 554  
                                 }
 555  
                         }
 556  
                 }
 557  59
         }
 558  
 
 559  
         /**
 560  
          * 指定されたクラスを <code>Class&lt;? extends Action&gt;</code> にキャストします。
 561  
          * 
 562  
          * @param clazz
 563  
          *            クラス
 564  
          * @return キャストされたクラス
 565  
          */
 566  
         @SuppressWarnings("unchecked")
 567  
         private static Class<? extends Action> cast(final Class<?> clazz) {
 568  59
                 return Class.class.cast(clazz);
 569  
         }
 570  
 
 571  
         /**
 572  
          * 指定された文字列を URL エンコードします。
 573  
          * 
 574  
          * @param str
 575  
          *            文字列
 576  
          * @param characterEncoding
 577  
          *            エンコーディング
 578  
          * @return エンコードされた文字列
 579  
          */
 580  
         private static String encode(final String str,
 581  
                         final String characterEncoding) {
 582  8
                 if (characterEncoding == null) {
 583  0
                         return str;
 584  
                 }
 585  
                 try {
 586  8
                         return URLEncoder.encode(str, characterEncoding);
 587  0
                 } catch (final UnsupportedEncodingException e) {
 588  0
                         throw new IORuntimeException(e);
 589  
                 }
 590  
         }
 591  
 
 592  
         /**
 593  
          * 指定された URL 文字列をデコードします。
 594  
          * 
 595  
          * @param str
 596  
          *            文字列
 597  
          * @param characterEncoding
 598  
          *            エンコーディング
 599  
          * @return デコードされた文字列
 600  
          */
 601  
         private static String decode(final String str,
 602  
                         final String characterEncoding) {
 603  13
                 if (characterEncoding == null) {
 604  1
                         return str;
 605  
                 }
 606  
                 try {
 607  12
                         return URLDecoder.decode(str, characterEncoding);
 608  0
                 } catch (final IOException e) {
 609  0
                         throw new IORuntimeException(e);
 610  
                 }
 611  
         }
 612  
 
 613  
 }