/*
* 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.direct;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mozilla.javascript.Context;
import org.mozilla.javascript.NativeObject;
import org.mozilla.javascript.Scriptable;
import org.mozilla.javascript.Undefined;
import org.seasar.mayaa.PositionAware;
import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.AttributeScope;
import org.seasar.mayaa.engine.specification.PrefixAwareName;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.impl.CONST_IMPL;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.cycle.script.AbstractTextCompiledScript;
import org.seasar.mayaa.impl.cycle.script.ReadOnlyScriptBlockException;
import org.seasar.mayaa.impl.cycle.script.rhino.OffsetLineRhinoException;
import org.seasar.mayaa.impl.cycle.script.rhino.RhinoUtil;
import org.seasar.mayaa.impl.engine.specification.PrefixAwareNameImpl;
import org.seasar.mayaa.impl.util.ObjectUtil;
import org.seasar.mayaa.impl.util.StringUtil;
/**
* スコープから変数を取得するだけの処理をするスクリプトの抽象クラス。
*
* @author Koji Suga (Gluegent Inc.)
*/
public abstract class AbstractGetterScript extends AbstractTextCompiledScript {
private static final long serialVersionUID = 1L;
private static final Log LOG = LogFactory.getLog(AbstractGetterScript.class);
private static final String[] SERVICE_CYCLE_PROPERTY_NAMES =
ObjectUtil.getPropertyNames(ServiceCycle.class);
protected final String _sourceName;
protected final int _lineNumber;
protected final int _offsetLine;
protected final String _scopeName;
protected final String _attributeName;
protected final String _propertyName;
protected final String _beanPropertyName;
public AbstractGetterScript(
String text, PositionAware position, int offsetLine, String scopeName,
String attributeName, String propertyName, String[] beanPropertyNames) {
super(text);
String sourceName = position.getSystemID();
if (position instanceof PrefixAwareName) {
PrefixAwareName prefixAwareName = (PrefixAwareName) position;
QName qName = prefixAwareName.getQName();
if (CONST_IMPL.URI_MAYAA == qName.getNamespaceURI()) {
sourceName += "#" + qName.getLocalName();
} else {
sourceName += "#"
+ PrefixAwareNameImpl.forPrefixAwareNameString(qName,
prefixAwareName.getPrefix());
}
}
_sourceName = sourceName;
_lineNumber = position.getLineNumber();
_offsetLine = offsetLine;
_scopeName = scopeName;
_attributeName = attributeName;
_propertyName = propertyName;
_beanPropertyName = (contains(beanPropertyNames, attributeName)) ?
attributeName : null;
}
private boolean contains(String[] array, String test) {
if (array != null) {
for (int i = 0; i < array.length; i++) {
if (array[i].equals(test)) {
return true;
}
}
}
return false;
}
/**
* スクリプトを実行した結果を取得します。
* このメソッドではスクリプトをエミュレートし、スコープからの属性取得および
* スコープのプロパティを取得して返します。
*
* @param args 引数。使用しない。
* @return 変数の値を返します。見つからない場合はnullを返します。
* @throws OffsetLineRhinoException 属性がnullで、プロパティを取得しようとしたとき。
*/
public Object execute(Object[] args) {
Context cx = RhinoUtil.enter();
try {
Object result = getAttribute();
if (_propertyName != null) {
if (result == null) {
String script = getText();
String message = StringUtil.getMessage(
AbstractGetterScript.class, 1, _propertyName, script);
int[] position = extractLineSourcePosition(script, _propertyName);
String lineSource = script.substring(position[0], position[1]);
throw new OffsetLineRhinoException(
message, _sourceName, _lineNumber, lineSource,
lineSource.indexOf(_propertyName), _offsetLine, null);
}
result = readProperty(result);
}
return RhinoUtil.convertResult(cx, getExpectedClass(), result);
} finally {
Context.exit();
}
}
/**
* attributeからpropertyを読み出して返します。
*
* @param attribute propertyを読み出す属性
* @return propertyの値、またはnull
*/
private Object readProperty(Object attribute) {
Object property = Undefined.instance;
if (attribute instanceof NativeObject) {
// Rhinoで作成したオブジェクトの場合
NativeObject nativeObject = (NativeObject) attribute;
Object nativeProperty = nativeObject.get(_propertyName, nativeObject);
if (nativeProperty != Scriptable.NOT_FOUND) {
property = nativeProperty;
}
} else if (attribute instanceof AttributeScope) {
// TODO __current__ とか __parent__ とかの場合
// スコープの場合
AttributeScope scope = (AttributeScope) attribute;
property = scope.getAttribute(_propertyName);
} else if (attribute instanceof Map) {
property = ((Map) attribute).get(_propertyName);
} else {
// それ以外はBeanとしてのアクセス
try {
property = RhinoUtil.getSimpleProperty(attribute, _propertyName);
} catch (RuntimeException ignore) {
// undefined
LOG.debug(StringUtil.getMessage(AbstractGetterScript.class, 2,
_propertyName, attribute.getClass().getName()));
}
}
return property;
}
protected static int[] extractLineSourcePosition(String text, String propertyName) {
if (text.indexOf('\n') == -1) {
return new int[] { 0, text.length() };
}
int index = text.lastIndexOf(propertyName);
int head = text.lastIndexOf('\n', index);
int tail = text.indexOf('\n', index + propertyName.length());
if (tail == -1) {
tail = text.length();
}
return new int[] { head + 1, tail };
}
public String getAttributeName(int index) {
return _attributeName;
}
public String getAttributeName() {
return _attributeName;
}
public String getPropertyName() {
return _propertyName;
}
/**
* {@link #execute(Object[])}で呼び出され、スコープから属性値を取得します。
* 指定された属性名がスコープのプロパティを指している場合はプロパティの値を
* 返します。
*
* @return 属性値
*/
protected Object getAttribute() {
AttributeScope scope = getScope();
if (scope == null) {
if (_scopeName == null) {
for (int i = 0; i < SERVICE_CYCLE_PROPERTY_NAMES.length; i++) {
if (SERVICE_CYCLE_PROPERTY_NAMES[i].equals(_attributeName)) {
ServiceCycle cycle = CycleUtil.getServiceCycle();
return ObjectUtil.getProperty(cycle, _attributeName);
}
}
}
return null;
}
Object result = scope.getAttribute(_attributeName);
if (result == null && _beanPropertyName != null) {
result = ObjectUtil.getProperty(scope, _beanPropertyName);
}
return result;
}
/**
* {@link #execute(Object[])}で呼び出されます。
* GetterScriptの実装はこのメソッドでスコープを返してください。
*
* @return スコープ
*/
protected abstract AttributeScope getScope();
/**
* 読み込み専用。
*
* @return true
*/
public boolean isReadOnly() {
return true;
}
/**
* サポートしない。
*
* @param value 値
* @throws ReadOnlyScriptBlockException
*/
public void assignValue(Object value) {
throw new ReadOnlyScriptBlockException(getScriptText());
}
}