Skip to content

Commit

Permalink
Merge pull request #483 from Thrameos/convert_string2
Browse files Browse the repository at this point in the history
Enable option to support legacy conversion of strings with polymorphic return.
  • Loading branch information
Thrameos committed Jun 18, 2019
2 parents bdd2059 + e607728 commit 2859229
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 35 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Expand Up @@ -5,6 +5,7 @@
*.cpp text eol=lf
*.c text eol=lf
*.h text eol=lf
*.java text eol=lf

*.xml text eol=lf
*.md text
Expand Down
19 changes: 19 additions & 0 deletions .travis.yml
Expand Up @@ -47,6 +47,14 @@ matrix:
dist: xenial
language: python

# TODO: remove in 0.8
- name: "Python 2.7 on Xenial Linux"
python: 2.7
dist: xenial
language: python
env:
- JPYPE_STR_CONVERSION=False

- name: "Python 3.7 on Xenial Linux (w/o NUMPY)"
python: 3.7
dist: xenial
Expand All @@ -64,6 +72,16 @@ matrix:
- PYTHON=python3
- PIP=pip3

# TODO: remove in 0.8
- name: "Python 3.7 on Xenial Linux"
python: 3.7
dist: xenial
language: python
env:
- PYTHON=python3
- PIP=pip3
- JPYPE_STR_CONVERSION=False

## This pattern is good, but something is broken in openJDK 12
## Deal with in another pull
- name: "Python 2.7 on macOS"
Expand Down Expand Up @@ -101,3 +119,4 @@ install:
script:
- $PYTHON -c "import jpype"
- $PYTHON -m pytest -v test/jpypetest

59 changes: 43 additions & 16 deletions jpype/_core.py
Expand Up @@ -113,7 +113,7 @@ def _handleClassPath(clsList):
def startJVM(*args, **kwargs):
"""
Starts a Java Virtual Machine. Without options it will start
the JVM with the default classpath and jvmpath.
the JVM with the default classpath and jvmpath.
The default classpath is determined by ``jpype.getClassPath()``.
The default jvmpath is determined by ``jpype.getDefaultJVMPath()``.
Expand All @@ -124,17 +124,29 @@ def startJVM(*args, **kwargs):
Keyword Arguments:
jvmpath (str): Path to the jvm library file,
Typically one of (``libjvm.so``, ``jvm.dll``, ...)
Typically one of (``libjvm.so``, ``jvm.dll``, ...)
Using None will apply the default jvmpath.
classpath (str,[str]): Set the classpath for the jvm.
This will override any classpath supplied in the arguments
list. A value of None will give no classpath to JVM.
ignoreUnrecognized (bool): Option to JVM to ignore
invalid JVM arguments. Default is False.
convertStrings (bool): Option to JPype to force Java strings to
cast to Python strings. This option is to support legacy code
for which conversion of Python strings was the default. This
will globally change the behavior of all calls using
strings, and a value of True is NOT recommended for newly
developed code.
The default value for this option during 0.7 series is
True. The option will be False starting in 0.8. A
warning will be issued if this option is not specified
during the transition period.
Raises:
OSError: if the JVM cannot be started or is already running.
TypeError: if an invalid keyword argument is supplied
TypeError: if an invalid keyword argument is supplied
or a keyword argument conflicts with the arguments.
"""
Expand Down Expand Up @@ -185,11 +197,26 @@ def startJVM(*args, **kwargs):

ignoreUnrecognized = kwargs.pop('ignoreUnrecognized', False)

if not "convertStrings" in kwargs:
import warnings
warnings.warn("""
-------------------------------------------------------------------------------
Deprecated: convertStrings was not specified when starting the JVM. The default
behavior in JPype will be False starting in JPype 0.8. The recommended setting
for new code is convertStrings=False. The legacy value of True was assumed for
this session. If you are a user of an application that reported this warning,
please file a ticket with the developer.
-------------------------------------------------------------------------------
""")

convertStrings = kwargs.pop('convertStrings', True)


if kwargs:
raise TypeError("startJVM() got an unexpected keyword argument '%s'"
% (','.join([str(i) for i in kwargs])))

_jpype.startup(jvmpath, tuple(args), ignoreUnrecognized)
_jpype.startup(jvmpath, tuple(args), ignoreUnrecognized, convertStrings)
_initialize()


