/**************************************************************************************
* Copyright (c) The AspectWerkz Team. All rights reserved. *
* http://aspectwerkz.codehaus.org *
* ---------------------------------------------------------------------------------- *
* The software in this package is published under the terms of the BSD style license *
* a copy of which has been included with this distribution in the license.txt file. *
**************************************************************************************/
package org.codehaus.aspectwerkz.pointcut;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.io.ObjectInputStream;
import org.apache.commons.jexl.JexlHelper;
import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.ExpressionFactory;
import org.apache.commons.jexl.Expression;
import org.codehaus.aspectwerkz.AspectWerkz;
import org.codehaus.aspectwerkz.exception.WrappedRuntimeException;
import org.codehaus.aspectwerkz.regexp.FieldPattern;
import org.codehaus.aspectwerkz.regexp.PointcutPatternTuple;
import org.codehaus.aspectwerkz.metadata.FieldMetaData;
import org.codehaus.aspectwerkz.metadata.ClassMetaData;
import org.codehaus.aspectwerkz.metadata.InterfaceMetaData;
import org.codehaus.aspectwerkz.advice.AdviceIndexTuple;
import org.codehaus.aspectwerkz.definition.PointcutDefinition;
/**
* Implements the pointcut concept for field access.
* Is an abstraction of a well defined point of execution in the program.<br/>
* Could matches one or many points as long as they are well defined.<br/>
* Stores the advices for this specific pointcut.
*
* @author <a href="mailto:jboner@codehaus.org">Jonas Bon�r</a>
*/
public class FieldPointcut implements Pointcut {
/**
* The expression for the pointcut.
*/
protected String m_expression;
/**
* The Jexl expression.
*/
protected transient Expression m_jexlExpr;
/**
* The pointcut definitions referenced in the m_expression.
* Mapped to the name of the pointcut definition.
*/
protected Map m_pointcutDefs = new HashMap();
/**
* The UUID for the AspectWerkz system.
*/
protected String m_uuid;
/**
* Holds the names of the pre advices.
*/
protected String[] m_preNames = new String[0];
/**
* Holds the names of the post advices.
*/
protected String[] m_postNames = new String[0];
/**
* Holds the indexes of the pre advices.
*/
protected int[] m_preIndexes = new int[0];
/**
* Holds the indexes of the post advices.
*/
protected int[] m_postIndexes = new int[0];
/**
* Creates a new field pointcut.
*
* @param pattern the pattern of the pointcut
*/
public FieldPointcut(final String pattern) {
this(AspectWerkz.DEFAULT_SYSTEM, pattern);
}
/**
* Creates a new field pointcut.
*
* @param uuid the UUID for the AspectWerkz system
* @param pattern the pattern for the pointcut
*/
public FieldPointcut(final String uuid,
final String pattern) {
if (uuid == null) throw new IllegalArgumentException("uuid can not be null");
if (pattern == null || pattern.trim().length() == 0) throw new IllegalArgumentException("pattern of pointcut can not be null or an empty string");
m_uuid = uuid;
m_expression = pattern;
try {
m_jexlExpr = ExpressionFactory.createExpression(m_expression);
}
catch (Exception e) {
throw new RuntimeException("could not create jexl expression from: " + m_expression);
}
}
/**
* Adds a new pointcut definition.
*
* @param pointcut the pointcut definition
*/
public void addPointcutDef(final PointcutDefinition pointcut) {
m_pointcutDefs.put(pointcut.getName(),
new PointcutPatternTuple(
pointcut.getRegexpClassPattern(),
pointcut.getRegexpPattern(),
pointcut.isHierarchical()));
}
/**
* Adds a pre advice to the pointcut.
*
* @param advice the name of the advice to add
*/
public void addPreAdvice(final String advice) {
if (advice == null || advice.trim().length() == 0) throw new IllegalArgumentException("name of advice to add can not be null or an empty string");
synchronized (m_preNames) {
synchronized (m_preIndexes) {
final String[] tmp = new String[m_preNames.length + 1];
System.arraycopy(m_preNames, 0, tmp, 0, m_preNames.length);
tmp[m_preNames.length] = advice;
m_preNames = new String[m_preNames.length + 1];
System.arraycopy(tmp, 0, m_preNames, 0, tmp.length);
m_preIndexes = new int[m_preNames.length];
for (int i = 0, j = m_preNames.length; i < j; i++) {
m_preIndexes[i] = AspectWerkz.getSystem(m_uuid).
getAdviceIndexFor(m_preNames[i]);
}
}
}
}
/**
* Adds post advice to the pointcut.
*
* @param advice the name of the advice to add
*/
public void addPostAdvice(final String advice) {
if (advice == null || advice.trim().length() == 0) throw new IllegalArgumentException("name of advice to add can not be null or an empty string");
synchronized (m_postNames) {
synchronized (m_postIndexes) {
final String[] tmp = new String[m_postNames.length + 1];
System.arraycopy(m_postNames, 0, tmp, 0, m_postNames.length);
tmp[m_postNames.length] = advice;
m_postNames = new String[m_postNames.length + 1];
System.arraycopy(tmp, 0, m_postNames, 0, tmp.length);
m_postIndexes = new int[m_postNames.length];
for (int i = 0, j = m_postNames.length; i < j; i++) {
m_postIndexes[i] = AspectWerkz.getSystem(m_uuid).
getAdviceIndexFor(m_postNames[i]);
}
}
}
}
/**
* Adds pre advices to the pointcut.
*
* @param advicesToAdd the advices to add
*/
public void addPreAdvices(final String[] advicesToAdd) {
for (int i = 0; i < advicesToAdd.length; i++) {
if (advicesToAdd[i] == null || advicesToAdd[i].trim().length() == 0) throw new IllegalArgumentException("name of advice to add can not be null or an empty string");
}
synchronized (m_preNames) {
synchronized (m_preIndexes) {
final String[] clone = new String[advicesToAdd.length];
System.arraycopy(advicesToAdd, 0, clone, 0, advicesToAdd.length);
final String[] tmp = new String[
m_preNames.length + advicesToAdd.length];
System.arraycopy(m_preNames, 0, tmp, 0, m_preNames.length);
System.arraycopy(clone, 0, tmp, m_preNames.length, tmp.length);
m_preNames = new String[tmp.length];
System.arraycopy(tmp, 0, m_preNames, 0, tmp.length);
m_preIndexes = new int[m_preNames.length];
for (int j = 0; j < m_preNames.length; j++) {
m_preIndexes[j] = AspectWerkz.getSystem(m_uuid).
getAdviceIndexFor(m_preNames[j]);
}
}
}
}
/**
* Adds post advices to the pointcut.
*
* @param advicesToAdd the advices to add
*/
public void addPostAdvices(final String[] advicesToAdd) {
for (int i = 0; i < advicesToAdd.length; i++) {
if (advicesToAdd[i] == null || advicesToAdd[i].trim().length() == 0) throw new IllegalArgumentException("name of advice to add can not be null or an empty string");
}
synchronized (m_postNames) {
synchronized (m_postIndexes) {
final String[] clone = new String[advicesToAdd.length];
System.arraycopy(advicesToAdd, 0, clone, 0, advicesToAdd.length);
final String[] tmp = new String[m_postNames.length + advicesToAdd.length];
System.arraycopy(m_postNames, 0, tmp, 0, m_postNames.length);
System.arraycopy(clone, 0, tmp, m_postNames.length, tmp.length);
m_postNames = new String[tmp.length];
System.arraycopy(tmp, 0, m_postNames, 0, tmp.length);
m_postIndexes = new int[m_postNames.length];
for (int j = 0; j < m_postNames.length; j++) {
m_postIndexes[j] = AspectWerkz.getSystem(m_uuid).
getAdviceIndexFor(m_postNames[j]);
}
}
}
}
/**
* Removes a pre advice from the pointcut.
*
* @param advice the name of the pre advice to remove
*/
public void removePreAdvice(final String advice) {
if (advice == null || advice.trim().length() == 0) throw new IllegalArgumentException("name of advice to remove can not be null or an empty string");
synchronized (m_preNames) {
synchronized (m_preIndexes) {
int index = -1;
for (int i = 0; i < m_preNames.length; i++) {
if (m_preNames[i].equals(advice)) {
index = i;
break;
}
}
if (index == -1) throw new RuntimeException("can not remove pre advice with the name " + advice + ": no such advice");
final String[] names = new String[m_preNames.length - 1];
int j, k;
for (j = 0, k = 0; j < index; j++, k++) {
names[j] = m_preNames[j];
}
j++;
for (; j < m_preNames.length; j++, k++) {
names[k] = m_preNames[j];
}
m_preNames = new String[names.length];
System.arraycopy(names, 0, m_preNames, 0, names.length);
final int[] indexes = new int[m_preIndexes.length - 1];
for (j = 0, k = 0; j < index; j++, k++) {
indexes[j] = m_preIndexes[j];
}
j++;
for (; j < m_preIndexes.length; j++, k++) {
indexes[k] = m_preIndexes[j];
}
m_preIndexes = new int[indexes.length];
System.arraycopy(indexes, 0, m_preIndexes, 0, indexes.length);
}
}
}
/**
* Removes a post advice from the pointcut.
*
* @param advice the name of the pre advice to remove
*/
public void removePostAdvice(final String advice) {
if (advice == null || advice.trim().length() == 0) throw new IllegalArgumentException("name of advice to remove can not be null or an empty string");
synchronized (m_postNames) {
synchronized (m_postIndexes) {
int index = -1;
for (int i = 0; i < m_postNames.length; i++) {
if (m_postNames[i].equals(advice)) {
index = i;
break;
}
}
if (index == -1) throw new RuntimeException("can not remove post advice with the name " + advice + ": no such advice");
final String[] names = new String[m_postNames.length - 1];
int j, k;
for (j = 0, k = 0; j < index; j++, k++) {
names[j] = m_postNames[j];
}
j++;
for (; j < m_postNames.length; j++, k++) {
names[k] = m_postNames[j];
}
m_postNames = new String[names.length];
System.arraycopy(names, 0, m_postNames, 0, names.length);
final int[] indexes = new int[m_postIndexes.length - 1];
for (j = 0, k = 0; j < index; j++, k++) {
indexes[j] = m_postIndexes[j];
}
j++;
for (; j < m_postIndexes.length; j++, k++) {
indexes[k] = m_postIndexes[j];
}
m_postIndexes = new int[indexes.length];
System.arraycopy(indexes, 0, m_postIndexes, 0, indexes.length);
}
}
}
/**
* Checks if the pointcuts has a certain pre advice.
*
* @param advice the advice to check for existence
* @return boolean
*/
public boolean hasPreAdvice(final String advice) {
for (int i = 0; i < m_preNames.length; i++) {
if (m_preNames[i].equals(advice)) {
return true;
}
}
return false;
}
/**
* Checks if the pointcuts has a certain post advice.
*
* @param advice the advice to check for existence
* @return boolean
*/
public boolean hasPostAdvice(final String advice) {
for (int i = 0; i < m_postNames.length; i++) {
if (m_postNames[i].equals(advice)) {
return true;
}
}
return false;
}
/**
* Returns the advices in the form of an array with advice/index tuples.
* To be used when a reordering of the advices is necessary.
*
* @return the current advice/index tuple array
*/
public AdviceIndexTuple[] getPreAdviceIndexTuples() {
synchronized (m_preIndexes) {
synchronized (m_preNames) {
final AdviceIndexTuple[] tuples = new AdviceIndexTuple[m_preNames.length];
for (int i = 0; i < m_preNames.length; i++) {
tuples[i] = new AdviceIndexTuple(m_preNames[i], m_preIndexes[i]);
}
return tuples;
}
}
}
/**
* Sets the advices. To be used when a reordering of the advices is necessary.
*
* @param tuple the new advice/index tuple array
*/
public void setPreAdviceIndexTuples(final AdviceIndexTuple[] tuple) {
synchronized (m_preIndexes) {
synchronized (m_preNames) {
m_preNames = new String[tuple.length];
m_preIndexes = new int[tuple.length];
for (int i = 0; i < tuple.length; i++) {
m_preNames[i] = tuple[i].getName();
m_preIndexes[i] = tuple[i].getIndex();
}
}
}
}
/**
* Returns the advices in the form of an array with advice/index tuples.
* To be used when a reordering of the advices is necessary.
*
* @return the current advice/index tuple array
*/
public AdviceIndexTuple[] getPostAdviceIndexTuples() {
synchronized (m_postIndexes) {
synchronized (m_postNames) {
final AdviceIndexTuple[] tuples = new AdviceIndexTuple[m_postNames.length];
for (int i = 0; i < m_postNames.length; i++) {
tuples[i] = new AdviceIndexTuple(m_postNames[i], m_postIndexes[i]);
}
return tuples;
}
}
}
/**
* Sets the advices. To be used when a reordering of the advices is necessary.
*
* @param tuple the new advice/index tuple array
*/
public void setPostAdviceIndexTuples(final AdviceIndexTuple[] tuple) {
synchronized (m_postIndexes) {
synchronized (m_postNames) {
m_postNames = new String[tuple.length];
m_postIndexes = new int[tuple.length];
for (int i = 0; i < tuple.length; i++) {
m_postNames[i] = tuple[i].getName();
m_postIndexes[i] = tuple[i].getIndex();
}
}
}
}
/**
* Returns a list with the indexes for the pre advices for the pointcut.
*
* @return the pre advice indexes
*/
public int[] getPreAdviceIndexes() {
return m_preIndexes;
}
/**
* Returns a list with the names for the pre advices for the pointcut.
*
* @return the pre advice names
*/
public String[] getPreAdviceNames() {
return m_preNames;
}
/**
* Returns a list with the indexes for the post advices for the pointcut.
*
* @return the pre advice indexes
*/
public int[] getPostAdviceIndexes() {
return m_postIndexes;
}
/**
* Returns a list with the names for the post advices for the pointcut.
*
* @return the post advice names
*/
public String[] getPostAdviceNames() {
return m_postNames;
}
/**
* Returns the expression of the pointcut.
*
* @return the expression
*/
public String getExpression() {
return m_expression;
}
/**
* Sets the pre advices.
* Caution: the index A name arrays have to be in synch.
*
* @param indexes the new pre advice index array
* @param names the new pre advice names array
*/
public void setPreAdvices(final int[] indexes, final String[] names) {
synchronized (m_preIndexes) {
synchronized (m_preNames) {
m_preIndexes = indexes;
m_preNames = names;
}
}
}
/**
* Sets the post advices.
* Caution: the index A name arrays have to be in synch.
*
* @param indexes the new post advice index array
* @param names the new post advice names array
*/
public void setPostAdvices(final int[] indexes, final String[] names) {
synchronized (m_postIndexes) {
synchronized (m_postNames) {
m_postIndexes = indexes;
m_postNames = names;
}
}
}
/**
* Checks if the pointcut matches a certain join point.
*
* @param classMetaData the class meta-data
* @param fieldMetaData the meta-data for the field
* @return boolean
*/
public boolean matches(final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData) {
JexlContext jexlContext = JexlHelper.createContext();
try {
matchPointcutPatterns(jexlContext, classMetaData, fieldMetaData);
// evaluate expression
Boolean result = (Boolean)m_jexlExpr.evaluate(jexlContext);
if (result == null || !result.booleanValue()) {
return false;
}
else {
return true;
}
}
catch (Exception e) {
throw new WrappedRuntimeException(e);
}
}
/**
* Tries to finds a match at some superclass in the hierarchy.
* Only checks for a class match to allow early filtering.
* Recursive.
*
* @param jexlContext the Jexl context
* @param name the name of the pointcut to evaluate
* @param classMetaData the class meta-data
* @param pointcutPattern the pointcut pattern
* @return boolean
*/
public static boolean matchFieldPointcutSuperClasses(final JexlContext jexlContext,
final String name,
final ClassMetaData classMetaData,
final PointcutPatternTuple pointcutPattern) {
if (classMetaData == null) {
return false;
}
// match the class/super class
if (pointcutPattern.getClassPattern().matches(classMetaData.getName())) {
jexlContext.getVars().put(name, Boolean.TRUE);
return true;
}
else {
// match the interfaces for the class
if (matchFieldPointcutInterfaces(
jexlContext, name, classMetaData.getInterfaces(),
classMetaData, pointcutPattern)) {
return true;
}
// no match; get the next superclass
return matchFieldPointcutSuperClasses(
jexlContext, name, classMetaData.getSuperClass(), pointcutPattern);
}
}
/**
* Tries to finds a match at some superclass in the hierarchy.
* Recursive.
*
* @param jexlContext the Jexl context
* @param name the name of the pointcut to evaluate
* @param classMetaData the class meta-data
* @param fieldMetaData the field meta-data
* @param pointcutPattern the pointcut pattern
* @return boolean
*/
public static boolean matchFieldPointcutSuperClasses(final JexlContext jexlContext,
final String name,
final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData,
final PointcutPatternTuple pointcutPattern) {
if (classMetaData == null) {
return false;
}
// match the class/super class
if (pointcutPattern.getClassPattern().matches(classMetaData.getName()) &&
((FieldPattern)pointcutPattern.getPattern()).matches(fieldMetaData)) {
jexlContext.getVars().put(name, Boolean.TRUE);
return true;
}
else {
// match the interfaces for the class
if (matchFieldPointcutInterfaces(
jexlContext, name, classMetaData.getInterfaces(),
classMetaData, fieldMetaData, pointcutPattern)) {
return true;
}
// no match; get the next superclass
return matchFieldPointcutSuperClasses(
jexlContext, name, classMetaData.getSuperClass(),
fieldMetaData, pointcutPattern);
}
}
/**
* Tries to finds a match at some interface in the hierarchy.
* Only checks for a class match to allow early filtering.
* Recursive.
*
* @param jexlContext the Jexl context
* @param name the name of the pointcut to evaluate
* @param interfaces the interfaces
* @param classMetaData the class meta-data
* @param pointcutPattern the pointcut pattern
* @return boolean
*/
private static boolean matchFieldPointcutInterfaces(final JexlContext jexlContext,
final String name,
final List interfaces,
final ClassMetaData classMetaData,
final PointcutPatternTuple pointcutPattern) {
if (interfaces.isEmpty()) {
return false;
}
for (Iterator it = interfaces.iterator(); it.hasNext();) {
InterfaceMetaData interfaceMD = (InterfaceMetaData)it.next();
if (pointcutPattern.getClassPattern().matches(interfaceMD.getName())) {
jexlContext.getVars().put(name, Boolean.TRUE);
return true;
}
else {
if (matchFieldPointcutInterfaces(
jexlContext, name, interfaceMD.getInterfaces(),
classMetaData, pointcutPattern)) {
return true;
}
else {
continue;
}
}
}
return false;
}
/**
* Tries to finds a match at some interface in the hierarchy.
* Recursive.
*
* @param jexlContext the Jexl context
* @param name the name of the pointcut to evaluate
* @param interfaces the interfaces
* @param classMetaData the class meta-data
* @param fieldMetaData the field meta-data
* @param pointcutPattern the pointcut pattern
* @return boolean
*/
private static boolean matchFieldPointcutInterfaces(final JexlContext jexlContext,
final String name,
final List interfaces,
final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData,
final PointcutPatternTuple pointcutPattern) {
if (interfaces.isEmpty()) {
return false;
}
for (Iterator it = interfaces.iterator(); it.hasNext();) {
InterfaceMetaData interfaceMD = (InterfaceMetaData)it.next();
if (pointcutPattern.getClassPattern().matches(interfaceMD.getName()) &&
((FieldPattern)pointcutPattern.getPattern()).matches(fieldMetaData)) {
jexlContext.getVars().put(name, Boolean.TRUE);
return true;
}
else {
if (matchFieldPointcutInterfaces(
jexlContext, name, interfaceMD.getInterfaces(),
classMetaData, fieldMetaData, pointcutPattern)) {
return true;
}
else {
continue;
}
}
}
return false;
}
/**
* Matches the field pointcut patterns.
*
* @param jexlContext the Jexl context
* @param classMetaData the class meta-data
* @param fieldMetaData the field meta-data
*/
private void matchPointcutPatterns(final JexlContext jexlContext,
final ClassMetaData classMetaData,
final FieldMetaData fieldMetaData) {
for (Iterator it = m_pointcutDefs.entrySet().iterator(); it.hasNext();) {
Map.Entry entry = (Map.Entry)it.next();
String name = (String)entry.getKey();
PointcutPatternTuple pointcutPattern = (PointcutPatternTuple)entry.getValue();
// try to find a match somewhere in the class hierarchy (interface or super class)
if (pointcutPattern.isHierarchical()) {
matchFieldPointcutSuperClasses(
jexlContext, name, classMetaData, fieldMetaData, pointcutPattern);
}
// match the class only
else if (pointcutPattern.getClassPattern().matches(classMetaData.getName()) &&
((FieldPattern)pointcutPattern.getPattern()).matches(fieldMetaData)) {
jexlContext.getVars().put(name, Boolean.TRUE);
}
else {
jexlContext.getVars().put(name, Boolean.FALSE);
}
}
}
/**
* Provides custom deserialization.
*
* @param stream the object input stream containing the serialized object
* @throws java.lang.Exception in case of failure
*/
private void readObject(final ObjectInputStream stream) throws Exception {
ObjectInputStream.GetField fields = stream.readFields();
m_expression = (String)fields.get("m_expression", null);
m_pointcutDefs = (Map)fields.get("m_pointcutDefs", null);
m_preNames = (String[])fields.get("m_preNames", null);
m_postNames = (String[])fields.get("m_postNames", null);
m_preIndexes = (int[])fields.get("m_preIndexes", null);
m_postIndexes = (int[])fields.get("m_postIndexes", null);
m_uuid = (String)fields.get("m_uuid", null);
try {
m_jexlExpr = ExpressionFactory.createExpression(m_expression);
}
catch (Exception e) {
throw new RuntimeException("could not create jexl expression from: " + m_expression);
}
}
}