1/*
2 * Copyright (c) 2006-2008, The RubyCocoa Project.
3 * Copyright (c) 2001-2006, FUJIMOTO Hisakuni.
4 * All Rights Reserved.
5 *
6 * RubyCocoa is free software, covered under either the Ruby's license or the
7 * LGPL. See the COPYRIGHT file for more information.
8 */
9
10#import <Foundation/NSBundle.h>
11#import <Foundation/NSAutoreleasePool.h>
12#import <Foundation/NSDictionary.h>
13#import "osx_ruby.h"
14#import "mdl_osxobjc.h"
15#import "ocdata_conv.h"
16#import "internal_macros.h"
17#import "objc_compat.h"
18
19/** module OSX::BundleSupport  **/
20static VALUE _mBundleSupport = Qnil;
21static NSMutableDictionary* gBundleMap;
22static const char* BUNDLE_MAP_NAME = "BUNDLE_MAP";
23static const char* BUNDLE_STACK_NAME = "BUNDLE_STACK";
24#define BUNDLE_STACK  rb_const_get(_mBundleSupport, rb_intern(BUNDLE_STACK_NAME))
25
26
27/** bundle_stack - stack for the current bundle and related
28 * parameter
29 *
30 * _push_bundle([ bundle, additional_param ])
31 * _pop_bundle     => [ bundle, additional_param ] or nil
32 * _current_bundle => [ bundle, additional_param ] or nil
33 *
34 * *NOTE* ocid_to_rbobj called at doing to pop/fetch a stack item,
35 * rather than at the push time, because it's not work at that time
36 * around the ns_import method.
37 **/
38
39/* this function should be called from inside a NSAutoreleasePool */
40static NSBundle* bundle_for(Class klass)
41{
42  return (klass == nil) ?
43    [NSBundle mainBundle] :
44    [NSBundle bundleForClass: klass];
45}
46
47static VALUE _make_stack_item(Class objc_class, id additional_param)
48{
49  VALUE args = Qnil;
50
51  POOL_DO(pool) {
52    id bundle;
53    VALUE a0, a1;
54
55    bundle = bundle_for(objc_class);
56    a0 = OCID2NUM(bundle);
57    a1 = OCID2NUM(additional_param);
58    args = rb_ary_new3(2, a0, a1);
59  } END_POOL(pool);
60  return args;
61}
62
63static void  _push_bundle(VALUE args) { (void) rb_ary_push(BUNDLE_STACK, args); }
64static void  _pop_bundle()            { (void) rb_ary_pop(BUNDLE_STACK); }
65
66static VALUE _current_bundle()
67{
68  VALUE item;
69  id bundle_id, param_id;
70  VALUE bundle, param;
71
72  item = rb_funcall(BUNDLE_STACK, rb_intern("last"), 0);
73  if (! NIL_P(item)) {
74    bundle_id = NUM2OCID(rb_ary_entry(item, 0));
75    param_id  = NUM2OCID(rb_ary_entry(item, 1));
76    bundle = ocid_to_rbobj(Qnil, bundle_id);
77    param  = ocid_to_rbobj(Qnil, param_id);
78    return rb_ary_new3(2, bundle, param);
79  }
80  return Qnil;
81}
82
83static VALUE rb_current_bundle(VALUE mdl) { return _current_bundle(); }
84
85
86/** bundle_map - the  mapping table of class to bundle **/
87
88static id _ruby2ocid(VALUE obj)
89{
90#if 1
91  id ocid;
92  return (rbobj_to_nsobj(obj, &ocid) == YES) ? ocid : nil;
93#else
94  return rbobj_get_ocid(obj);
95#endif
96}
97
98static id
99bundle_for_class(Class klass)
100{
101  return [gBundleMap objectForKey:klass];
102}
103
104static VALUE
105rb_bundle_for_class(VALUE mdl, VALUE objc_class)
106{
107  return ocid_get_rbobj([gBundleMap objectForKey:_ruby2ocid(objc_class)]);
108}
109
110static VALUE
111rb_bind_class_with_current_bundle(VALUE mdl, VALUE objc_class)
112{
113  VALUE stack_item;
114  stack_item = _current_bundle();
115  if (! NIL_P(stack_item)) {
116    VALUE bundle = rb_ary_entry(stack_item, 0);
117    if (!gBundleMap) gBundleMap = [NSMutableDictionary new];
118	[gBundleMap setObject:[NSNumber numberWithUnsignedLongLong:bundle] forKey:_ruby2ocid(objc_class)];
119    return bundle;
120  }
121  return Qnil;
122}
123
124static VALUE my_load_clause(VALUE prog_name)
125{
126  rb_require(StringValuePtr(prog_name));
127  return Qnil;
128}
129
130static VALUE my_eval_clause(VALUE prog_source)
131{
132  rb_eval_string(StringValuePtr(prog_source));
133  return Qnil;
134}
135
136static VALUE my_rescue_clause(VALUE prog_name)
137{
138  return ruby_errinfo;
139}
140
141/**
142  def load_ruby_program_for_class(path, objc_klass, additional_param)
143    _push_bundle(bundle for objc_class, additional_param)
144    require(prog_name)
145    nil
146  rescue Exception => err
147    err
148  ensure
149    _pop_bundle
150  end
151**/
152
153VALUE
154load_ruby_program_for_class(const char* path, Class objc_class, id additional_param)
155{
156  VALUE prog_name, stack_item, result;
157
158  prog_name  = rb_str_new2(path);
159  stack_item = _make_stack_item(objc_class, additional_param);
160  _push_bundle(stack_item);
161  result = rb_rescue2( my_load_clause,   prog_name,
162                       my_rescue_clause, prog_name, rb_eException, (VALUE)0);
163  _pop_bundle();
164  return result;
165}
166
167VALUE
168eval_ruby_program_for_class(const char* program, Class objc_class, id additional_param)
169{
170  VALUE prog_source, stack_item, result;
171
172  prog_source = rb_str_new2(program);
173  stack_item  = _make_stack_item(objc_class, additional_param);
174  _push_bundle(stack_item);
175  result = rb_rescue2( my_eval_clause,   prog_source,
176                       my_rescue_clause, Qnil, rb_eException, (VALUE)0);
177  _pop_bundle();
178  return result;
179}
180
181
182/* replace NSBundle.bundleForClass */
183static IMP original_bundleForClass = NULL;
184
185static id rubycocoa_bundleForClass(id rcv, SEL op, id klass)
186{
187  id bundle = bundle_for_class(klass);
188  if (! bundle)
189    bundle = original_bundleForClass(rcv, op, klass);
190  return bundle;
191}
192
193static void setup_bundleForClass()
194{
195  if (original_bundleForClass == NULL) {
196    Method method;
197    method = class_getClassMethod([NSBundle class], @selector(bundleForClass:));
198    if (method) {
199      original_bundleForClass = method_getImplementation(method);
200      method_setImplementation(method, (IMP)rubycocoa_bundleForClass);
201    }
202  }
203}
204
205/** initialize primitive functions for module OSX::BundleSupport **/
206void
207initialize_mdl_bundle_support()
208{
209  if (NIL_P(_mBundleSupport)) {
210    _mBundleSupport = rb_define_module_under(osx_s_module(), "BundleSupport");
211
212    rb_define_const(_mBundleSupport, BUNDLE_MAP_NAME,   ocid_get_rbobj(gBundleMap));
213    rb_define_const(_mBundleSupport, BUNDLE_STACK_NAME, rb_ary_new());
214
215    rb_define_module_function(_mBundleSupport,
216                              "bundle_for_class",
217			      rb_bundle_for_class, 1);
218
219    rb_define_module_function(_mBundleSupport,
220                              "bind_class_with_current_bundle",
221			      rb_bind_class_with_current_bundle, 1);
222
223    rb_define_module_function(_mBundleSupport,
224                              "_current_bundle",
225                              rb_current_bundle, 0);
226    setup_bundleForClass();
227  }
228}
229