You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2013/11/15 22:08:05 UTC

svn commit: r1542401 - in /subversion/trunk: ./ subversion/bindings/javahl/native/ subversion/bindings/javahl/native/jniwrapper/ subversion/bindings/javahl/src/org/apache/subversion/javahl/ subversion/bindings/javahl/src/org/apache/subversion/javahl/ut...

Author: brane
Date: Fri Nov 15 21:08:05 2013
New Revision: 1542401

URL: http://svn.apache.org/r1542401
Log:
Add utilities for file content translation to JavaHL.

* build.conf (private-built-includes):
   Added org_apache_subversion_javahl_util_SubstLib.h.

[in subversion/bindings/javahl]
* src/org/apache/subversion/javahl/SVNUtil.java
  (SVNUtil.substLib,
   SVNUtil.EOL_LF SVNUtil.EOL_CR SVNUtil.EOL_CRLF,
   SVNUtil.buildKeywords, SVNUtil.translateStream): New.
* src/org/apache/subversion/javahl/util/SubstLib.java: New class.

* native/jniwrapper/jni_string_map.hpp,
  native/jniwrapper/jni_string_map.cpp: New; JNI wrapper for
   the java.util.Map class with String keys.
* org_apache_subversion_javahl_util_SubstLib.cpp: New;
   Implementation of native methods in class SubstLib.

* tests/org/apache/subversion/javahl/UtilTests.java
   (UtilTests.testBuildKeywords): New testcase.

Added:
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp   (with props)
    subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp   (with props)
    subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp   (with props)
    subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java   (with props)
Modified:
    subversion/trunk/build.conf
    subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java
    subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/UtilTests.java

Modified: subversion/trunk/build.conf
URL: http://svn.apache.org/viewvc/subversion/trunk/build.conf?rev=1542401&r1=1542400&r2=1542401&view=diff
==============================================================================
--- subversion/trunk/build.conf (original)
+++ subversion/trunk/build.conf Fri Nov 15 21:08:05 2013
@@ -82,6 +82,7 @@ private-built-includes =
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_ConfigLib.h
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_DiffLib.h
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_PropLib.h
+        subversion/bindings/javahl/include/org_apache_subversion_javahl_util_SubstLib.h
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_TunnelChannel.h
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_RequestChannel.h
         subversion/bindings/javahl/include/org_apache_subversion_javahl_util_ResponseChannel.h

