/* * Copyright 2004-2012 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.mayaa.impl.cycle.script.rhino; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Context; import org.mozilla.javascript.JavaAdapter; import org.mozilla.javascript.Scriptable; import org.mozilla.javascript.Undefined; import org.mozilla.javascript.WrapFactory; import org.mozilla.javascript.WrappedException; import org.seasar.mayaa.cycle.ServiceCycle; import org.seasar.mayaa.cycle.scope.AttributeScope; import org.seasar.mayaa.impl.cycle.CycleUtil; import org.seasar.mayaa.impl.util.StringUtil; /** * Rhinoを利用する際に使うさまざまなメソッドを集めたユーティリティクラス。 * * @author Masataka Kurihara (Gluegent, Inc.) * @author Koji Suga (Gluegent Inc.) */ public class RhinoUtil { private static final Log LOG = LogFactory.getLog(RhinoUtil.class); /** 引数無しメソッド取得に使う引数 */ private static final Class[] VOID_ARGS_CLASS = new Class[0]; private RhinoUtil() { // no instantiation. } /** * カレントスレッドに関連付いたContextを取得します。 * 同時にScriptEnvironmentからWrapFactoryを取得してContextにセットします。 * Rhinoスクリプト実行前に、Context.enter()の代わりに利用します。 * これを実行したあとは、try {} finally {} を使って Context.exit() を * 必ず実行する必要があります。 * * @return カレントスレッドに関連付いたContext */ public static Context enter() { Context cx = Context.enter(); WrapFactory factory = ScriptEnvironmentImpl.getWrapFactory(); if (factory != null) { cx.setWrapFactory(factory); } return cx; } /** * 現在位置のスコープを取得します。 * * @return 現在位置のスコープ */ public static Scriptable getScope() { ServiceCycle cycle = CycleUtil.getServiceCycle(); AttributeScope attrs = cycle.getPageScope(); if (attrs instanceof PageAttributeScope) { attrs = (AttributeScope) attrs.getAttribute( PageAttributeScope.KEY_CURRENT); } if (attrs instanceof Scriptable) { return (Scriptable) attrs; } throw new IllegalStateException("script scope does not get"); } /** * Rhinoスクリプトの実行結果をJavaのオブジェクトに変換して返します。 * 特殊な変換をするのは3種類。 * <ul><li>booleanの場合、JavaScriptの仕様で「undefinedでない=true」 * という判定になるのを防ぐ</li> * <li>戻り値の型がvoidの場合、実行結果がundefinedの場合にnullを返す</li> * <li>戻り値の型が数値の場合、元の型を維持するようにする (RhinoはDoubleにしてしまう)</li> * </ul> * * @param cx 現在スレッドのコンテキスト * @param expectedClass 戻り値の型 * @param jsRet Rhinoスクリプト実行結果 * @return jsRetをJavaで利用するために変換したオブジェクト */ public static Object convertResult( Context cx, Class expectedClass, Object jsRet) { Object ret; if (expectedClass.equals(Boolean.TYPE)) { // workaround to ECMA1.3 ret = JavaAdapter.convertResult(jsRet, Object.class); } else if (expectedClass == Void.class || expectedClass == void.class || jsRet == Undefined.instance) { ret = null; } else { if (isNumber(expectedClass, jsRet)) { ret = jsRet; } else { ret = JavaAdapter.convertResult(jsRet, expectedClass); } } return ret; } /** * Rhinoスクリプト実行中に発生した例外を、wrapされていない元の例外にして * 再度throwします。もしwrapされていた例外がRuntimeExceptionでない場合、 * RuntimeExceptionでwrapしてthrowします。 * * @param e Rhinoスクリプト中で発生した例外 */ public static void removeWrappedException(WrappedException e) { Throwable t = e.getWrappedException(); if (t instanceof RuntimeException) { throw (RuntimeException) t; } throw new RuntimeException(t); } /** * Rhinoスクリプトの実行結果が数値の場合、無変換で返すかどうかを判定します。 * 戻り値の型がNumber, Objectの場合、および実行結果の型と同じ場合に無変換で * 返すと判定します。 * * @param expectedClass 戻り値の型 * @param jsRet Rhinoスクリプトの実行結果 * @return 無変換で返せる場合はtrue */ public static boolean isNumber(Class expectedClass, Object jsRet) { if (jsRet != null && expectedClass != null) { Class originalClass = jsRet.getClass(); if (Number.class.isAssignableFrom(originalClass)) { if (Object.class.equals(expectedClass) || Number.class.equals(expectedClass) || originalClass.equals(expectedClass)) { return true; } } } return false; } /** * propertyNameから単純にプロパティを取得します。 * プロパティと見なすのは順にpublicなgetterメソッド、publicなフィールドです。 * 命名規則はJavaBeanの命名規則に準拠します。 * ただし、インデックス付きアクセスはサポートしません。 * * @param bean プロパティを取得する対象のオブジェクト * @param propertyName プロパティ名 * @return プロパティの値 * @throws RuntimeException Reflectionでのアクセス時に発生する * IllegalAccessException, InvocationTargetException, NoSuchMethodException * をcauseとするRuntimeException */ public static Object getSimpleProperty(Object bean, String propertyName) { Throwable noSuchMethod; try { return getWithGetterMethod(bean, propertyName); } catch (NoSuchMethodException e) { // try field noSuchMethod = e; } Object result = getFromPublicField(bean, propertyName); if (result != null) { return result; } throw new RuntimeException(noSuchMethod); } /** * プロパティ名の先頭を大文字に変換します。 * * @param propertyName プロパティ名 * @return 先頭を大文字にした名前 */ protected static String capitalizePropertyName(String propertyName) { if (propertyName.length() == 0) { return propertyName; } char chars[] = propertyName.toCharArray(); chars[0] = Character.toUpperCase(chars[0]); return new String(chars); } /** * getterメソッドを利用して値を取得します。 * JavaBeanの命名規則に準拠します。 * * @param bean 対象のオブジェクト * @param propertyName プロパティ名 * @return プロパティの値。メソッドにアクセスできない場合はnull * @throws NoSuchMethodException getterメソッドが見つからない場合 */ protected static Object getWithGetterMethod(Object bean, String propertyName) throws NoSuchMethodException { Class beanClass = bean.getClass(); String baseName = capitalizePropertyName(propertyName); Method getter = null; try { getter = beanClass.getMethod("get" + baseName, VOID_ARGS_CLASS); } catch (NoSuchMethodException ignore) { // try boolean } if (getter == null) { try { // TODO Methodキャッシュ Method booleanGetter = beanClass.getMethod("is" + baseName, VOID_ARGS_CLASS); if (booleanGetter != null) { Class returnType = booleanGetter.getReturnType(); if (returnType.equals(Boolean.class) || returnType.equals(Boolean.TYPE)) { getter = booleanGetter; } } } catch (NoSuchMethodException ignore) { // throw new exception for "getXxx" } } if (getter == null || Modifier.isPublic(getter.getModifiers()) == false) { throw new NoSuchMethodException( beanClass.toString() + ".get" + baseName + "()"); } if (getter.isAccessible() == false) { getter.setAccessible(true); } try { return getter.invoke(bean, null); } catch (IllegalAccessException e) { LOG.debug(StringUtil.getMessage(RhinoUtil.class, 1, propertyName, beanClass.getName())); } catch (InvocationTargetException e) { LOG.debug(StringUtil.getMessage(RhinoUtil.class, 1, propertyName, beanClass.getName())); } return null; } /** * publicなフィールドからプロパティを取得します。 * * @param bean 対象オブジェクト * @param propertyName プロパティ名 * @return フィールドの値。フィールドがなければnull */ protected static Object getFromPublicField(Object bean, String propertyName) { try { // TODO Fieldキャッシュ Class beanClass = bean.getClass(); Field field = beanClass.getField(propertyName); if (Modifier.isPublic(field.getModifiers())) { if (field.isAccessible() == false) { field.setAccessible(true); } return field.get(bean); } } catch (SecurityException ignore) { // no-op } catch (NoSuchFieldException ignore) { // no-op } catch (IllegalArgumentException ignore) { // no-op } catch (IllegalAccessException eignore) { // no-op } return null; } /** * Rhinoのバージョンを取得する。 * @return Rhinoのバージョン */ public static String getRhinoVersion() { return Context.getCurrentContext().getImplementationVersion(); } /* public static class NativeEmpty extends NativeJavaObject { private static final long serialVersionUID = 7282176381199691056L; public static final NativeEmpty instance = new NativeEmpty(); private NativeEmpty() { // singleton } public String getClassName() { return "undefined"; } public String toString() { return ""; } } */ }