summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlex Tambellini <atambellini@gmail.com>2013-06-23 22:20:34 -0400
committerAlex Tambellini <atambellini@gmail.com>2013-06-23 22:21:35 -0400
commit5fcdd11c8a2209d13f9fc49a7cc7a7755430fd1a (patch)
tree2bb66880e89833f9822ba14e44ca915203f0a740
parent2c644e184192975b261a81f486a04defa3172b3f (diff)
downloadpsych-5fcdd11c8a2209d13f9fc49a7cc7a7755430fd1a.tar.gz
Add native jruby support to psych. Fixes #145
-rw-r--r--.gitignore1
-rw-r--r--.travis.yml1
-rw-r--r--Rakefile26
-rw-r--r--ext/java/PsychEmitter.java349
-rw-r--r--ext/java/PsychLibrary.java81
-rw-r--r--ext/java/PsychParser.java400
-rw-r--r--ext/java/PsychToRuby.java78
-rw-r--r--ext/java/PsychYamlTree.java58
-rw-r--r--lib/psych.rb11
-rw-r--r--lib/snakeyaml-1.11.jarbin0 -> 270553 bytes
10 files changed, 996 insertions, 9 deletions
diff --git a/.gitignore b/.gitignore
index 574f763..daf3a69 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
*.swp
*.bundle
+lib/psych.jar
/pkg
/tmp
diff --git a/.travis.yml b/.travis.yml
index 9dc61c1..4daaf5c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -2,6 +2,7 @@ rvm:
- 1.9.2
- 1.9.3
- ruby-head
+ - jruby-19mode
before_script:
- gem install isolate
- gem install hoe
diff --git a/Rakefile b/Rakefile
index 00f040a..a7e2f3f 100644
--- a/Rakefile
+++ b/Rakefile
@@ -23,13 +23,25 @@ $hoe = Hoe.spec 'psych' do
extra_dev_deps << ['rake-compiler', '>= 0.4.1']
- self.spec_extras = {
- :extensions => ["ext/psych/extconf.rb"],
- :required_ruby_version => '>= 1.9.2'
- }
-
- Rake::ExtensionTask.new "psych", spec do |ext|
- ext.lib_dir = File.join(*['lib', ENV['FAT_DIR']].compact)
+ if RUBY_PLATFORM =~ /java/
+ self.spec_extras = { :platform => 'java' }
+
+ require "rake/javaextensiontask"
+ Rake::JavaExtensionTask.new("psych", spec) do |ext|
+ jruby_home = RbConfig::CONFIG['prefix']
+ ext.ext_dir = 'ext/java'
+ jars = ["#{jruby_home}/lib/jruby.jar"] + FileList['lib/*.jar']
+ ext.classpath = jars.map { |x| File.expand_path x }.join ':'
+ end
+ else
+ self.spec_extras = {
+ :extensions => ["ext/psych/extconf.rb"],
+ :required_ruby_version => '>= 1.9.2'
+ }
+
+ Rake::ExtensionTask.new "psych", spec do |ext|
+ ext.lib_dir = File.join(*['lib', ENV['FAT_DIR']].compact)
+ end
end
end
diff --git a/ext/java/PsychEmitter.java b/ext/java/PsychEmitter.java
new file mode 100644
index 0000000..1c4baa4
--- /dev/null
+++ b/ext/java/PsychEmitter.java
@@ -0,0 +1,349 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: EPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Eclipse Public
+ * License Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Charles O Nutter <headius@headius.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the EPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the EPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.psych;
+
+import static org.jruby.runtime.Visibility.*;
+
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.jcodings.Encoding;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
+import org.jruby.RubyFixnum;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.IOOutputStream;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.emitter.Emitter;
+import org.yaml.snakeyaml.emitter.EmitterException;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.events.AliasEvent;
+import org.yaml.snakeyaml.events.DocumentEndEvent;
+import org.yaml.snakeyaml.events.DocumentStartEvent;
+import org.yaml.snakeyaml.events.Event;
+import org.yaml.snakeyaml.events.ImplicitTuple;
+import org.yaml.snakeyaml.events.MappingEndEvent;
+import org.yaml.snakeyaml.events.MappingStartEvent;
+import org.yaml.snakeyaml.events.ScalarEvent;
+import org.yaml.snakeyaml.events.SequenceEndEvent;
+import org.yaml.snakeyaml.events.SequenceStartEvent;
+import org.yaml.snakeyaml.events.StreamEndEvent;
+import org.yaml.snakeyaml.events.StreamStartEvent;
+
+public class PsychEmitter extends RubyObject {
+ public static void initPsychEmitter(Ruby runtime, RubyModule psych) {
+ RubyClass psychHandler = runtime.defineClassUnder("Handler", runtime.getObject(), runtime.getObject()
+ .getAllocator(), psych);
+ RubyClass psychEmitter = runtime.defineClassUnder("Emitter", psychHandler, new ObjectAllocator() {
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new PsychEmitter(runtime, klazz);
+ }
+ }, psych);
+
+ psychEmitter.defineAnnotatedMethods(PsychEmitter.class);
+ }
+
+ public PsychEmitter(Ruby runtime, RubyClass klass) {
+ super(runtime, klass);
+ }
+
+ @JRubyMethod(visibility = PRIVATE)
+ public IRubyObject initialize(ThreadContext context, IRubyObject io) {
+ options = new DumperOptions();
+ options.setIndent(2);
+
+ this.io = io;
+
+ return context.nil;
+ }
+
+ @JRubyMethod(visibility = PRIVATE)
+ public IRubyObject initialize(ThreadContext context, IRubyObject io, IRubyObject rbOptions) {
+ IRubyObject width = rbOptions.callMethod(context, "line_width");
+ IRubyObject canonical = rbOptions.callMethod(context, "canonical");
+ IRubyObject level = rbOptions.callMethod(context, "indentation");
+
+ options = new DumperOptions();
+
+ options.setCanonical(canonical.isTrue());
+ options.setIndent((int) level.convertToInteger().getLongValue());
+ options.setWidth((int) width.convertToInteger().getLongValue());
+
+ this.io = io;
+
+ return context.nil;
+ }
+
+ @JRubyMethod
+ public IRubyObject start_stream(ThreadContext context, IRubyObject encoding) {
+ if (!(encoding instanceof RubyFixnum)) {
+ throw context.runtime.newTypeError(encoding, context.runtime.getFixnum());
+ }
+
+ initEmitter(context, encoding);
+
+ StreamStartEvent event = new StreamStartEvent(NULL_MARK, NULL_MARK);
+
+ emit(context, event);
+
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject end_stream(ThreadContext context) {
+ StreamEndEvent event = new StreamEndEvent(NULL_MARK, NULL_MARK);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject start_document(ThreadContext context, IRubyObject _version, IRubyObject tags,
+ IRubyObject implicit) {
+ DumperOptions.Version version = null;
+ boolean implicitBool = implicit.isTrue();
+ Map<String, String> tagsMap = null;
+
+ RubyArray versionAry = _version.convertToArray();
+ if (versionAry.size() == 2) {
+ int versionInt0 = (int) versionAry.eltInternal(0).convertToInteger().getLongValue();
+ int versionInt1 = (int) versionAry.eltInternal(1).convertToInteger().getLongValue();
+
+ if (versionInt0 == 1) {
+ if (versionInt1 == 0) {
+ version = DumperOptions.Version.V1_0;
+ } else if (versionInt1 == 1) {
+ version = DumperOptions.Version.V1_1;
+ }
+ }
+ if (version == null) {
+ throw context.runtime.newArgumentError("invalid YAML version: " + versionAry);
+ }
+ }
+
+ RubyArray tagsAry = tags.convertToArray();
+ if (tagsAry.size() > 0) {
+ tagsMap = new HashMap<String, String>(tagsAry.size());
+ for (int i = 0; i < tagsAry.size(); i++) {
+ RubyArray tagsTuple = tagsAry.eltInternal(i).convertToArray();
+ if (tagsTuple.size() != 2) {
+ throw context.runtime.newRuntimeError("tags tuple must be of length 2");
+ }
+ IRubyObject key = tagsTuple.eltInternal(0);
+ IRubyObject value = tagsTuple.eltInternal(1);
+ tagsMap.put(
+ key.asJavaString(),
+ value.asJavaString());
+ }
+ }
+
+ DocumentStartEvent event = new DocumentStartEvent(NULL_MARK, NULL_MARK, !implicitBool, version, tagsMap);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject end_document(ThreadContext context, IRubyObject implicit) {
+ DocumentEndEvent event = new DocumentEndEvent(NULL_MARK, NULL_MARK, !implicit.isTrue());
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod(required = 6)
+ public IRubyObject scalar(ThreadContext context, IRubyObject[] args) {
+ IRubyObject value = args[0];
+ IRubyObject anchor = args[1];
+ IRubyObject tag = args[2];
+ IRubyObject plain = args[3];
+ IRubyObject quoted = args[4];
+ IRubyObject style = args[5];
+
+ if (!(value instanceof RubyString)) {
+ throw context.runtime.newTypeError(value, context.runtime.getString());
+ }
+
+ ScalarEvent event = new ScalarEvent(
+ anchor.isNil() ? null : anchor.asJavaString(),
+ tag.isNil() ? null : tag.asJavaString(),
+ new ImplicitTuple(plain.isTrue(),
+ quoted.isTrue()),
+ value.asJavaString(),
+ NULL_MARK,
+ NULL_MARK,
+ SCALAR_STYLES[(int) style.convertToInteger().getLongValue()]);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod(required = 4)
+ public IRubyObject start_sequence(ThreadContext context, IRubyObject[] args) {
+ IRubyObject anchor = args[0];
+ IRubyObject tag = args[1];
+ IRubyObject implicit = args[2];
+ IRubyObject style = args[3];
+
+ final int SEQUENCE_BLOCK = 1; // see psych/nodes/sequence.rb
+
+ SequenceStartEvent event = new SequenceStartEvent(
+ anchor.isNil() ? null : anchor.asJavaString(),
+ tag.isNil() ? null : tag.asJavaString(),
+ implicit.isTrue(),
+ NULL_MARK,
+ NULL_MARK,
+ SEQUENCE_BLOCK != style.convertToInteger().getLongValue());
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject end_sequence(ThreadContext context) {
+ SequenceEndEvent event = new SequenceEndEvent(NULL_MARK, NULL_MARK);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod(required = 4)
+ public IRubyObject start_mapping(ThreadContext context, IRubyObject[] args) {
+ IRubyObject anchor = args[0];
+ IRubyObject tag = args[1];
+ IRubyObject implicit = args[2];
+ IRubyObject style = args[3];
+
+ final int MAPPING_BLOCK = 1; // see psych/nodes/mapping.rb
+
+ MappingStartEvent event = new MappingStartEvent(
+ anchor.isNil() ? null : anchor.asJavaString(),
+ tag.isNil() ? null : tag.asJavaString(),
+ implicit.isTrue(),
+ NULL_MARK,
+ NULL_MARK,
+ MAPPING_BLOCK != style.convertToInteger().getLongValue());
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject end_mapping(ThreadContext context) {
+ MappingEndEvent event = new MappingEndEvent(NULL_MARK, NULL_MARK);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod
+ public IRubyObject alias(ThreadContext context, IRubyObject anchor) {
+ AliasEvent event = new AliasEvent(anchor.asJavaString(), NULL_MARK, NULL_MARK);
+ emit(context, event);
+ return this;
+ }
+
+ @JRubyMethod(name = "canonical=")
+ public IRubyObject canonical_set(ThreadContext context, IRubyObject canonical) {
+ // TODO: unclear if this affects a running emitter
+ options.setCanonical(canonical.isTrue());
+ return canonical;
+ }
+
+ @JRubyMethod
+ public IRubyObject canonical(ThreadContext context) {
+ // TODO: unclear if this affects a running emitter
+ return context.runtime.newBoolean(options.isCanonical());
+ }
+
+ @JRubyMethod(name = "indentation=")
+ public IRubyObject indentation_set(ThreadContext context, IRubyObject level) {
+ // TODO: unclear if this affects a running emitter
+ options.setIndent((int) level.convertToInteger().getLongValue());
+ return level;
+ }
+
+ @JRubyMethod
+ public IRubyObject indentation(ThreadContext context) {
+ // TODO: unclear if this affects a running emitter
+ return context.runtime.newFixnum(options.getIndent());
+ }
+
+ @JRubyMethod(name = "line_width=")
+ public IRubyObject line_width_set(ThreadContext context, IRubyObject width) {
+ options.setWidth((int) width.convertToInteger().getLongValue());
+ return width;
+ }
+
+ @JRubyMethod
+ public IRubyObject line_width(ThreadContext context) {
+ return context.runtime.newFixnum(options.getWidth());
+ }
+
+ private void emit(ThreadContext context, Event event) {
+ try {
+ if (emitter == null)
+ throw context.runtime.newRuntimeError("uninitialized emitter");
+
+ emitter.emit(event);
+ } catch (IOException ioe) {
+ throw context.runtime.newIOErrorFromException(ioe);
+ } catch (EmitterException ee) {
+ throw context.runtime.newRuntimeError(ee.toString());
+ }
+ }
+
+ private void initEmitter(ThreadContext context, IRubyObject _encoding) {
+ if (emitter != null)
+ throw context.runtime.newRuntimeError("already initialized emitter");
+
+ Encoding encoding = PsychLibrary.YAMLEncoding.values()[(int) _encoding.convertToInteger().getLongValue()].encoding;
+ Charset charset = context.runtime.getEncodingService().charsetForEncoding(encoding);
+
+ emitter = new Emitter(new OutputStreamWriter(new IOOutputStream(io), charset), options);
+ }
+
+ Emitter emitter;
+ DumperOptions options = new DumperOptions();
+ IRubyObject io;
+
+ private static final Mark NULL_MARK = new Mark(null, 0, 0, 0, null, 0);
+
+ // Map style constants from Psych values (ANY = 0 ... FOLDED = 5)
+ // to SnakeYaml values; see psych/nodes/scalar.rb.
+ private static final Character[] SCALAR_STYLES = new Character[] {
+ null, // ANY; we'll choose plain
+ null, // PLAIN
+ '\'', // SINGLE_QUOTED
+ '"', // DOUBLE_QUOTED
+ '|', // LITERAL
+ '>', // FOLDED
+ };
+}
diff --git a/ext/java/PsychLibrary.java b/ext/java/PsychLibrary.java
new file mode 100644
index 0000000..6544e74
--- /dev/null
+++ b/ext/java/PsychLibrary.java
@@ -0,0 +1,81 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: EPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Eclipse Public
+ * License Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Charles O Nutter <headius@headius.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the EPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the EPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.psych;
+
+import org.jcodings.Encoding;
+import org.jcodings.specific.UTF16BEEncoding;
+import org.jcodings.specific.UTF16LEEncoding;
+import org.jcodings.specific.UTF8Encoding;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyModule;
+import org.jruby.RubyString;
+import org.jruby.ext.psych.PsychEmitter;
+import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodZero;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.Visibility;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class PsychLibrary {
+ public void load(final Ruby runtime) {
+ RubyModule psych = runtime.defineModule("Psych");
+
+ RubyString version = runtime.newString("0.1.4");
+ version.setFrozen(true);
+
+ final RubyArray versionElements = runtime.newArray(runtime.newFixnum(0), runtime.newFixnum(1),
+ runtime.newFixnum(4));
+ versionElements.setFrozen(true);
+
+ psych.setConstant("LIBYAML_VERSION", runtime.newString("0.1.4"));
+ psych.getSingletonClass().addMethod("libyaml_version", new JavaMethodZero(psych, Visibility.PUBLIC) {
+ @Override
+ public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) {
+ return versionElements;
+ }
+ });
+
+ PsychParser.initPsychParser(runtime, psych);
+ PsychEmitter.initPsychEmitter(runtime, psych);
+ PsychToRuby.initPsychToRuby(runtime, psych);
+ PsychYamlTree.initPsychYamlTree(runtime, psych);
+ }
+
+ public enum YAMLEncoding {
+ YAML_ANY_ENCODING(UTF8Encoding.INSTANCE),
+ YAML_UTF8_ENCODING(UTF8Encoding.INSTANCE),
+ YAML_UTF16LE_ENCODING(UTF16LEEncoding.INSTANCE),
+ YAML_UTF16BE_ENCODING(UTF16BEEncoding.INSTANCE);
+
+ YAMLEncoding(Encoding encoding) {
+ this.encoding = encoding;
+ }
+
+ public final Encoding encoding;
+ }
+}
diff --git a/ext/java/PsychParser.java b/ext/java/PsychParser.java
new file mode 100644
index 0000000..5df6ce6
--- /dev/null
+++ b/ext/java/PsychParser.java
@@ -0,0 +1,400 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: EPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Eclipse Public
+ * License Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Charles O Nutter <headius@headius.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the EPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the EPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.psych;
+
+import static org.jruby.ext.psych.PsychLibrary.YAMLEncoding.*;
+import static org.jruby.runtime.Helpers.*;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+import org.jcodings.Encoding;
+import org.jcodings.specific.UTF8Encoding;
+import org.jruby.Ruby;
+import org.jruby.RubyArray;
+import org.jruby.RubyClass;
+import org.jruby.RubyEncoding;
+import org.jruby.RubyIO;
+import org.jruby.RubyKernel;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.RubyString;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.Block;
+import org.jruby.runtime.ObjectAllocator;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+import org.jruby.util.ByteList;
+import org.jruby.util.IOInputStream;
+import org.jruby.util.log.Logger;
+import org.jruby.util.log.LoggerFactory;
+import org.jruby.util.unsafe.UnsafeFactory;
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.error.Mark;
+import org.yaml.snakeyaml.error.MarkedYAMLException;
+import org.yaml.snakeyaml.events.AliasEvent;
+import org.yaml.snakeyaml.events.DocumentEndEvent;
+import org.yaml.snakeyaml.events.DocumentStartEvent;
+import org.yaml.snakeyaml.events.Event;
+import org.yaml.snakeyaml.events.Event.ID;
+import org.yaml.snakeyaml.events.MappingStartEvent;
+import org.yaml.snakeyaml.events.ScalarEvent;
+import org.yaml.snakeyaml.events.SequenceStartEvent;
+import org.yaml.snakeyaml.parser.Parser;
+import org.yaml.snakeyaml.parser.ParserException;
+import org.yaml.snakeyaml.parser.ParserImpl;
+import org.yaml.snakeyaml.reader.ReaderException;
+import org.yaml.snakeyaml.reader.StreamReader;
+import org.yaml.snakeyaml.scanner.ScannerException;
+
+public class PsychParser extends RubyObject {
+
+ private static final Logger LOG = LoggerFactory.getLogger("PsychParser");
+
+ public static void initPsychParser(Ruby runtime, RubyModule psych) {
+ RubyClass psychParser = runtime.defineClassUnder("Parser", runtime.getObject(), new ObjectAllocator() {
+ public IRubyObject allocate(Ruby runtime, RubyClass klazz) {
+ return new PsychParser(runtime, klazz);
+ }
+ }, psych);
+
+ RubyKernel.require(runtime.getNil(),
+ runtime.newString("psych/syntax_error"), Block.NULL_BLOCK);
+ psychParser.defineConstant("ANY", runtime.newFixnum(YAML_ANY_ENCODING.ordinal()));
+ psychParser.defineConstant("UTF8", runtime.newFixnum(YAML_UTF8_ENCODING.ordinal()));
+ psychParser.defineConstant("UTF16LE", runtime.newFixnum(YAML_UTF16LE_ENCODING.ordinal()));
+ psychParser.defineConstant("UTF16BE", runtime.newFixnum(YAML_UTF16BE_ENCODING.ordinal()));
+
+ psychParser.defineAnnotatedMethods(PsychParser.class);
+ }
+
+ public PsychParser(Ruby runtime, RubyClass klass) {
+ super(runtime, klass);
+ }
+
+ @JRubyMethod
+ public IRubyObject parse(ThreadContext context, IRubyObject yaml) {
+ Ruby runtime = context.runtime;
+
+ return parse(context, yaml, runtime.getNil());
+ }
+
+ private IRubyObject stringOrNilFor(Ruby runtime, String value, boolean tainted) {
+ if (value == null)
+ return runtime.getNil(); // No need to taint nil
+
+ return stringFor(runtime, value, tainted);
+ }
+
+ private RubyString stringFor(Ruby runtime, String value, boolean tainted) {
+ Encoding encoding = runtime.getDefaultInternalEncoding();
+ if (encoding == null) {
+ encoding = UTF8Encoding.INSTANCE;
+ }
+
+ Charset charset = RubyEncoding.UTF8;
+ if (encoding.getCharset() != null) {
+ charset = encoding.getCharset();
+ }
+
+ ByteList bytes = new ByteList(value.getBytes(charset), encoding);
+ RubyString string = RubyString.newString(runtime, bytes);
+
+ string.setTaint(tainted);
+
+ return string;
+ }
+
+ private StreamReader readerFor(ThreadContext context, IRubyObject yaml) {
+ Ruby runtime = context.runtime;
+
+ if (yaml instanceof RubyString) {
+ ByteList byteList = ((RubyString) yaml).getByteList();
+ ByteArrayInputStream bais = new ByteArrayInputStream(byteList.getUnsafeBytes(), byteList.getBegin(),
+ byteList.getRealSize());
+
+ Charset charset = byteList.getEncoding().getCharset();
+ if (charset == null)
+ charset = Charset.defaultCharset();
+
+ InputStreamReader isr = new InputStreamReader(bais, charset);
+
+ return new StreamReader(isr);
+ }
+
+ // fall back on IOInputStream, using default charset
+ if (yaml.respondsTo("read")) {
+ return new StreamReader(new InputStreamReader(new IOInputStream(yaml), Charset.defaultCharset()));
+ } else {
+ throw runtime.newTypeError(yaml, runtime.getIO());
+ }
+ }
+
+ @JRubyMethod
+ public IRubyObject parse(ThreadContext context, IRubyObject yaml, IRubyObject path) {
+ Ruby runtime = context.runtime;
+ boolean tainted = yaml.isTaint() || yaml instanceof RubyIO;
+
+ try {
+ parser = new ParserImpl(readerFor(context, yaml));
+
+ if (path.isNil() && yaml.respondsTo("path")) {
+ path = yaml.callMethod(context, "path");
+ }
+
+ IRubyObject handler = getInstanceVariable("@handler");
+
+ while (true) {
+ event = parser.getEvent();
+
+ // FIXME: Event should expose a getID, so it can be switched
+ if (event.is(ID.StreamStart)) {
+ invoke(context, handler, "start_stream", runtime.newFixnum(YAML_ANY_ENCODING.ordinal()));
+ } else if (event.is(ID.DocumentStart)) {
+ handleDocumentStart(context, (DocumentStartEvent) event, tainted, handler);
+ } else if (event.is(ID.DocumentEnd)) {
+ IRubyObject notExplicit = runtime.newBoolean(!((DocumentEndEvent) event).getExplicit());
+
+ invoke(context, handler, "end_document", notExplicit);
+ } else if (event.is(ID.Alias)) {
+ IRubyObject alias = stringOrNilFor(runtime, ((AliasEvent) event).getAnchor(), tainted);
+
+ invoke(context, handler, "alias", alias);
+ } else if (event.is(ID.Scalar)) {
+ handleScalar(context, (ScalarEvent) event, tainted, handler);
+ } else if (event.is(ID.SequenceStart)) {
+ handleSequenceStart(context, (SequenceStartEvent) event, tainted, handler);
+ } else if (event.is(ID.SequenceEnd)) {
+ invoke(context, handler, "end_sequence");
+ } else if (event.is(ID.MappingStart)) {
+ handleMappingStart(context, (MappingStartEvent) event, tainted, handler);
+ } else if (event.is(ID.MappingEnd)) {
+ invoke(context, handler, "end_mapping");
+ } else if (event.is(ID.StreamEnd)) {
+ invoke(context, handler, "end_stream");
+
+ break;
+ }
+ }
+ } catch (ParserException pe) {
+ parser = null;
+ raiseParserException(context, yaml, pe, path);
+
+ } catch (ScannerException se) {
+ parser = null;
+ StringBuilder message = new StringBuilder("syntax error");
+ if (se.getProblemMark() != null) {
+ message.append(se.getProblemMark().toString());
+ }
+ raiseParserException(context, yaml, se, path);
+
+ } catch (ReaderException re) {
+ parser = null;
+ raiseParserException(context, yaml, re, path);
+
+ } catch (Throwable t) {
+ UnsafeFactory.getUnsafe().throwException(t);
+ return this;
+ }
+
+ return this;
+ }
+
+ private void handleDocumentStart(ThreadContext context, DocumentStartEvent dse, boolean tainted, IRubyObject handler) {
+ Ruby runtime = context.runtime;
+ DumperOptions.Version _version = dse.getVersion();
+ Integer[] versionInts = _version == null ? null : _version.getArray();
+ IRubyObject version = versionInts == null ?
+ RubyArray.newArray(runtime) :
+ RubyArray.newArray(runtime, runtime.newFixnum(versionInts[0]), runtime.newFixnum(versionInts[1]));
+
+ Map<String, String> tagsMap = dse.getTags();
+ RubyArray tags = RubyArray.newArray(runtime);
+ if (tagsMap != null && tagsMap.size() > 0) {
+ for (Map.Entry<String, String> tag : tagsMap.entrySet()) {
+ IRubyObject key = stringFor(runtime, tag.getKey(), tainted);
+ IRubyObject value = stringFor(runtime, tag.getValue(), tainted);
+
+ tags.append(RubyArray.newArray(runtime, key, value));
+ }
+ }
+ IRubyObject notExplicit = runtime.newBoolean(!dse.getExplicit());
+
+ invoke(context, handler, "start_document", version, tags, notExplicit);
+ }
+
+ private void handleMappingStart(ThreadContext context, MappingStartEvent mse, boolean tainted, IRubyObject handler) {
+ Ruby runtime = context.runtime;
+ IRubyObject anchor = stringOrNilFor(runtime, mse.getAnchor(), tainted);
+ IRubyObject tag = stringOrNilFor(runtime, mse.getTag(), tainted);
+ IRubyObject implicit = runtime.newBoolean(mse.getImplicit());
+ IRubyObject style = runtime.newFixnum(translateFlowStyle(mse.getFlowStyle()));
+
+ invoke(context, handler, "start_mapping", anchor, tag, implicit, style);
+ }
+
+ private void handleScalar(ThreadContext context, ScalarEvent se, boolean tainted, IRubyObject handler) {
+ Ruby runtime = context.runtime;
+ IRubyObject anchor = stringOrNilFor(runtime, se.getAnchor(), tainted);
+ IRubyObject tag = stringOrNilFor(runtime, se.getTag(), tainted);
+ IRubyObject plain_implicit = runtime.newBoolean(se.getImplicit().canOmitTagInPlainScalar());
+ IRubyObject quoted_implicit = runtime.newBoolean(se.getImplicit().canOmitTagInNonPlainScalar());
+ IRubyObject style = runtime.newFixnum(translateStyle(se.getStyle()));
+ IRubyObject val = stringFor(runtime, se.getValue(), tainted);
+
+ invoke(context, handler, "scalar", val, anchor, tag, plain_implicit,
+ quoted_implicit, style);
+ }
+
+ private void handleSequenceStart(ThreadContext context, SequenceStartEvent sse, boolean tainted, IRubyObject handler) {
+ Ruby runtime = context.runtime;
+ IRubyObject anchor = stringOrNilFor(runtime, sse.getAnchor(), tainted);
+ IRubyObject tag = stringOrNilFor(runtime, sse.getTag(), tainted);
+ IRubyObject implicit = runtime.newBoolean(sse.getImplicit());
+ IRubyObject style = runtime.newFixnum(translateFlowStyle(sse.getFlowStyle()));
+
+ invoke(context, handler, "start_sequence", anchor, tag, implicit, style);
+ }
+
+ private static void raiseParserException(ThreadContext context, IRubyObject yaml, ReaderException re,
+ IRubyObject rbPath) {
+ Ruby runtime;
+ RubyClass se;
+ IRubyObject exception;
+
+ runtime = context.runtime;
+ se = (RubyClass) runtime.getModule("Psych").getConstant("SyntaxError");
+
+ exception = se.newInstance(context,
+ new IRubyObject[] {
+ rbPath,
+ runtime.newFixnum(0),
+ runtime.newFixnum(0),
+ runtime.newFixnum(re.getPosition()),
+ (null == re.getName() ? runtime.getNil() : runtime.newString(re.getName())),
+ (null == re.toString() ? runtime.getNil() : runtime.newString(re.toString()))
+ },
+ Block.NULL_BLOCK);
+
+ RubyKernel.raise(context, runtime.getKernel(), new IRubyObject[] { exception }, Block.NULL_BLOCK);
+ }
+
+ private static void raiseParserException(ThreadContext context, IRubyObject yaml, MarkedYAMLException mye,
+ IRubyObject rbPath) {
+ Ruby runtime;
+ Mark mark;
+ RubyClass se;
+ IRubyObject exception;
+
+ runtime = context.runtime;
+ se = (RubyClass) runtime.getModule("Psych").getConstant("SyntaxError");
+
+ mark = mye.getProblemMark();
+
+ exception = se.newInstance(context,
+ new IRubyObject[] {
+ rbPath,
+ runtime.newFixnum(mark.getLine() + 1),
+ runtime.newFixnum(mark.getColumn() + 1),
+ runtime.newFixnum(mark.getIndex()),
+ (null == mye.getProblem() ? runtime.getNil() : runtime.newString(mye.getProblem())),
+ (null == mye.getContext() ? runtime.getNil() : runtime.newString(mye.getContext()))
+ },
+ Block.NULL_BLOCK);
+
+ RubyKernel.raise(context, runtime.getKernel(), new IRubyObject[] { exception }, Block.NULL_BLOCK);
+ }
+
+ private static int translateStyle(Character style) {
+ if (style == null)
+ return 0; // any
+
+ switch (style) {
+ case 0:
+ return 1; // plain
+ case '\'':
+ return 2; // single-quoted
+ case '"':
+ return 3; // double-quoted
+ case '|':
+ return 4; // literal
+ case '>':
+ return 5; // folded
+ default:
+ return 0; // any
+ }
+ }
+
+ private static int translateFlowStyle(Boolean flowStyle) {
+ if (flowStyle == null)
+ return 0; // any
+
+ if (flowStyle)
+ return 2;
+ return 1;
+ }
+
+ @JRubyMethod
+ public IRubyObject mark(ThreadContext context) {
+ Ruby runtime = context.runtime;
+
+ Event event = null;
+
+ if (parser != null) {
+ event = parser.peekEvent();
+
+ if (event == null)
+ event = this.event;
+ }
+
+ if (event == null) {
+ return ((RubyClass) context.runtime.getClassFromPath("Psych::Parser::Mark")).newInstance(
+ context,
+ runtime.newFixnum(0),
+ runtime.newFixnum(0),
+ runtime.newFixnum(0),
+ Block.NULL_BLOCK
+ );
+ }
+
+ Mark mark = event.getStartMark();
+
+ return ((RubyClass) context.runtime.getClassFromPath("Psych::Parser::Mark")).newInstance(
+ context,
+ runtime.newFixnum(mark.getIndex()),
+ runtime.newFixnum(mark.getLine()),
+ runtime.newFixnum(mark.getColumn()),
+ Block.NULL_BLOCK
+ );
+ }
+
+ private Parser parser;
+ private Event event;
+}
diff --git a/ext/java/PsychToRuby.java b/ext/java/PsychToRuby.java
new file mode 100644
index 0000000..42b1853
--- /dev/null
+++ b/ext/java/PsychToRuby.java
@@ -0,0 +1,78 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: EPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Eclipse Public
+ * License Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Charles O Nutter <headius@headius.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the EPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the EPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.psych;
+
+import static org.jruby.runtime.Visibility.*;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyException;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.exceptions.RaiseException;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class PsychToRuby {
+ public static void initPsychToRuby(Ruby runtime, RubyModule psych) {
+ RubyModule visitors = runtime.defineModuleUnder("Visitors", psych);
+ RubyClass classLoader = runtime.defineClassUnder("ClassLoader", runtime.getObject(), runtime.getObject()
+ .getAllocator(), psych);
+ RubyClass visitor = runtime.defineClassUnder("Visitor", runtime.getObject(),
+ runtime.getObject().getAllocator(), visitors);
+ RubyClass psychToRuby = runtime.defineClassUnder("ToRuby", visitor, RubyObject.OBJECT_ALLOCATOR, visitors);
+
+ psychToRuby.defineAnnotatedMethod(PsychToRuby.class, "build_exception");
+ classLoader.defineAnnotatedMethod(PsychToRuby.class, "path2class");
+ }
+
+ @JRubyMethod(visibility = PRIVATE)
+ public static IRubyObject build_exception(ThreadContext context, IRubyObject self, IRubyObject klass,
+ IRubyObject message) {
+ if (klass instanceof RubyClass) {
+ IRubyObject exception = ((RubyClass) klass).allocate();
+ ((RubyException) exception).message = message;
+ return exception;
+ } else {
+ throw context.runtime.newTypeError(klass, context.runtime.getClassClass());
+ }
+ }
+
+ @JRubyMethod(visibility = PRIVATE)
+ public static IRubyObject path2class(ThreadContext context, IRubyObject self, IRubyObject path) {
+ try {
+ return context.runtime.getClassFromPath(path.asJavaString());
+ } catch (RaiseException re) {
+ if (re.getException().getMetaClass() == context.runtime.getNameError()) {
+ throw context.runtime.newArgumentError("undefined class/module " + path);
+ }
+ throw re;
+ }
+ }
+}
diff --git a/ext/java/PsychYamlTree.java b/ext/java/PsychYamlTree.java
new file mode 100644
index 0000000..d78e1c1
--- /dev/null
+++ b/ext/java/PsychYamlTree.java
@@ -0,0 +1,58 @@
+/***** BEGIN LICENSE BLOCK *****
+ * Version: EPL 1.0/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Eclipse Public
+ * License Version 1.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.eclipse.org/legal/epl-v10.html
+ *
+ * Software distributed under the License is distributed on an "AS
+ * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
+ * implied. See the License for the specific language governing
+ * rights and limitations under the License.
+ *
+ * Copyright (C) 2010 Charles O Nutter <headius@headius.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the EPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the EPL, the GPL or the LGPL.
+ ***** END LICENSE BLOCK *****/
+package org.psych;
+
+import static org.jruby.runtime.Visibility.*;
+
+import org.jruby.Ruby;
+import org.jruby.RubyClass;
+import org.jruby.RubyModule;
+import org.jruby.RubyObject;
+import org.jruby.anno.JRubyMethod;
+import org.jruby.runtime.ThreadContext;
+import org.jruby.runtime.builtin.IRubyObject;
+
+public class PsychYamlTree {
+ public static void initPsychYamlTree(Ruby runtime, RubyModule psych) {
+ RubyModule visitors = (RubyModule) psych.getConstant("Visitors");
+ RubyClass visitor = (RubyClass) visitors.getConstant("Visitor");
+ RubyClass psychYamlTree = runtime.defineClassUnder("YAMLTree", visitor, RubyObject.OBJECT_ALLOCATOR, visitors);
+
+ psychYamlTree.defineAnnotatedMethods(PsychYamlTree.class);
+ }
+
+ @JRubyMethod(visibility = PRIVATE)
+ public static IRubyObject private_iv_get(ThreadContext context, IRubyObject self, IRubyObject target,
+ IRubyObject prop) {
+ IRubyObject obj = (IRubyObject) target.getInternalVariables().getInternalVariable(prop.asJavaString());
+ if (obj == null)
+ obj = context.nil;
+
+ return obj;
+ }
+}
diff --git a/lib/psych.rb b/lib/psych.rb
index 711b3c1..5d89f3b 100644
--- a/lib/psych.rb
+++ b/lib/psych.rb
@@ -1,4 +1,11 @@
-require 'psych.so'
+if RUBY_PLATFORM =~ /java/
+ require 'jruby'
+ require './psych.jar'
+ org.psych.PsychLibrary.new.load(JRuby.runtime)
+else
+ require 'psych.so'
+end
+
require 'psych/nodes'
require 'psych/streaming'
require 'psych/visitors'
@@ -219,7 +226,7 @@ module Psych
VERSION = '2.0.0'
# The version of libyaml Psych is using
- LIBYAML_VERSION = Psych.libyaml_version.join '.'
+ LIBYAML_VERSION = Psych.libyaml_version.join '.' unless RUBY_PLATFORM =~ /java/
###
# Load +yaml+ in to a Ruby data structure. If multiple documents are
diff --git a/lib/snakeyaml-1.11.jar b/lib/snakeyaml-1.11.jar
new file mode 100644
index 0000000..49ecd87
--- /dev/null
+++ b/lib/snakeyaml-1.11.jar
Binary files differ