Added: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp?rev=1542401&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp (added)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp Fri Nov 15 21:08:05 2013
@@ -0,0 +1,145 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#include <stdexcept>
+
+#include "jni_string.hpp"
+#include "jni_string_map.hpp"
+
+namespace Java {
+
+// Class Java::BaseMap
+
+const char* const BaseMap::m_class_name = "java/util/Map";
+
+jobject BaseMap::operator[](const std::string& index) const
+{
+  somap::const_iterator it = m_contents.find(index);
+  if (it == m_contents.end())
+    {
+      std::string msg(_("Map does not contain key: "));
+      msg += index;
+      throw std::out_of_range(msg.c_str());
+    }
+  return it->second;
+}
+
+BaseMap::somap BaseMap::convert_to_map(Env env, jclass cls, jobject jmap)
+{
+  if (!env.CallIntMethod(jmap, env.GetMethodID(cls, "size", "()I")))
+    return somap();
+
+  // Get an iterator over the map's entry set
+  const jobject entries = env.CallObjectMethod(
+      jmap, env.GetMethodID(cls, "entrySet", "()Ljava/util/Set;"));
+  const jobject iterator = env.CallObjectMethod(
+      entries, env.GetMethodID(env.GetObjectClass(entries), "iterator",
+                               "()Ljava/util/Iterator;"));
+  const jclass cls_iterator = env.GetObjectClass(iterator);
+  const jmethodID mid_it_has_next = env.GetMethodID(cls_iterator,
+                                                    "hasNext", "()Z");
+  const jmethodID mid_it_next = env.GetMethodID(cls_iterator, "next",
+                                                "()Ljava/lang/Object;");
+
+  // Find the methods for retreiving the key and value from an entry
+  const jclass cls_entry = env.FindClass("java/util/Map$Entry");
+  const jmethodID mid_get_key = env.GetMethodID(cls_entry, "getKey",
+                                                "()Ljava/lang/Object;");
+  const jmethodID mid_get_value = env.GetMethodID(cls_entry, "getValue",
+                                                  "()Ljava/lang/Object;");
+
+  // And finally ... iterate over the map, filling the native map
+  somap contents;
+  while (env.CallBooleanMethod(iterator, mid_it_has_next))
+    {
+      const jobject e = env.CallObjectMethod(iterator, mid_it_next);
+      const String keystr(env, jstring(env.CallObjectMethod(e, mid_get_key)));
+      const jobject value(env.CallObjectMethod(e, mid_get_value));
+      const String::Contents key(keystr);
+      contents.insert(somap::value_type(key.c_str(), value));
+    }
+  return contents;
+}
+
+// Class Java::BaseMutableMap
+
+const char* const BaseMutableMap::m_class_name = "java/util/HashMap";
+
+namespace {
+jobject make_hash_map(Env env, const char* class_name, jint length)
+{
+  const jclass cls = env.FindClass(class_name);
+  const jmethodID mid_ctor = env.GetMethodID(cls, "<init>", "(I)V");
+  return env.NewObject(cls, mid_ctor, length);
+}
+} // anonymous namespace
+
+BaseMutableMap::BaseMutableMap(Env env, jint length)
+  : Object(env, m_class_name,
+           make_hash_map(env, m_class_name, length))
+{}
+
+void BaseMutableMap::clear()
+{
+  if (!m_mid_clear)
+    m_mid_clear = m_env.GetMethodID(m_class, "clear", "()V");
+  m_env.CallVoidMethod(m_jthis, m_mid_clear);
+}
+
+jint BaseMutableMap::length() const
+{
+  if (!m_mid_size)
+    m_mid_size = m_env.GetMethodID(m_class, "size", "()I");
+  return m_env.CallIntMethod(m_jthis, m_mid_size);
+}
+
+void BaseMutableMap::put(const std::string& key, jobject obj)
+{
+  if (!m_mid_put)
+    m_mid_put = m_env.GetMethodID(m_class, "put",
+                                  "(Ljava/lang/Object;Ljava/lang/Object;)"
+                                  "Ljava/lang/Object;");
+  m_env.CallObjectMethod(m_jthis, m_mid_put, String(m_env, key).get(), obj);
+}
+
+jobject BaseMutableMap::operator[](const std::string& index) const
+{
+  if (!m_mid_has_key)
+    m_mid_has_key = m_env.GetMethodID(m_class, "containsKey",
+                                      "(Ljava/lang/Object;)Z");
+
+  const String key(m_env, index);
+  if (!m_env.CallBooleanMethod(m_jthis, m_mid_has_key, key.get()))
+    {
+      std::string msg(_("Map does not contain key: "));
+      msg += index;
+      throw std::out_of_range(msg.c_str());
+    }
+
+  if (!m_mid_get)
+    m_mid_get = m_env.GetMethodID(m_class, "get",
+                                  "(Ljava/lang/Object;)Ljava/lang/Object;");
+  return m_env.CallObjectMethod(m_jthis, m_mid_get, key.get());
+}
+
+} // namespace Java

Propchange: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.cpp
------------------------------------------------------------------------------
    svn:eol-style = native

