summaryrefslogtreecommitdiff
path: root/java/src/json/ext/RuntimeInfo.java
blob: 2323bd942b116fb6243527fa56c6ab1fb054da67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
/*
 * This code is copyrighted work by Daniel Luz <dev at mernen dot com>.
 *
 * Distributed under the Ruby license: https://www.ruby-lang.org/en/about/license.txt
 */
package json.ext;

import java.lang.ref.WeakReference;
import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;
import org.jruby.Ruby;
import org.jruby.RubyClass;
import org.jruby.RubyEncoding;
import org.jruby.RubyModule;
import org.jruby.runtime.ThreadContext;
import org.jruby.runtime.builtin.IRubyObject;


final class RuntimeInfo {
    // since the vast majority of cases runs just one runtime,
    // we optimize for that
    private static WeakReference<Ruby> runtime1 = new WeakReference<Ruby>(null);
    private static RuntimeInfo info1;
    // store remaining runtimes here (does not include runtime1)
    private static Map<Ruby, RuntimeInfo> runtimes;

    // these fields are filled by the service loaders
    // Use WeakReferences so that RuntimeInfo doesn't indirectly hold a hard reference to
    // the Ruby runtime object, which would cause memory leaks in the runtimes map above.
    /** JSON */
    WeakReference<RubyModule> jsonModule;
    /** JSON::Ext::Generator::GeneratorMethods::String::Extend */
    WeakReference<RubyModule> stringExtendModule;
    /** JSON::Ext::Generator::State */
    WeakReference<RubyClass> generatorStateClass;
    /** JSON::SAFE_STATE_PROTOTYPE */
    WeakReference<GeneratorState> safeStatePrototype;

    final WeakReference<RubyEncoding> utf8;
    final WeakReference<RubyEncoding> ascii8bit;
    // other encodings
    private final Map<String, WeakReference<RubyEncoding>> encodings;

    private RuntimeInfo(Ruby runtime) {
        RubyClass encodingClass = runtime.getEncoding();
        if (encodingClass == null) { // 1.8 mode
            utf8 = ascii8bit = null;
            encodings = null;
        } else {
            ThreadContext context = runtime.getCurrentContext();

            utf8 = new WeakReference<RubyEncoding>((RubyEncoding)RubyEncoding.find(context,
                    encodingClass, runtime.newString("utf-8")));
            ascii8bit = new WeakReference<RubyEncoding>((RubyEncoding)RubyEncoding.find(context,
                    encodingClass, runtime.newString("ascii-8bit")));
            encodings = new HashMap<String, WeakReference<RubyEncoding>>();
        }
    }

    static RuntimeInfo initRuntime(Ruby runtime) {
        synchronized (RuntimeInfo.class) {
            if (runtime1.get() == runtime) {
                return info1;
            } else if (runtime1.get() == null) {
                runtime1 = new WeakReference<Ruby>(runtime);
                info1 = new RuntimeInfo(runtime);
                return info1;
            } else {
                if (runtimes == null) {
                    runtimes = new WeakHashMap<Ruby, RuntimeInfo>(1);
                }
                RuntimeInfo cache = runtimes.get(runtime);
                if (cache == null) {
                    cache = new RuntimeInfo(runtime);
                    runtimes.put(runtime, cache);
                }
                return cache;
            }
        }
    }

    public static RuntimeInfo forRuntime(Ruby runtime) {
        synchronized (RuntimeInfo.class) {
            if (runtime1.get() == runtime) return info1;
            RuntimeInfo cache = null;
            if (runtimes != null) cache = runtimes.get(runtime);
            assert cache != null : "Runtime given has not initialized JSON::Ext";
            return cache;
        }
    }

    public RubyEncoding getEncoding(ThreadContext context, String name) {
        synchronized (encodings) {
            WeakReference<RubyEncoding> encoding = encodings.get(name);
            if (encoding == null) {
                Ruby runtime = context.getRuntime();
                encoding = new WeakReference<RubyEncoding>((RubyEncoding)RubyEncoding.find(context,
                        runtime.getEncoding(), runtime.newString(name)));
                encodings.put(name, encoding);
            }
            return encoding.get();
        }
    }

    public GeneratorState getSafeStatePrototype(ThreadContext context) {
        if (safeStatePrototype == null) {
            IRubyObject value = jsonModule.get().getConstant("SAFE_STATE_PROTOTYPE");
            if (!(value instanceof GeneratorState)) {
                throw context.getRuntime().newTypeError(value, generatorStateClass.get());
            }
            safeStatePrototype = new WeakReference<GeneratorState>((GeneratorState)value);
        }
        return safeStatePrototype.get();
    }
}