-PluginPreferences

さて、ProjectPreferencesがどうも動きが悪いみたいなのと、設定を\workspace\\.settings\***.prefsというように、プロジェクト内に設定を保存してしまうことから、PluginPreferencesを作りました。これは、\workspace\.metadata\.plugins\\settings\***.prefsに保存するものです。Kijimunaはバージョン毎にまだ設定項目等が代わるのと、ProjectPreferencesではCVSでプロジェクト共有したときに設定も共有してしまうのがうれしくなかったからです。InstancePreferenceと大きな違いはないのですが、プラグイン独自スペースに保存するのでバージョンコンフリクトしたときには、フォルダまるごと削除できるので使い勝手がよいかなと思います。
EclipseのPreferences仕様では、ExtensionPointを用いてプロトコル風に用いることができるように、IScopeContextを実装することになっています。IContextScope#getName()で返す名前がパスの第一ノードとなります。たとえば/plugin/hogehogeや、/instance/gehogehoというようになります。Preferencesはツリー上になってますから、あるPreferencesからparentやchildといったWalkerメソッドによって移動することができるのです。第一ノードごとに永続化方法が変り、以下のノードごとに永続化ファイルが異なるのです。DBに永続化するPreferencesとかも作れますね。一点残念なのは、Preferencesの仕様(OSGi仕様)で、パス+パラメータの仕組みがなかったこと。たとえば、/plugin/dicon?key1=value1とやると設定できちゃったりとかね。ここまでやるならDAPっぽい仕組みにしてもよかったかな〜と。ま、実装上乗せすればすぐ作れる機能ですが。

public class PluginScope implements IScopeContext {
  public static final String SCOPE = "plugin";
  private Plugin plugin;
  private String qualifier;
  public PluginScope(Plugin plugin) {
    super();
    if(plugin == null) {
      throw new IllegalArgumentException();
    }
    this.plugin = plugin;
  }
  public IPath getLocation() {
    IPath path = ResourcesPlugin.getPlugin().getStateLocation();
    String pluginID = plugin.getBundle().getSymbolicName();
    path = path.removeLastSegments(1).append(pluginID);
    return path;
  }
  public String getName() {
    return SCOPE;
  }
  public IEclipsePreferences getNode(String qualifier) {
    if (qualifier == null) {
      throw new IllegalArgumentException();
    }
    if (plugin == null) {
      return null;
    }
    IEclipsePreferences root = Platform.getPreferencesService().getRootNode();
    String pluginID = plugin.getBundle().getSymbolicName();
    return (IEclipsePreferences)root.node(SCOPE).node(pluginID).node(qualifier);
  }
}

Preferencesは、複数ノードがツリー上になっているので赤字のところのようなノードの数を数える処理が必要です。基本的な機能は基底のEclipsePreferencesで実装されているので、オーバーライドするべきJavaDocが書かれているメソッドを実装しなおすだけでOK。ちょっと分かりにくいのは、Preferences自体がPreferencesのファクトリーとなっているところです。privateコンストラクターと、internalCreate()メソッドは、PluginScope#getNode()で利用している、Preferences#node()メソッドより呼び出されます。

public class PluginPreferences extends EclipsePreferences {
  private static Set loadedNodes = new HashSet();
  private String pluginID;
  private String qualifier;
  private IEclipsePreferences loadLevel;
  private int segmentCount;
  private boolean initialized; 
  public PluginPreferences() {
    super(null, null);
  }
  private PluginPreferences(IEclipsePreferences parent, String name) {
    super(parent, name);
    String path = absolutePath();
    segmentCount = getSegmentCount(path);
    if (segmentCount < 2) {
      return;
    }
    pluginID = getSegment(path, 1);
    if (segmentCount > 2) {
      qualifier = getSegment(path, 2);
    }
  }
  protected EclipsePreferences internalCreate(
    IEclipsePreferences nodeParent, String nodeName, Plugin context) {
      return new PluginPreferences(nodeParent, nodeName);
  }
  protected IPath getLocation() {
    if (pluginID == null || qualifier == null) {
      return null;
    }
    IPath path = ResourcesPlugin.getPlugin().getStateLocation();
    path = path.removeLastSegments(1).append(pluginID);
    return computeLocation(path, qualifier);
  }
  protected IEclipsePreferences getLoadLevel() {
    if (loadLevel == null) {
      if (pluginID == null || qualifier == null) {
        return null;
      }
      IEclipsePreferences node = this;
      for (int i = 3; i < segmentCount; i++) {
        node = (IEclipsePreferences) node.parent();
      }
      loadLevel = node;
    }
    return loadLevel;
  }
  protected void loaded() {
    loadedNodes.add(name());
  }
  protected boolean isAlreadyLoaded(IEclipsePreferences node) {
    return loadedNodes.contains(node.name());
  }  
}

Plugin.xmlは。。。

<extension point="org.eclipse.core.runtime.preferences">
  <scope class="org.seasar.kijimuna.core.preferences.PluginPreferences" name="plugin"/>
</extension>