Added: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp?rev=1542401&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp (added)
+++ subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp Fri Nov 15 21:08:05 2013
@@ -0,0 +1,249 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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.
+ * ====================================================================
+ * @endcopyright
+ */
+
+#ifndef SVN_JAVAHL_JNIWRAPPER_STRING_MAP_HPP
+#define SVN_JAVAHL_JNIWRAPPER_STRING_MAP_HPP
+
+#include <map>
+#include <string>
+#include <algorithm>
+
+#include "jni_env.hpp"
+#include "jni_object.hpp"
+
+#include "svn_private_config.h"
+
+namespace Java {
+
+/**
+ * Non-template base for an immutable type-safe Java map with String keys.
+ *
+ * Converts the map to a @c std::map containing @c jobject references.
+ *
+ * @since New in 1.9.
+ */
+class BaseMap : public Object
+{
+  typedef std::map<std::string, jobject> somap;
+
+public:
+  /**
+   * Returns the number of elements in the map.
+   */
+  jint length() const
+    {
+      return jint(m_contents.size());
+    }
+
+protected:
+  /**
+   * Constructs the map wrapper, converting the contents to an
+   * @c std::map.
+   */
+  explicit BaseMap(Env env, jobject jmap)
+    : Object(env, m_class_name, jmap),
+      m_contents(convert_to_map(env, m_class, m_jthis))
+    {}
+
+  /**
+   * Returns the object reference identified by @a index.
+   * @throw std::out_of_range if there is no such element.
+   */
+  jobject operator[](const std::string& index) const;
+
+  const somap m_contents;
+
+private:
+  static const char* const m_class_name;
+  static somap convert_to_map(Env env, jclass cls, jobject jmap);
+};
+
+/**
+ * Template wrapper for an immutable type-safe Java map.
+ *
+ * @since New in 1.9.
+ */
+template <typename T, typename NativeT=jobject>
+class Map : public BaseMap
+{
+public:
+  /**
+   * Constructs the map wrapper, converting the contents to an
+   * @c std::map.
+   */
+  explicit Map(Env env, jobject jmap)
+    : BaseMap(env, jmap)
+    {}
+
+  /**
+   * Returns a wrapper object for the object reference identified by @a index.
+   * @throw std::out_of_range if there is no such element.
+   */
+  T operator[](const std::string& index) const
+    {
+      return T(m_env, NativeT(BaseMap::operator[](index)));
+    }
+
+  /**
+   * Iterates over the items in the map, calling @a function for
+   * each item.
+   * @see std::for_each
+   * @note Unlike std::for_each, which invokes the functor with a
+   *     single @c value_type argument, this iterator adapts it to cal
+   *     @a function with separate @c const references to the key and
+   *     value.
+   */
+  template<typename F>
+  F for_each(F function) const
+    {
+      const FunctorAdapter<F> adapter(m_env, function);
+      std::for_each(m_contents.begin(), m_contents.end(), adapter);
+      return function;
+    }
+
+private:
+  template<typename F>
+  struct FunctorAdapter
+  {
+    explicit FunctorAdapter(const Env& env, F& function)
+      : m_env(env),
+        m_function(function)
+      {}
+
+    void operator()(const std::pair<std::string, jobject>& item) const
+      {
+        const std::string& key(item.first);
+        const T value(m_env, NativeT(item.second));
+        m_function(key, value);
+      }
+
+    const Env& m_env;
+    F& m_function;
+  };
+};
+
+/**
+ * Non-template base for a mutable type-safe Java map with String keys.
+ *
+ * @since New in 1.9.
+ */
+class BaseMutableMap : public Object
+{
+public:
+  /**
+   * Clears the contents of the map.
+   */
+  void clear();
+
+  /**
+   * Returns the number of elements in the map.
+   */
+  jint length() const;
+
+  /**
+   * Checks if the map is empty.
+   */
+  bool is_empty() const
+    {
+      return (length() == 0);
+    }
+
+protected:
+  /**
+   * Constructs the map wrapper, deriving the class from @a jmap.
+   */
+  explicit BaseMutableMap(Env env, jobject jmap)
+    : Object(env, jmap)
+    {}
+
+  /**
+   * Constructs and wraps an empty map of type @c java.util.HashMap
+   * with initial allocation size @a length.
+   */
+  explicit BaseMutableMap(Env env, jint length);
+
+  /**
+   * Inserts @a obj identified by @a key into the map.
+   */
+  void put(const std::string& key, jobject obj);
+
+  /**
+   * Returns the object reference identified by @a index.
+   * @throw std::out_of_range if there is no such element.
+   */
+  jobject operator[](const std::string& index) const;
+
+private:
+  static const char* const m_class_name;
+  MethodID m_mid_put;
+  MethodID m_mid_clear;
+  mutable MethodID m_mid_has_key;
+  mutable MethodID m_mid_get;
+  mutable MethodID m_mid_size;
+};
+
+/**
+ * Template wrapper for a mutable type-safe Java map.
+ *
+ * @since New in 1.9.
+ */
+template <typename T, typename NativeT=jobject>
+class MutableMap : public BaseMutableMap
+{
+public:
+  /**
+   * Constructs the map wrapper, deriving the class from @a jmap.
+   */
+  explicit MutableMap(Env env, jobject jmap)
+    : BaseMutableMap(env, jmap)
+    {}
+
+  /**
+   * Constructs and wraps an empty map of type @c java.util.HashMap
+   * with initial allocation size @a length.
+   */
+  explicit MutableMap(Env env, jint length = 0)
+    : BaseMutableMap(env, length)
+    {}
+
+  /**
+   * Inserts @a obj identified by @a key into the map.
+   */
+  void put(const std::string& key, const T& obj)
+    {
+      BaseMutableMap::put(key, obj.get());
+    }
+
+  /**
+   * Returns a wrapper object for the object reference identified by @a index.
+   * @throw std::out_of_range if there is no such element.
+   */
+  T operator[](const std::string& index) const
+    {
+      return T(m_env, NativeT(BaseMutableMap::operator[](index)));
+    }
+};
+
+} // namespace Java
+
+#endif // SVN_JAVAHL_JNIWRAPPER_STRING_MAP_HPP

