FriendInterceptor

パッケージ構成の依存関係はある程度 A→B→C
と一方方向となりCからAは参照しちゃいかんみたいになることが多いと思う。
こういうことは規約で明記されるが、いかんせんpublicにしたらどこからでも呼べちゃうので守ってもらえないことも多い。

そこで考えたのがC++のfriendをInterceptorで実装すればいいじゃんというもの。
開発時に設定しておいてリリース時にAOPだから簡単に除くことが可能。
たしか1年ぐらい前にアイディアはしゃべって実装したのだが公開してなかった。
なんで公開してなかったのかは実装バグなのか、仕様バグなのかちょっと記憶が定かでないがなんかだめな部分があったのだと思う。
作業ログをみると、JDK1.5じゃないとうごかないとかAntのようにincludes,excludesが指定できないと意味がないだろとかかいてあるが。。。

package org.seasar.framework.aop.interceptors;

import java.util.ArrayList;
import java.util.Iterator;

import org.aopalliance.intercept.MethodInvocation;

/**
 * Friend Interceptor(like C++)
 * 
 * @author udagawa
 */
public class FriendInterceptor extends AbstractInterceptor {

  protected ArrayList friendPattern_ = new ArrayList();

  public FriendInterceptor() {
  }

  public Object invoke(MethodInvocation invocation) throws Throwable {
    if (friendPattern_.size() == 0) {
      return invocation.proceed();
    }

    friendPattern_.add(getTargetClass(invocation).getName());
    String invocationMethodName = invocation.getClass().getName();

    Exception e = new Exception();
    StackTraceElement[] stackTrace = e.getStackTrace();
    boolean patternMatch = false;
    String caller = "";

    for (int i = 0; i < stackTrace.length; i++) {
      if (invocationMethodName.indexOf(stackTrace[i].getClassName()) != -1) {
        caller = stackTrace[i + 2].getClassName();
        break;
      }
    }
    for (Iterator iter = friendPattern_.iterator(); iter.hasNext();) {
      String pattern = (String) iter.next();
      if (caller.matches(pattern)) {
        patternMatch = true;
        break;
      }
    }

    if (patternMatch) {
      return invocation.proceed();
    } else {
      throw new IllegalAccessException(caller + " should not call "
          + invocation.getStaticPart());
    }
  }

  public void addFriendPattern(String pattern) {
    friendPattern_.add(pattern.replaceAll("/", "\\."));
  }

}
package examples.aop.friendinterceptor;

public interface Hello {
  public void hello();
  public void hello(Hello hello);
}
package examples.aop.friendinterceptor;

public class HelloA implements Hello {

  public void hello() {
    System.out.println("I am A");
  }

  public void hello(Hello hello) {
    hello.hello();
  }
}

package examples.aop.friendinterceptor;

public class HelloB implements Hello {
	public void hello() {
		System.out.println("I am B");
	}

	public void hello(Hello hello) {
		hello.hello();
	}
	
	public void myHello() {
		hello();
	}
}

D1,D2,D3はHelloAと同じ。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN"    
"http://www.seasar.org/dtd/components23.dtd">
<components>
  <component name="friendInterceptor"
    class="org.seasar.framework.aop.interceptors.FriendInterceptor">
    <initMethod>#self.addFriendPattern(".*Test$")</initMethod>
  </component>
    
  <component name="nonfriendInterceptor"
    class="org.seasar.framework.aop.interceptors.FriendInterceptor">
    <initMethod>#self.addFriendPattern("nofreind")</initMethod>
  </component>

  <component class="examples.aop.friendinterceptor.HelloA">
    <aspect>friendInterceptor</aspect>
  </component>
  
  <component class="examples.aop.friendinterceptor.HelloB">
    <aspect>nonfriendInterceptor</aspect>
  </component>


  <component name="d1"
    class="org.seasar.framework.aop.interceptors.FriendInterceptor">
    <initMethod>#self.addFriendPattern(".*Test$")</initMethod>
  </component>

  <component name="d2"
    class="org.seasar.framework.aop.interceptors.FriendInterceptor">
    <initMethod>#self.addFriendPattern(".*Test$")</initMethod>
    <initMethod>#self.addFriendPattern("examples/aop/friendinterceptor/d1/.*")</initMethod>
  </component>

  <component name="d3"
    class="org.seasar.framework.aop.interceptors.FriendInterceptor">
    <initMethod>#self.addFriendPattern(".*Test$")</initMethod>
    <initMethod>#self.addFriendPattern("examples/aop/friendinterceptor/d1/d2/.*")</initMethod>
  </component>


  <component class="examples.aop.friendinterceptor.d1.D1">
    <aspect>d1</aspect>
  </component>
  <component class="examples.aop.friendinterceptor.d1.d2.D2">
    <aspect>d2</aspect>
  </component>
  <component class="examples.aop.friendinterceptor.d1.d2.d3.D3">
    <aspect>d3</aspect>
  </component>
</components>
package examples.aop.friendinterceptor;

import org.seasar.extension.unit.S2TestCase;

import examples.aop.friendinterceptor.d1.D1;
import examples.aop.friendinterceptor.d1.d2.D2;
import examples.aop.friendinterceptor.d1.d2.d3.D3;

public class FriendInterceptorTest extends S2TestCase {
  private final String PATH = "friend.dicon";

  HelloA _helloA;
  HelloB _helloB;
  D1 _d1;
  D2 _d2;
  D3 _d3;

  public void testA() throws Exception {
    _helloA.hello();
  }
  
  public void testB() throws Exception {
    try {
      _helloB.hello();
      fail();
    } catch (Throwable e) {
      ;
    }
    _helloB.myHello();
  }
  
  public void testD1() throws Exception {
    _d1.hello();
    try {
      _d1.hello(_d2);
      fail();
    } catch (Throwable e) {
      ;
    }

    try {
      _d1.hello(_d3);
      fail();
    } catch (Throwable e) {
      ;
    }
  }

  public void testD2() throws Exception {
    _d2.hello();

    try {
      _d2.hello(_d1);
      fail();
    } catch (Throwable e) {
      ;
    }

    _d2.hello(_d3);
  }

  public void testD3() throws Exception {
    _d3.hello();
    _d3.hello(_d2);
    
    try {
      _d3.hello(_d1);
      fail();
    } catch (Throwable e) {
      ;
    }
  }

  protected void setUp() throws Exception {
    include(PATH);
  }
}

一応Seasar2.3.7でテストは正常にうごいてはいました。
# org.seasar.framework.aop.interceptors になっているのは参考にしたinterceptorのコピーペーのあとですね。。。