Expand All @@ -201,8 +228,8 @@ def attachToJVM(jvm):
def shutdownJVM():
""" Shuts down the JVM.
This method shuts down the JVM and thus disables access to existing
Java objects. Due to limitations in the JPype, it is not possible to
This method shuts down the JVM and thus disables access to existing
Java objects. Due to limitations in the JPype, it is not possible to
restart the JVM after being terminated.
"""
_jpype.shutdown()
Expand All @@ -211,10 +238,10 @@ def shutdownJVM():
def isThreadAttachedToJVM():
""" Checks if a thread is attached to the JVM.
Python automatically attaches threads when a Java method is called.
This creates a resource in Java for the Python thread. This method
can be used to check if a Python thread is currently attached so that
it can be disconnected prior to thread termination to prevent leaks.
Python automatically attaches threads when a Java method is called.
This creates a resource in Java for the Python thread. This method
can be used to check if a Python thread is currently attached so that
it can be disconnected prior to thread termination to prevent leaks.
Returns:
True if the thread is attached to the JVM, False if the thread is
Expand All @@ -226,8 +253,8 @@ def isThreadAttachedToJVM():
def attachThreadToJVM():
""" Attaches a thread to the JVM.
The function manually connects a thread to the JVM to allow access to
Java objects and methods. JPype automaticatlly attaches when a Java
The function manually connects a thread to the JVM to allow access to
Java objects and methods. JPype automaticatlly attaches when a Java
resource is used, so a call to this is usually not needed.
Raises:
Expand All @@ -241,7 +268,7 @@ def detachThreadFromJVM():
This function detaches the thread and frees the associated resource in
the JVM. For codes making heavy use of threading this should be used
to prevent resource leaks. The thread can be reattached, so there
to prevent resource leaks. The thread can be reattached, so there
is no harm in detaching early or more than once. This method cannot fail
and there is no harm in calling it when the JVM is not running.
"""
Expand All @@ -252,7 +279,7 @@ def synchronized(obj):
""" Creates a resource lock for a Java object.
Produces a monitor object. During the lifespan of the monitor the Java
will not be able to acquire a thread lock on the object. This will
will not be able to acquire a thread lock on the object. This will
prevent multiple threads from modifying a shared resource.
This should always be used as part of a Python ``with`` startment.
Expand Down Expand Up @@ -319,8 +346,8 @@ def get_default_jvm_path(*args, **kwargs):
def getJVMVersion():
""" Get the JVM version if the JVM is started.
This function can be used to determine the version of the JVM. It is
useful to help determine why a Jar has failed to load.
This function can be used to determine the version of the JVM. It is
useful to help determine why a Jar has failed to load.
Returns:
A typle with the (major, minor, revison) of the JVM if running.
Expand Down
4 changes: 3 additions & 1 deletion native/common/include/jp_env.h
Expand Up @@ -44,7 +44,7 @@ namespace JPEnv
* Load the JVM
* TODO : add the non-string parameters, for possible callbacks
*/
void loadJVM(const string& vmPath, char ignoreUnrecognized, const StringVector& args);
void loadJVM(const string& vmPath, const StringVector& args, bool ignoreUnrecognized, bool convertStrings);

void attachJVM(const string& vmPath);

Expand All @@ -62,6 +62,8 @@ namespace JPEnv

void CreateJavaVM(void* arg);
void GetCreatedJavaVM();

bool getConvertStrings();
}

#endif // _JPENV_H_
9 changes: 8 additions & 1 deletion native/common/jp_env.cpp
Expand Up @@ -26,6 +26,7 @@
namespace
{ // impl details
JavaVM* s_JavaVM = NULL;
bool s_ConvertStrings = false;

jint(JNICALL *CreateJVM_Method)(JavaVM **pvm, void **penv, void *args);
jint(JNICALL *GetCreatedJVMs_Method)(JavaVM **pvm, jsize size, jsize* nVms);
Expand Down Expand Up @@ -137,9 +138,10 @@ void loadEntryPoints(const string& path)
GetCreatedJVMs_Method = (jint(JNICALL *)(JavaVM **, jsize, jsize*))GetAdapter()->getSymbol("JNI_GetCreatedJavaVMs");
}

void JPEnv::loadJVM(const string& vmPath, char ignoreUnrecognized, const StringVector& args)
void JPEnv::loadJVM(const string& vmPath, const StringVector& args, bool ignoreUnrecognized, bool convertStrings)
{
JP_TRACE_IN("JPEnv::loadJVM");
s_ConvertStrings = convertStrings;

// Get the entry points in the shared library
loadEntryPoints(vmPath);
Expand Down Expand Up @@ -177,6 +179,11 @@ void JPEnv::loadJVM(const string& vmPath, char ignoreUnrecognized, const StringV
JP_TRACE_OUT;
}

bool JPEnv::getConvertStrings()
{
return s_ConvertStrings;
}

void JPEnv::shutdown()
{
JP_TRACE_IN("JPEnv::shutdown");
Expand Down
21 changes: 19 additions & 2 deletions native/common/jp_stringclass.cpp
Expand Up @@ -12,7 +12,7 @@
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.
*****************************************************************************/
#include <jpype.h>

Expand All @@ -33,6 +33,23 @@ JPPyObject JPStringClass::convertToPythonObject(jvalue val)
return JPPyObject::getNone();
}

if (JPEnv::getConvertStrings())
{
bool unicode = false;
string str = JPJni::toStringUTF8((jstring) (val.l));
#if PY_MAJOR_VERSION < 3
for (int i = 0; i < str.size(); ++i)
{
if (str[i]&0x80)
{
unicode = true;
break;
}
}
#endif
return JPPyString::fromStringUTF8(str, unicode);
}

return JPPythonEnv::newJavaObject(JPValue(this, val));
JP_TRACE_OUT;
}
Expand Down Expand Up @@ -78,7 +95,7 @@ jvalue JPStringClass::convertToJava(PyObject* obj)
return res;
}

// java.lang.string is already a global object
// java.lang.string is already a global object
JPValue* value = JPPythonEnv::getJavaValue(obj);
if (value != NULL)
{
Expand Down
12 changes: 7 additions & 5 deletions native/python/pyjp_module.cpp
Expand Up @@ -12,7 +12,7 @@
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.
*****************************************************************************/

#include <pyjp.h>
Expand Down Expand Up @@ -62,7 +62,7 @@ PyMODINIT_FUNC PyInit__jpype()
PyMODINIT_FUNC init_jpype()
#endif
{
// This is required for python versions prior to 3.7.
// This is required for python versions prior to 3.7.
// It is called by the python initialization starting from 3.7,
// but is safe to call afterwards.
PyEval_InitThreads();
Expand Down Expand Up @@ -117,8 +117,10 @@ PyObject* PyJPModule::startup(PyObject* obj, PyObject* args)
PyObject* vmOpt;
PyObject* vmPath;
char ignoreUnrecognized = true;
char convertStrings = false;

if (!PyArg_ParseTuple(args, "OO!b|", &vmPath, &PyTuple_Type, &vmOpt, &ignoreUnrecognized))
if (!PyArg_ParseTuple(args, "OO!bb", &vmPath, &PyTuple_Type, &vmOpt,
&ignoreUnrecognized, &convertStrings))
{
return NULL;
}
Expand Down Expand Up @@ -151,7 +153,7 @@ PyObject* PyJPModule::startup(PyObject* obj, PyObject* args)
}
}

JPEnv::loadJVM(cVmPath, ignoreUnrecognized, args);
JPEnv::loadJVM(cVmPath, args, ignoreUnrecognized, convertStrings);
Py_RETURN_NONE;
}
PY_STANDARD_CATCH;
Expand Down Expand Up @@ -324,7 +326,7 @@ PyObject* PyJPModule::convertToDirectByteBuffer(PyObject* self, PyObject* args)
// Bind lifespan of the python to the java object.
JPReferenceQueue::registerRef(ref, src);

// Convert to python object
// Convert to python object

jvalue v;
v.l = ref;
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -40,7 +40,7 @@
package_dir={
'jpype': 'jpype',
},
setup_requires=['setuptools_scm'],
#setup_requires=['setuptools_scm'],
tests_require=['pytest', 'mock', 'unittest2'],
extras_require={'numpy': ['numpy>=1.6']},
cmdclass={
Expand Down
4 changes: 2 additions & 2 deletions test/harness/jpype/array/TestArray.java
Expand Up @@ -27,7 +27,7 @@ public TestArray()

public Object[] getSubClassArray()
{
return new String[] { "aaa", "bbb" };
return new Integer[] { 1, 2 };
}

public Object getArrayAsObject()
Expand All @@ -45,4 +45,4 @@ public byte[] getByteArray()
String s = "avcd";
return s.getBytes();
}
}
}
6 changes: 6 additions & 0 deletions test/harness/jpype/str/StringFunction.java
@@ -0,0 +1,6 @@
package jpype.str;

public interface StringFunction
{
public String call(String s);
}
28 changes: 28 additions & 0 deletions test/harness/jpype/str/Test.java
@@ -0,0 +1,28 @@
package jpype.str;

public class Test
{

public static String staticField = "staticField";
public String memberField = "memberField";

public static String staticCall()
{
return "staticCall";
}

public String memberCall()
{
return "memberCall";
}

public static final String array[] =
{"apples","banana","cherries","dates","elderberry"};

public static String callProxy(StringFunction f, String s)
{
return f.call(s);
}
}


0 comments on commit 2859229

Please sign in to comment.