Propchange: subversion/trunk/subversion/bindings/javahl/native/jniwrapper/jni_string_map.hpp
------------------------------------------------------------------------------
    svn:eol-style = native

Added: subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp?rev=1542401&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp (added)
+++ subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp Fri Nov 15 21:08:05 2013
@@ -0,0 +1,107 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file org_apache_subversion_javahl_util_SubstLib.cpp
+ * @brief Implementation of the native methods in the Java class SubstLib
+ */
+
+#include "../include/org_apache_subversion_javahl_util_SubstLib.h"
+
+#include "jniwrapper/jni_stack.hpp"
+#include "jniwrapper/jni_array.hpp"
+#include "jniwrapper/jni_string.hpp"
+#include "jniwrapper/jni_string_map.hpp"
+
+#include "JNIUtil.h"
+#include "InputStream.h"
+
+#include <apr_strings.h>
+#include <apr_hash.h>
+
+#include "svn_subst.h"
+
+#include "svn_private_config.h"
+
+
+namespace {
+} // anoymous namespace
+
+
+JNIEXPORT jobject JNICALL
+Java_org_apache_subversion_javahl_util_SubstLib_buildKeywords(
+    JNIEnv* jenv, jobject jthis,
+    jbyteArray jkeywords_value, jlong jrevision,
+    jstring jurl, jstring jrepos_root_url,
+    jobject jdate, jstring jauthor)
+{
+  SVN_JAVAHL_JNI_TRY(SubstLib, buildKeywords)
+    {
+      const Java::Env env(jenv);
+
+      const Java::ByteArray keywords_value(env, jkeywords_value);
+      const Java::String url(env, jurl);
+      const Java::String repos_root_url(env, jrepos_root_url);
+      const Java::String author(env, jauthor);
+
+      // Using a "global" request pool since we don't keep a context with
+      // its own pool around for these functions.
+      SVN::Pool pool;
+
+      const Java::ByteArray::Contents keywords_contents(keywords_value);
+      svn_string_t* keywords_string = keywords_contents.get_string(pool);
+      const char* revision = (jrevision < 0 ? NULL
+                              : apr_psprintf(pool.getPool(),
+                                             "%"APR_UINT64_T_FMT,
+                                             apr_uint64_t(jrevision)));
+      const Java::String::Contents url_contents(url);
+      const Java::String::Contents root_url_contents(repos_root_url);
+      const Java::String::Contents author_contents(author);
+
+      apr_hash_t* kw = NULL;
+      SVN_JAVAHL_CHECK(svn_subst_build_keywords3(
+                           &kw,
+                           keywords_string->data,
+                           revision,
+                           url_contents.c_str(),
+                           root_url_contents.c_str(),
+                           (jdate ? JNIUtil::getDate(jdate) : 0),
+                           author_contents.c_str(),
+                           pool.getPool()));
+
+      Java::MutableMap<Java::ByteArray, jbyteArray>
+        keywords(env, jint(apr_hash_count(kw)));
+      for (apr_hash_index_t* hi = apr_hash_first(pool.getPool(), kw);
+           hi; hi = apr_hash_next(hi))
+        {
+          const void* rkey;
+          void* rval;
+          apr_hash_this(hi, &rkey, NULL, &rval);
+
+          svn_string_t* const val = static_cast<svn_string_t*>(rval);
+          keywords.put(static_cast<const char*>(rkey),
+                       Java::ByteArray(env, val->data, jsize(val->len)));
+        }
+      return keywords.get();
+    }
+  SVN_JAVAHL_JNI_CATCH;
+  return NULL;
+}

Propchange: subversion/trunk/subversion/bindings/javahl/native/org_apache_subversion_javahl_util_SubstLib.cpp
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java?rev=1542401&r1=1542400&r2=1542401&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java (original)
+++ subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/SVNUtil.java Fri Nov 15 21:08:05 2013
@@ -29,7 +29,9 @@ import org.apache.subversion.javahl.util
 
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 public class SVNUtil
 {
@@ -398,6 +400,7 @@ public class SVNUtil
      *  <dd>relative to the scheme</dd>
      *  <dt><code>/</code></dt>
      *  <dd>relative to the server's hostname</dd>
+     * </dl>
      *<p>
      * The <code>../<code> and ^/ relative URLs may use <code>..<code>
      * to remove path elements up to the server root.
@@ -416,4 +419,234 @@ public class SVNUtil
         return propLib.resolveExternalsUrl(
                    external, reposRootUrl, parentDirUrl);
     }
+
+    //
+    // Newline translation and keyword expansion
+    //
+    private static final SubstLib substLib = new SubstLib();
+
+    /**
+     * Use the linefeed code point ('<code>\x0a</code>')
+     * for the newline separator.
+     * @see translateStream
+     * @see untranslateStream
+     */
+    public static final byte[] EOL_LF = substLib.EOL_LF;
+
+    /**
+     * Use the carraige-return code point ('<code>\x0d</code>')
+     * for the newline separator.
+     * @see translateStream
+     * @see untranslateStream
+     */
+    public static final byte[] EOL_CR = substLib.EOL_CR;
+
+    /**
+     * Use carriage-return/linefeed sequence ('<code>\x0d\x0a</code>')
+     * for the newline separator.
+     * @see translateStream
+     * @see untranslateStream
+     */
+    public static final byte[] EOL_CRLF = substLib.EOL_CRLF;
+
+
+    /**
+     * Build a dictionary of expanded keyword values, given the
+     * contents of a file's <code>svn:keywords</code> property, its
+     * revision, URL, the date it was committed on, the author of the
+     * commit and teh URL of the repository root.
+     *<p>
+     * Custom keywords defined in <code>svn:keywords</code> properties
+     * are expanded using the provided parameters and in accordance
+     * with the following format substitutions in the
+     * <code>keywordsValue</code>:
+     * <dl>
+     *   <dt><code>%a</dt></code>
+     * <dd>The author.</dd>
+     *   <dt><code>%b</dt></code>
+     * <dd>The basename of the URL.</dd>
+     *   <dt><code>%d</dt></code>
+     * <dd>Short format of the date.</dd>
+     *   <dt><code>%D</dt></code>
+     * <dd>Long format of the date.</dd>
+     *   <dt><code>%P</dt></code>
+     * <dd>The file's path, relative to the repository root URL.</dd>
+     *   <dt><code>%r</dt></code>
+     * <dd>The revision.</dd>
+     *   <dt><code>%R</dt></code>
+     * <dd>The URL to the root of the repository.</dd>
+     *   <dt><code>%u</dt></code>
+     * <dd>The URL of the file.</dd>
+     *   <dt><code>%_</dt></code>
+     * <dd>A space (keyword definitions cannot contain a literal space).</dd>
+     *   <dt><code>%%</dt></code>
+     * <dd>A literal '%'.</dd>
+     *   <dt><code>%H</dt></code>
+     * <dd>Equivalent to <code>%P%_%r%_%d%_%a</code>.</dd>
+     *   <dt><code>%I</dt></code>
+     * <dd>Equivalent to <code>%b%_%r%_%d%_%a</code>.</dd>
+     * </dl>
+     *<p>
+     * Custom keywords are defined by appending '=' to the keyword
+     * name, followed by a string containing any combination of the
+     * format substitutions.
+     *<p>
+     * Any of the <code>revision</code>, <code>url</code>,
+     * <code>reposRootUrl</code>, <code>date</code> and
+     * <code>author</code> parameters may be <code>null</code>, or
+     * {@link Revision#SVN_INVALID_REVNUM} for <code>revision</code>,
+     * to indicate that the information is not present. Each piece of
+     * information that is not present expands to the empty string
+     * wherever it appears in an expanded keyword value.  (This can
+     * result in multiple adjacent spaces in the expansion of a
+     * multi-valued keyword such as "<code>Id</code>".)
+     */
+    public static Map<String, byte[]> buildKeywords(byte[] keywordsValue,
+                                                    long revision,
+                                                    String url,
+                                                    String reposRootUrl,
+                                                    Date date,
+                                                    String author)
+        throws SubversionException, ClientException
+    {
+        return substLib.buildKeywords(keywordsValue, revision,
+                                      url, reposRootUrl, date, author);
+    }
+
+    /**
+     * Return a stream which performs end-of-line translation and
+     * keyword expansion when read from.
+     *<p>
+     * Make sure you close the reurned stream stream to ensure all
+     * data are flushed and cleaned up (this will also close the
+     * provided stream).
+     *<p>
+     * If <code>eolMarker</code> is not <code>null</code>, replace
+     * whatever any end-of-line sequences in the input with
+     * <code>eolMarker</code>.  If the input has an inconsistent line
+     * ending style, then:
+     * <ul>
+     *   <li>if <code>repairEol</code> is <code>false</code>, then a
+     *       subsequent read or other operation on the stream will
+     *       generate an error when the inconsistency is detected;</li>
+     *   <li>if <code>repaorEol</code> is <code>true</code>, convert any
+     *       line ending to <code>eolMarker</code>.<br/>
+     *       Recognized line endings are: "\n", "\r", and "\r\n".</li>
+     * </ul>
+     *<p>
+     * Expand or contract keywords using the contents of
+     * <code>keywords</code> as the new values.  If
+     * <code>expandKeywords</code> is <code>true</code>, expand
+     * contracted keywords and re-expand expanded keywords; otherwise,
+     * contract expanded keywords and ignore contracted ones.
+     * Keywords not found in the dictionary are ignored (not
+     * contracted or expanded).  If the <code>keywords</code> itself
+     * is <code>null</code>, keyword substitution will be altogether
+     * ignored.
+     *<p>
+     * Detect only keywords that are no longer than
+     * <code>SVN_KEYWORD_MAX_LEN</code> bytes (currently: 255),
+     * including the delimiters and the keyword itself.
+     *<p>
+     * Recommendation: if <code>expandKeywords</code> is
+     * <code>false</code>, then you don't care about the keyword
+     * values, so just put <code>null</code> values into the
+     * <code>keywords</code> dictionary.
+     *<p>
+     * If the inner stream implements marking and seeking via
+     * {@link InputStream#mark} and {@link InputStream#reset}, the
+     * translated stream will too.
+     *
+     * @param source the source (untranslated) stream.
+     * @param eolMarker the byte sequence to use as the end-of-line marker;
+     *     must be one of {@link #EOL_LF}, {@link #EOL_CR}
+     *     or {@link #EOL_CRLF}.
+     * @param repairEol flag to repair end-of-lines; see above
+     * @param keywords the keyword dictionary; see {@link buildKeywords}
+     * @param expandKeywords flag to expand keywords
+     */
+    public static InputStream translateStream(InputStream source,
+                                              byte[] eolMarker,
+                                              boolean repairEol,
+                                              Map<String, byte[]> keywords,
+                                              boolean expandKeywords)
+        throws SubversionException, ClientException
+    {
+        return substLib.translateInputStream(
+                    source, eolMarker, repairEol,
+                    keywords, true, expandKeywords,
+                    null, Revision.SVN_INVALID_REVNUM,
+                    null, null, null, null);
+    }
+
+    /**
+     * Expand keywords and return a stream which performs end-of-line
+     * translation and keyword expansion when read from.
+     * @see buildKeywords
+     * @see translateStream(InputStream,byte[],boolean,Map,boolean)
+     */
+    public static InputStream translateStream(InputStream source,
+                                              byte[] eolMarker,
+                                              boolean repairEol,
+                                              boolean expandKeywords,
+                                              byte[] keywordsValue,
+                                              long revision,
+                                              String url,
+                                              String reposRootUrl,
+                                              Date date,
+                                              String author)
+        throws SubversionException, ClientException
+    {
+        return substLib.translateInputStream(
+                    source, eolMarker, repairEol,
+                    null, false, expandKeywords,
+                    keywordsValue, revision,
+                    url, reposRootUrl, date, author);
+    }
+
+    /**
+     * Return a stream which performs end-of-line translation and
+     * keyword expansion when written to. Behaves like
+     * {@link #translateStream(InputStream,byte[],boolean,Map,boolean)},
+     * except that it translates an <code>OutputStream</code> and never
+     * supports marking and seeking.
+     */
+    public static OutputStream translateStream(OutputStream destination,
+                                               byte[] eolMarker,
+                                               boolean repairEol,
+                                               Map<String, byte[]> keywords,
+                                               boolean expandKeywords)
+        throws SubversionException, ClientException
+    {
+        return substLib.translateOutputStream(
+                    destination, eolMarker, repairEol,
+                    keywords, true, expandKeywords,
+                    null, Revision.SVN_INVALID_REVNUM,
+                    null, null, null, null);
+    }
+
+    /**
+     * Expand keywords and return a stream which performs end-of-line
+     * translation and keyword expansion when written to.
+     * @see buildKeywords
+     * @see translateStream(OutputStream,byte[],boolean,Map,boolean)
+     */
+    public static OutputStream translateStream(OutputStream destination,
+                                               byte[] eolMarker,
+                                               boolean repairEol,
+                                               boolean expandKeywords,
+                                               byte[] keywordsValue,
+                                               long revision,
+                                               String url,
+                                               String reposRootUrl,
+                                               Date date,
+                                               String author)
+        throws SubversionException, ClientException
+    {
+        return substLib.translateOutputStream(
+                    destination, eolMarker, repairEol,
+                    null, false, expandKeywords,
+                    keywordsValue, revision,
+                    url, reposRootUrl, date, author);
+    }
 }

Added: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java?rev=1542401&view=auto
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java (added)
+++ subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java Fri Nov 15 21:08:05 2013
@@ -0,0 +1,119 @@
+/**
+ * @copyright
+ * ====================================================================
+ *    Licensed to the Apache Software Foundation (ASF) under one
+ *    or more contributor license agreements.  See the NOTICE file
+ *    distributed with this work for additional information
+ *    regarding copyright ownership.  The ASF licenses this file
+ *    to you 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.
+ * ====================================================================
+ * @endcopyright
+ */
+
+package org.apache.subversion.javahl.util;
+
+import org.apache.subversion.javahl.SVNUtil;
+import org.apache.subversion.javahl.ClientException;
+import org.apache.subversion.javahl.SubversionException;
+import org.apache.subversion.javahl.NativeResources;
+
+import java.util.Date;
+import java.util.Map;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Encapsulates utility functions for substitution and translation
+ * provided by the <code>svn_subst</code> module of
+ * <code>libsvn_subr</code>.
+ * @since 1.9
+ */
+public class SubstLib
+{
+    /**
+     * Load the required native library.
+     */
+    static
+    {
+        NativeResources.loadNativeLibrary();
+    }
+
+    /**
+     * @see SVNUtil.EOL_LF
+     */
+    public static final byte[] EOL_LF = new byte[]{ (byte)10 };
+
+    /**
+     * @see SVNUtil.EOL_CR
+     */
+    public static final byte[] EOL_CR = new byte[]{ (byte)13 };
+
+    /**
+     * @see SVNUtil.EOL_CRLF
+     */
+    public static final byte[] EOL_CRLF = new byte[]{ EOL_CR[0], EOL_LF[0] };
+
+    /**
+     * @see SVNUtil.buildKeywords
+     */
+    public native Map<String, byte[]> buildKeywords(byte[] keywordsValue,
+                                                    long revision,
+                                                    String url,
+                                                    String reposRootUrl,
+                                                    Date date,
+                                                    String author)
+        throws SubversionException, ClientException;
+
+    /**
+     * @see SVNUtil.translateStream
+     */
+    public /*native*/ InputStream translateInputStream(
+                                      InputStream source,
+                                      byte[] eolMarker,
+                                      boolean repairEol,
+                                      Map<String, byte[]> keywords,
+                                      boolean useKeywordsMap,
+                                      boolean expandKeywords,
+                                      byte[] keywordsValue,
+                                      long revision,
+                                      String url,
+                                      String reposRootUrl,
+                                      Date date,
+                                      String author)
+        throws SubversionException, ClientException//;
+    {
+        throw new RuntimeException("Not implemented: SubstLib.translateInputStream");
+    }
+
+    /**
+     * @see SVNUtil.translateStream
+     */
+    public /*native*/ OutputStream translateOutputStream(
+                                      OutputStream destination,
+                                      byte[] eolMarker,
+                                      boolean repairEol,
+                                      Map<String, byte[]> keywords,
+                                      boolean useKeywordsMap,
+                                      boolean expandKeywords,
+                                      byte[] keywordsValue,
+                                      long revision,
+                                      String url,
+                                      String reposRootUrl,
+                                      Date date,
+                                      String author)
+        throws SubversionException, ClientException//;
+    {
+        throw new RuntimeException("Not implemented: SubstLib.translateOutputStream");
+    }
+}

Propchange: subversion/trunk/subversion/bindings/javahl/src/org/apache/subversion/javahl/util/SubstLib.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/UtilTests.java
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/UtilTests.java?rev=1542401&r1=1542400&r2=1542401&view=diff
==============================================================================
--- subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/UtilTests.java (original)
+++ subversion/trunk/subversion/bindings/javahl/tests/org/apache/subversion/javahl/UtilTests.java Fri Nov 15 21:08:05 2013
@@ -35,6 +35,7 @@ import java.util.Arrays;
 import java.util.ArrayList;
 import java.util.Date;
 import java.util.List;
+import java.util.Map;
 
 /**
  * Tests the JavaHL SVNUtil APIs.
@@ -392,4 +393,20 @@ public class UtilTests extends SVNTests
                          new ExternalItem("x", "//a/b/c", null, null),
                          "http://a", "http://a/b"));
     }
+
+    public void testBuildKeywords() throws Throwable
+    {
+        final byte[] kwval = "TEST=%H%_%b%_%u".getBytes();
+
+        Map<String, byte[]> result;
+
+        result = SVNUtil.buildKeywords(kwval, Revision.SVN_INVALID_REVNUM,
+                                       null, null, null, null);
+        assertEquals("     ", new String(result.get("TEST")));
+
+        result = SVNUtil.buildKeywords(kwval, 48, "http://a/b/c",
+                                       "http://a", new Date(1), "X");
+        assertEquals("b/c 48 1970-01-01 00:00:00Z X c http://a/b/c",
+                     new String(result.get("TEST")));
+    }
 }